Skye Route & Dispatch Vault Offline dispatch • proof-of-delivery • exportable logs
'; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

Operator launch board

' + (latest ? ('
'+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
'+esc(latest.note || '')+'
Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } async function runLegacyRecordProofComplete(){ const laneResults = []; const importedRows = []; for(const lane of laneOrder){ let bundle = null; let matrix = null; try{ bundle = await runProofLaneBundleSmart(lane); }catch(err){ bundle = { states:{ legacy:false, restore:false }, saved:{ note: clean(err && err.message) || 'Bundle failed.' } }; } try{ matrix = await runHistoricalGenerationMatrix(lane); }catch(err){ matrix = { id:'', variants:[], note: clean(err && err.message) || 'Generation matrix failed.' }; } const states = (bundle && bundle.saved && bundle.saved.proofStates) ? bundle.saved.proofStates : ((bundle && bundle.states) || {}); const matrixVariants = Array.isArray(matrix && matrix.variants) ? matrix.variants.length : 0; const ok = !!states.legacy && !!states.restore && matrixVariants > 0; const note = [clean(bundle && bundle.saved && bundle.saved.note), clean(matrix && matrix.note)].filter(Boolean).join(' • '); laneResults.push({ lane, ok, states, matrixId: clean(matrix && matrix.id), matrixVariants, note }); importedRows.push(buildLegacyIntakeRow(lane, bundle, matrix)); } importedRows.forEach(row => pushLegacyIntake(row)); importedRows.forEach(row => pushLegacyOutbox({ ...row, exportedAt: nowISO(), source:'routex-legacy-outbox' })); const digest = JSON.stringify({ laneResults: laneResults.map(item => ({ lane:item.lane, ok:item.ok, matrix:item.matrixId })), imported: importedRows.map(item => item.fingerprint) }); const ok = laneResults.every(item => item.ok) && importedRows.length === laneOrder.length; const row = pushLegacyRun({ id: uid(), createdAt: nowISO(), source: 'legacy-record-proof-complete', label: 'Legacy record proof • ' + dayISO(), fingerprint: 'lrp-' + hash(digest), ok, importedCount: importedRows.length, outboxCount: readLegacyOutbox().length, laneResults, note: ok ? 'Legacy-record proof runner saved imported legacy proof rows, generation matrices, and AE FLOW outbox payloads for every directive-first lane.' : 'Legacy-record proof runner saved evidence, but one or more lanes still need review.' }); return row; } function exportLatestLegacyProofHtml(){ const row = readLegacyRuns()[0]; if(!row) return toast('Run legacy proof first.', 'warn'); downloadText(buildLegacyRunHtml(row), 'routex_legacy_record_proof_' + dayISO() + '.html', 'text/html'); toast('Legacy proof HTML exported.', 'good'); } function exportLatestLegacyProofJson(){ const row = readLegacyRuns()[0]; if(!row) return toast('Run legacy proof first.', 'warn'); downloadText(JSON.stringify(row, null, 2), 'routex_legacy_record_proof_' + dayISO() + '.json', 'application/json'); toast('Legacy proof JSON exported.', 'good'); } function openLegacyProofRunnerManager(){ const rows = readLegacyRuns().map(item => '
'+esc(item.label || 'Legacy proof')+' '+esc(item.fingerprint || '—')+'
'+esc(fmt(item.createdAt || Date.now()))+' • '+esc(item.note || '')+'
').join('') || '
No legacy-record proof runs saved yet.
'; openModal('Legacy record proof', '
This is the one-click legacy lane runner. It replays the directive-first lanes through the legacy fixture path, saves historical matrix evidence, stores legacy intake rows, and pushes a shared outbox payload AE FLOW can sync.
Saved legacy intake rows: '+esc(String(readLegacyIntake().length))+' • Shared legacy outbox: '+esc(String(readLegacyOutbox().length))+'
'+rows+'
', ''); $('#legacy_run').onclick = async ()=>{ const btn = $('#legacy_run'); if(btn){ btn.disabled = true; btn.textContent = 'Running...'; } const row = await runLegacyRecordProofComplete(); if(btn){ btn.disabled = false; btn.textContent = 'Run legacy proof'; } toast(row.ok ? 'Legacy-record proof passed.' : 'Legacy-record proof needs review.', row.ok ? 'good' : 'warn'); closeModal(); openLegacyProofRunnerManager(); }; $('#legacy_export_html').onclick = exportLatestLegacyProofHtml; $('#legacy_export_json').onclick = exportLatestLegacyProofJson; Array.from(document.querySelectorAll('[data-legacy-html]')).forEach(btn => btn.onclick = ()=>{ const row = readLegacyRuns().find(item => clean(item.id) === clean(btn.getAttribute('data-legacy-html'))); if(!row) return; downloadText(buildLegacyRunHtml(row), 'routex_legacy_record_proof_' + clean(row.fingerprint || dayISO()) + '.html', 'text/html'); toast('Legacy proof HTML exported.', 'good'); }); Array.from(document.querySelectorAll('[data-legacy-json]')).forEach(btn => btn.onclick = ()=>{ const row = readLegacyRuns().find(item => clean(item.id) === clean(btn.getAttribute('data-legacy-json'))); if(!row) return; downloadText(JSON.stringify(row, null, 2), 'routex_legacy_record_proof_' + clean(row.fingerprint || dayISO()) + '.json', 'application/json'); toast('Legacy proof JSON exported.', 'good'); }); } function injectLegacyButtons(){ const footer = $('#cc_capture') && $('#cc_capture').parentNode; if(footer && !$('#cc_legacy_runs')){ const mgr = document.createElement('button'); mgr.className='btn'; mgr.id='cc_legacy_runs'; mgr.textContent='Legacy proof'; mgr.onclick=()=> openLegacyProofRunnerManager(); footer.insertBefore(mgr, $('#cc_capture')); } if(footer && !$('#cc_run_legacy')){ const btn = document.createElement('button'); btn.className='btn'; btn.id='cc_run_legacy'; btn.textContent='Run legacy proof'; btn.onclick = async ()=>{ btn.disabled = true; btn.textContent = 'Running...'; const row = await runLegacyRecordProofComplete(); btn.disabled = false; btn.textContent = 'Run legacy proof'; toast(row.ok ? 'Legacy-record proof passed.' : 'Legacy-record proof needs review.', row.ok ? 'good' : 'warn'); try{ if(typeof closeModal === 'function') closeModal(); }catch(_){ } if(typeof window.openRoutexCompletionCenter === 'function') window.openRoutexCompletionCenter(); }; footer.insertBefore(btn, $('#cc_capture')); } const row = document.querySelector('#pv_completion_center .row') || document.querySelector('#st_completion_center')?.parentNode; if(row && !document.querySelector('#pv_legacy_manager')){ const btn = document.createElement('button'); btn.className='btn'; btn.id='pv_legacy_manager'; btn.textContent='Legacy proof'; btn.onclick=()=> openLegacyProofRunnerManager(); row.appendChild(btn); } } const prevOpenCompletion = window.openRoutexCompletionCenter; if(typeof prevOpenCompletion === 'function'){ window.openRoutexCompletionCenter = function(){ const out = prevOpenCompletion.apply(this, arguments); setTimeout(injectLegacyButtons, 0); return out; }; } const prevRenderAll = window.renderAll; if(typeof prevRenderAll === 'function'){ window.renderAll = function(){ const out = prevRenderAll.apply(this, arguments); setTimeout(injectLegacyButtons, 0); return out; }; } if(typeof MutationObserver === 'function' && document.body){ const mo = new MutationObserver(()=> injectLegacyButtons()); mo.observe(document.body, { childList:true, subtree:true }); } window.listLegacyProofIntake = readLegacyIntake; window.readRoutexLegacyProofRuns = readLegacyRuns; window.runLegacyRecordProofComplete = runLegacyRecordProofComplete; window.openRoutexLegacyProofManager = openLegacyProofRunnerManager; })(); '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

Operator launch board

' + (latest ? ('
'+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
'+esc(latest.note || '')+'
Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); `; } function buildQuoteDraftHtml(route, stop){ return `${escapeHTML(stop.label)} — Quote Draft

Quote Draft

Route: ${escapeHTML(route.name)} • Date: ${escapeHTML(route.date || '')}
Client: ${escapeHTML(stop.label)} • Address: ${escapeHTML(stop.address || stop.serviceArea || '—')}
Line itemDescriptionAmount
VisitOn-site route visit / dispatch stop$0.00
ScopeFill in service scope, materials, and labor$0.00
Follow-up${escapeHTML(stop.outcomeNote || 'Complete next-step scope')}$0.00
Generated offline from route + client data. Final pricing must be set by operator.
\n '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

Operator launch board

' + (latest ? ('
'+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
'+esc(latest.note || '')+'
Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); `; } function buildFollowupSheetHtml(route, stop){ return `${escapeHTML(stop.label)} — Follow-up Sheet

Follow-up Sheet

Client: ${escapeHTML(stop.label)}
Route: ${escapeHTML(route.name)} • Date: ${escapeHTML(route.date || '')}
Best contact lane: ${escapeHTML(joinNonEmpty([stop.contact, stop.phone, stop.businessEmail], ' • ') || '—')}
Address / area: ${escapeHTML(stop.address || stop.serviceArea || '—')}
Outcome requiring follow-up: ${escapeHTML(stop.outcomeNote || STOP_STATUS_LABELS[stop.status] || stop.status || 'Pending')}
Suggested next action: Revisit, call back, send follow-up note, or attach a quote/service summary from the doc vault.
\n '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

Operator launch board

' + (latest ? ('
'+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
'+esc(latest.note || '')+'
Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); `; } function openSignatureCaptureModal(routeId, stopId){ const route = APP.cached.routes.find(item => item.id === routeId); const stop = APP.cached.stops.find(item => item.id === stopId); if(!route || !stop) return; openModal(`Signature • ${escapeHTML(stop.label)}`, `
Capture a local offline signature and store it in the document vault for this stop.
`, ``); const canvas = $('#sig_canvas'); const ctx = canvas.getContext('2d'); ctx.fillStyle = '#fff'; ctx.fillRect(0,0,canvas.width,canvas.height); ctx.lineWidth = 3; ctx.lineCap = 'round'; ctx.strokeStyle = '#111'; let drawing = false; const pos = e => { const rect = canvas.getBoundingClientRect(); const point = e.touches && e.touches[0] ? e.touches[0] : e; return { x: ((point.clientX - rect.left) / rect.width) * canvas.width, y: ((point.clientY - rect.top) / rect.height) * canvas.height }; }; const start = e => { drawing = true; const p = pos(e); ctx.beginPath(); ctx.moveTo(p.x, p.y); e.preventDefault(); }; const move = e => { if(!drawing) return; const p = pos(e); ctx.lineTo(p.x, p.y); ctx.stroke(); e.preventDefault(); }; const end = e => { drawing = false; e.preventDefault(); }; ['mousedown','touchstart'].forEach(ev => canvas.addEventListener(ev, start)); ['mousemove','touchmove'].forEach(ev => canvas.addEventListener(ev, move)); ['mouseup','mouseleave','touchend','touchcancel'].forEach(ev => canvas.addEventListener(ev, end)); $('#sig_clear').onclick = ()=>{ ctx.fillStyle = '#fff'; ctx.fillRect(0,0,canvas.width,canvas.height); }; $('#sig_save').onclick = ()=>{ const dataUrl = canvas.toDataURL('image/png'); saveVaultDoc({ routeId, stopId, sourceAccountId: stop.sourceAccountId, businessEmail: stop.businessEmail, businessName: stop.label, title: `${stop.label} — Signature`, kind:'signature', mime:'image/png', tags:['signature','completion-proof'], note: $('#sig_note').value, attachments:[{ id:uid(), name:'signature.png', type:'image/png', size: Math.round(dataUrl.length * .75), dataUrl, createdAt: nowISO() }] }); toast('Signature stored in doc vault.', 'good'); closeModal(); }; } function openDocVaultModal(routeId, stopId, accountInput){ const route = APP.cached.routes.find(item => item.id === cleanStr(routeId)); const stop = APP.cached.stops.find(item => item.id === cleanStr(stopId)); const account = accountInput ? sanitizeAEFlowAccount(accountInput) : (stop ? { id: stop.sourceAccountId, business_email: stop.businessEmail, business_name: stop.label } : null); const docs = stop ? getVaultDocsForStop(stop) : (account ? getVaultDocsForAccount(account) : []); const rows = docs.map(doc => `
${escapeHTML(doc.title)} ${escapeHTML(doc.kind)}${doc.tags.length ? ` ${escapeHTML(doc.tags.join(', '))}` : ''}
${escapeHTML(fmt(doc.updatedAt))}${doc.note ? ` • ${escapeHTML(doc.note)}` : ''}${doc.attachments.length ? ` • ${doc.attachments.length} attachment${doc.attachments.length===1?'':'s'}` : ''}
`).join(''); openModal(`Document Vault • ${escapeHTML((stop && stop.label) || (account && account.business_name) || 'Account')}`, `
Store files, generated HTML docs, notes, signatures, receipts, and proof add-ons locally for this stop/client. Selected docs can be included in proof packets and backups.
${stop ? `` : ''}
${rows || `
No vault docs yet for this client/stop.
`}
`, ``); if($('#dv_upload')) $('#dv_upload').onclick = async ()=>{ const attachments = await filesToDataAttachments(Array.from($('#dv_files').files || []), 8, 6); if(!attachments.length){ toast('Choose at least one file.', 'warn'); return; } saveVaultDoc({ routeId: cleanStr(routeId), stopId: cleanStr(stopId), sourceAccountId: stop ? stop.sourceAccountId : (account && account.id), businessEmail: stop ? stop.businessEmail : (account && account.business_email), businessName: stop ? stop.label : (account && account.business_name), title: $('#dv_title').value || attachments[0].name, kind:'file', mime: attachments[0].type, tags: normalizeTags(String($('#dv_tags').value || '').split(',')), note: $('#dv_note').value, attachments }); toast('Vault upload saved.', 'good'); openDocVaultModal(routeId, stopId, account); }; if($('#dv_note_doc')) $('#dv_note_doc').onclick = ()=>{ const note = cleanStr($('#dv_note').value); if(!note){ toast('Add a note first.', 'warn'); return; } makeHtmlDocEntry({ routeId: cleanStr(routeId), stopId: cleanStr(stopId), sourceAccountId: stop ? stop.sourceAccountId : (account && account.id), businessEmail: stop ? stop.businessEmail : (account && account.business_email), businessName: stop ? stop.label : (account && account.business_name), title: $('#dv_title').value || `${(stop && stop.label) || (account && account.business_name) || 'Account'} — Note`, kind:'note', tags: normalizeTags(String($('#dv_tags').value || '').split(',')), note }, `
${escapeHTML(note)}
'; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

Operator launch board

' + (latest ? ('
'+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
'+esc(latest.note || '')+'
Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); `); toast('Note doc saved.', 'good'); openDocVaultModal(routeId, stopId, account); }; if($('#dv_signature')) $('#dv_signature').onclick = ()=> openSignatureCaptureModal(routeId, stopId); if($('#dv_quote')) $('#dv_quote').onclick = ()=>{ const targetStop = stop || (account && { label: account.business_name, businessEmail: account.business_email, sourceAccountId: account.id, address: buildAEFlowPreciseAddress(account), serviceArea: account.service_area, phone: account.phone, contact: account.contact_name, notes: account.notes, status: 'pending', outcomeNote:'' }); if(!route || !targetStop){ toast('Open this from a route stop to generate a quote draft.', 'warn'); return; } makeHtmlDocEntry({ routeId: route.id, stopId: cleanStr(targetStop.id), sourceAccountId: targetStop.sourceAccountId, businessEmail: targetStop.businessEmail, businessName: targetStop.label, title: `${targetStop.label} — Quote Draft`, kind:'quote', tags:['quote'] }, buildQuoteDraftHtml(route, targetStop)); toast('Quote draft saved.', 'good'); openDocVaultModal(routeId, stopId, account); }; if($('#dv_followup')) $('#dv_followup').onclick = ()=>{ const targetStop = stop || (account && { label: account.business_name, businessEmail: account.business_email, sourceAccountId: account.id, address: buildAEFlowPreciseAddress(account), serviceArea: account.service_area, phone: account.phone, contact: account.contact_name, notes: account.notes, status: 'follow-up-needed', outcomeNote:'Follow up needed' }); if(!route || !targetStop){ toast('Open this from a route stop to generate a follow-up sheet.', 'warn'); return; } makeHtmlDocEntry({ routeId: route.id, stopId: cleanStr(targetStop.id), sourceAccountId: targetStop.sourceAccountId, businessEmail: targetStop.businessEmail, businessName: targetStop.label, title: `${targetStop.label} — Follow-up Sheet`, kind:'followup-sheet', tags:['follow-up'] }, buildFollowupSheetHtml(route, targetStop)); toast('Follow-up sheet saved.', 'good'); openDocVaultModal(routeId, stopId, account); }; if($('#dv_service')) $('#dv_service').onclick = ()=>{ if(!route || !stop) return; makeHtmlDocEntry({ routeId: route.id, stopId: stop.id, sourceAccountId: stop.sourceAccountId, businessEmail: stop.businessEmail, businessName: stop.label, title: `${stop.label} — Service Summary`, kind:'service-summary', tags:['service-slip','completion-proof'] }, buildServiceSummaryHtml(route, stop)); toast('Service summary saved.', 'good'); openDocVaultModal(routeId, stopId, account); }; $$('[data-doc-delete]').forEach(btn=> btn.onclick = ()=>{ if(!confirm('Delete this vault doc?')) return; deleteVaultDoc(btn.getAttribute('data-doc-delete')); toast('Vault doc deleted.', 'warn'); openDocVaultModal(routeId, stopId, account); }); $$('[data-doc-toggle]').forEach(btn=> btn.onclick = ()=>{ const id = btn.getAttribute('data-doc-toggle'); const doc = readVaultDocs().find(item => item.id === id); if(!doc) return; saveVaultDoc({ ...doc, selectedForProof: !doc.selectedForProof }); toast(doc.selectedForProof ? 'Removed from proof selection.' : 'Added to proof selection.', 'good'); openDocVaultModal(routeId, stopId, account); }); $$('[data-doc-download]').forEach(btn=> btn.onclick = ()=>{ const doc = readVaultDocs().find(item => item.id === btn.getAttribute('data-doc-download')); if(!doc) return; if(doc.html){ downloadText(doc.html, sanitizeFileName(`${doc.title}.html`), doc.mime || 'text/html'); return; } const first = doc.attachments[0]; if(first && first.dataUrl){ const a = document.createElement('a'); a.href = first.dataUrl; a.download = sanitizeFileName(first.name || `${doc.title}`); document.body.appendChild(a); a.click(); a.remove(); } }); } function openRouteEconomicsAuditModal(routeId){ const route = APP.cached.routes.find(item => item.id === routeId); if(!route) return; const metrics = computeRouteMetrics(route); const snapshot = route.liveSnapshot || route.closeoutSnapshot || buildRouteSnapshot(route); const fuelRows = normalizeFuelEntries(route.fuelEntries).map(entry => `
Fuel • ${escapeHTML(entry.station || 'Station')}
${fmt(entry.at)} • ${entry.gallons} gal • ${fmtMoney(entry.total)}${entry.note ? ` • ${escapeHTML(entry.note)}` : ''}
`).join(''); const expenseRows = normalizeExpenseEntries(route.expenseEntries).map(entry => `
Expense • ${escapeHTML(entry.category)}
${fmt(entry.at)} • ${fmtMoney(entry.total)}${entry.stopLabel ? ` • ${escapeHTML(entry.stopLabel)}` : ''}${entry.note ? ` • ${escapeHTML(entry.note)}` : ''}
`).join(''); const materialRows = normalizeMaterialEntries(route.materialEntries).map(entry => `
Material • ${escapeHTML(entry.itemName)}
${fmt(entry.at)} • Qty ${entry.qty} • ${fmtMoney(entry.totalCost)}
`).join(''); const collectionRows = normalizeCollectionEntries(route.collectionEntries).map(entry => `
Collection • ${escapeHTML(entry.method)}
${fmt(entry.at)} • ${fmtMoney(entry.amount)}${entry.balanceDue ? ` • due ${fmtMoney(entry.balanceDue)}` : ''}
`).join(''); openModal(`Economics Audit • ${escapeHTML(route.name)}`, `
Raw economic lines and stored snapshot for audit. Totals below come directly from saved fuel, expense, material, collection, and odometer fields.
${metrics.miles}
Miles
${fmtMoney(metrics.totalCost)}
Total cost
${fmtMoney(metrics.revenue)}
Revenue
${fmtMoney(metrics.net)}
Net
${fuelRows || ''}${expenseRows || ''}${materialRows || ''}${collectionRows || '' || `
No raw lines yet.
`}
`, ``); } function openDateComparisonModal(){ const today = dayISO(); const lastWeek = new Date(); lastWeek.setDate(lastWeek.getDate() - 7); openModal('Date Range Comparison', `
Compare two local date ranges side by side for routes, miles, delivery, spend, and net.
`, ``); $('#cmp_run').onclick = ()=>{ const a = buildDateRangeSummary($('#cmp_a_start').value, $('#cmp_a_end').value); const b = buildDateRangeSummary($('#cmp_b_start').value, $('#cmp_b_end').value); $('#cmp_results').innerHTML = `

Range A

${escapeHTML($('#cmp_a_start').value)} → ${escapeHTML($('#cmp_a_end').value)}
${['routes','miles','stops','delivered','revenue','spend','net','followups'].map(key => `
${escapeHTML(key)}
${escapeHTML(String(a[key] ?? 0))}
`).join('')}

Range B

${escapeHTML($('#cmp_b_start').value)} → ${escapeHTML($('#cmp_b_end').value)}
${['routes','miles','stops','delivered','revenue','spend','net','followups'].map(key => `
${escapeHTML(key)}
${escapeHTML(String(b[key] ?? 0))}
`).join('')}
`; }; } function openReminderCenterModal(){ const saved = readLocalReminders(); const system = buildSystemReminders(); const merged = [...saved, ...system.filter(item => !saved.find(savedItem => savedItem.id === item.id))].sort((a,b)=> cleanStr(a.dueAt).localeCompare(cleanStr(b.dueAt))); merged.forEach(item => { if(!saved.find(savedItem => savedItem.id === item.id)) saveLocalReminder(item); maybeNotifyReminder(item); }); const rows = readLocalReminders().filter(item => !item.isDismissed).sort((a,b)=> cleanStr(a.dueAt).localeCompare(cleanStr(b.dueAt))).map(item => `
${escapeHTML(item.title)} ${escapeHTML(item.type)}
${item.dueAt ? `${escapeHTML(fmt(item.dueAt))} • ` : ''}${escapeHTML(item.note || 'Local reminder')}
`).join(''); openModal('Reminder Center', `
Offline reminders live on this device. Tasks, appointment windows, and vehicle-service warnings are turned into local reminders without a backend.
${rows || `
No active reminders right now.
`}
`, ``); if($('#rm_notify')) $('#rm_notify').onclick = async ()=>{ if('Notification' in window){ try{ await Notification.requestPermission(); toast(`Notification permission: ${Notification.permission}`, Notification.permission === 'granted' ? 'good' : 'warn'); }catch(err){ toast('Could not request notification permission.', 'warn'); } } }; if($('#rm_save')) $('#rm_save').onclick = ()=>{ const title = cleanStr($('#rm_title').value); if(!title){ toast('Add a reminder title.', 'warn'); return; } saveLocalReminder({ title, note: $('#rm_note').value, dueAt: $('#rm_due').value, type:'manual', level:'warn' }); toast('Local reminder saved.', 'good'); openReminderCenterModal(); }; $$('[data-remind-dismiss]').forEach(btn=> btn.onclick = ()=>{ dismissLocalReminder(btn.getAttribute('data-remind-dismiss')); toast('Reminder dismissed.', 'good'); openReminderCenterModal(); }); } function openReadinessDashboardModal(){ const accounts = getAEFlowAccounts(); const rows = accounts.map(account => ({ account, readiness: getAEFlowReadiness(account) })); const ready = rows.filter(row => row.readiness.label === 'Route Ready'); const patch = rows.filter(row => row.readiness.label === 'Needs Patch'); const notReady = rows.filter(row => row.readiness.label === 'Not Ready'); const missingPhone = rows.filter(row => !cleanStr(row.account.phone)); const missingContact = rows.filter(row => !cleanStr(row.account.contact_name)); const missingExactAddress = rows.filter(row => !hasAEFlowPreciseAddress(row.account)); const list = rows.filter(row => row.readiness.label !== 'Route Ready').slice(0, 30).map(row => `
${escapeHTML(row.account.business_name)} ${escapeHTML(row.readiness.label)}
${escapeHTML(row.readiness.issues.join(' • ') || 'Needs data patch')}
`).join(''); openModal('Readiness Dashboard', `
See how many accounts are truly route-ready, which fields are missing most often, and bulk-patch the common gaps from one place.
${ready.length}
Route ready
${patch.length}
Needs patch
${notReady.length}
Not ready
${missingPhone.length}
Missing phone
${missingContact.length}
Missing contact
${missingExactAddress.length}
Missing exact address
${list || `
Everything looks route-ready right now.
`}
`, ``); $('#rd_apply_bulk').onclick = ()=>{ const territory = cleanStr($('#rd_bulk_territory').value); const city = cleanStr($('#rd_bulk_city').value); const state = cleanStr($('#rd_bulk_state').value); const contact = cleanStr($('#rd_bulk_contact').value); const phone = cleanStr($('#rd_bulk_phone').value); if(!territory && !city && !state && !contact && !phone){ toast('Add at least one bulk field.', 'warn'); return; } let count = 0; rows.forEach(row => { if($('#rd_bulk_scope').value === 'not-ready' && row.readiness.label === 'Route Ready') return; const patch = {}; if(territory && !getAccountTerritories(row.account).length) patch.territory_zones = [territory]; if(city && !cleanStr(row.account.city)) patch.city = city; if(state && !cleanStr(row.account.state)) patch.state = state; if(contact && !cleanStr(row.account.contact_name)) patch.contact_name = contact; if(phone && !cleanStr(row.account.phone)) patch.phone = phone; if(Object.keys(patch).length){ updateAEFlowAccountByKey(getAEFlowAccountKey(row.account), patch); count += 1; } }); toast(`Bulk patch applied to ${count} account${count===1?'':'s'}.`, count ? 'good' : 'warn'); openReadinessDashboardModal(); }; } function openAccountDossierModal(account){ const a = sanitizeAEFlowAccount(account); const history = getAccountVisitHistory(a); const docs = getVaultDocsForAccount(a); const tasks = getAccountRouteTasks(a); const routes = Array.from(new Map(history.map(stop => [stop.routeId, APP.cached.routes.find(route => route.id === stop.routeId)]).filter(entry => entry[1]).map(entry => [entry[0], entry[1]])).values()); openModal(`Client Dossier • ${escapeHTML(a.business_name)}`, `
Unified client history across AE FLOW, Routex visits, follow-up tasks, and the local document vault.
${history.length}
Route visits
${tasks.filter(task => task.status !== 'done').length}
Open tasks
${docs.length}
Vault docs
${routes.length}
Routes touched
${history.slice(0,12).map(stop => `
${escapeHTML(stop.label)} ${stopBadge(stop.status)}
${escapeHTML(fmt(getStopActualAt(stop) || stop.createdAt))} • ${escapeHTML((APP.cached.routes.find(route => route.id === stop.routeId) || {}).name || 'Route')} ${stop.outcomeNote ? `• ${escapeHTML(stop.outcomeNote)}` : ''}
`).join('') || `
No route visits yet.
`}
`, ``); if($('#dos_docs')) $('#dos_docs').onclick = ()=> openDocVaultModal('', '', a); if($('#dos_task')) $('#dos_task').onclick = ()=> openRouteTaskModal(a); } function openSavedViewsModal(scope, currentFilters, onApply){ const views = readSavedViews().filter(view => cleanStr(view.scope) === cleanStr(scope)); openModal('Saved Views', `
Save and reuse local filters for AE FLOW routing views.
${views.map(view => `
${escapeHTML(view.name)}
${escapeHTML(JSON.stringify(view.filters))}
`).join('') || `
No saved views yet.
`}
`, ``); if($('#sv_save')) $('#sv_save').onclick = ()=>{ const name = cleanStr($('#sv_name').value); if(!name){ toast('Name the view.', 'warn'); return; } saveSavedView({ name, scope, filters: currentFilters }); toast('Saved view created.', 'good'); openSavedViewsModal(scope, currentFilters, onApply); }; $$('[data-sv-apply]').forEach(btn => btn.onclick = ()=>{ const view = views.find(item => item.id === btn.getAttribute('data-sv-apply')); if(!view) return; if(typeof onApply === 'function') onApply(view.filters); closeModal(); }); $$('[data-sv-delete]').forEach(btn => btn.onclick = ()=>{ deleteSavedView(btn.getAttribute('data-sv-delete')); toast('Saved view deleted.', 'warn'); openSavedViewsModal(scope, currentFilters, onApply); }); } function getRouteConflictWindowWarnings(accounts, date, driver){ const targetDate = cleanStr(date) || dayISO(); const warnings = []; (accounts || []).forEach(account => { const key = buildClientKeyFromAccount(account); APP.cached.stops.forEach(stop => { const route = APP.cached.routes.find(item => item.id === stop.routeId); if(!route || cleanStr(route.date) !== targetDate || stop.routeId === APP.routeId) return; if(buildClientKeyFromStop(stop) !== key) return; if(!cleanStr(stop.appointmentWindowStart) && !cleanStr(stop.appointmentWindowEnd)) return; if(cleanStr(route.driver) && cleanStr(driver) && cleanStr(route.driver) !== cleanStr(driver)) warnings.push(`${account.business_name} already has stop-window coverage on ${route.name} for ${route.driver}`); }); }); return Array.from(new Set(warnings)).slice(0, 12); } function statusBadge(status){ if(status==="completed") return `Completed`; if(status==="in-progress") return `In progress`; if(status==="planned") return `Planned`; return `${status||"—"}`; } function stopBadge(status){ if(status==="delivered") return `Delivered`; if(status==="arrived") return `Arrived`; if(status==="rescheduled" || status==="follow-up-needed") return `${escapeHTML(STOP_STATUS_LABELS[status] || status)}`; if(status && status !== "pending") return `${escapeHTML(STOP_STATUS_LABELS[status] || status)}`; return `Pending`; } async function createRoute(data){ const selectedProfile = getVehicleProfileById(cleanStr(data.vehicleProfileId)); const r = { id: uid(), name: (data.name||"Route").trim() || "Route", date: data.date || dayISO(), driver: (data.driver || (selectedProfile && selectedProfile.defaultDriver) || "").trim(), vehicle: (data.vehicle || vehicleLabel(selectedProfile) || "").trim(), vehicleProfileId: cleanStr(data.vehicleProfileId), territory: cleanStr(data.territory), routeNotes: cleanStr(data.routeNotes), startOdo: data.startOdo ? String(data.startOdo).trim() : "", endOdo: cleanStr(data.endOdo), revenue: data.revenue ? String(data.revenue).trim() : "", fuelGallons: data.fuelGallons ? String(data.fuelGallons).trim() : "", fuelTotal: data.fuelTotal ? String(data.fuelTotal).trim() : "", fuelPricePerGallon: data.fuelPricePerGallon ? String(data.fuelPricePerGallon).trim() : "", fuelStation: (data.fuelStation||"").trim(), fuelNote: (data.fuelNote||"").trim(), fuelEntries: normalizeFuelEntries(data.fuelEntries), expenseEntries: normalizeExpenseEntries(data.expenseEntries), materialEntries: normalizeMaterialEntries(data.materialEntries), collectionEntries: normalizeCollectionEntries(data.collectionEntries), miscExpense: data.miscExpense ? String(data.miscExpense).trim() : "", closeoutNotes: cleanStr(data.closeoutNotes), closeoutAt: cleanStr(data.closeoutAt), status: cleanStr(data.status) || "planned", startedAt: cleanStr(data.startedAt), routePhase: cleanStr(data.routePhase), pauseEntries: normalizeRoutePauseEntries(data.pauseEntries), breakEntries: normalizeRouteBreakEntries(data.breakEntries), eventLog: [{ id: uid(), type: "created", summary: "Route created", at: nowISO(), meta: {} }], createdAt: nowISO(), updatedAt: nowISO() }; syncRouteDerivedCollections(r); await put(APP.db, "routes", r); toast("Route created.", "good"); await refreshCache(); await persistRouteSnapshot(r.id); return r; } async function updateRoute(id, patch){ const r = await get(APP.db, "routes", id); if(!r) return; Object.assign(r, patch, { updatedAt: nowISO() }); syncRouteDerivedCollections(r); await put(APP.db, "routes", r); await refreshCache(); await persistRouteSnapshot(id); } async function appendRouteEvent(routeId, type, summary, meta){ const route = await get(APP.db, "routes", routeId); if(!route) return null; const entry = { id: uid(), type: cleanStr(type) || "updated", summary: cleanStr(summary) || "Route updated", at: nowISO(), meta: meta && typeof meta === "object" ? meta : {} }; route.eventLog = [entry, ...(Array.isArray(route.eventLog) ? route.eventLog : [])].slice(0, 250); route.updatedAt = nowISO(); syncRouteDerivedCollections(route); await put(APP.db, "routes", route); await refreshCache(); await persistRouteSnapshot(routeId); return entry; } async function deleteRoute(id){ // delete stops and photos const stops = APP.cached.stops.filter(s=>s.routeId===id); for(const s of stops){ const ps = APP.cached.photos.filter(p=>p.stopId===s.id); for(const p of ps) await del(APP.db, "photos", p.id); await del(APP.db, "stops", s.id); } await del(APP.db, "routes", id); toast("Route deleted.", "warn"); await refreshCache(); } async function createStop(routeId, data){ const routeStops = stopsForRoute(routeId); const seq = routeStops.length ? Math.max(...routeStops.map(s=>s.seq||0)) + 1 : 1; const s = { id: uid(), routeId, seq, label: (data.label||"Stop").trim() || "Stop", address: (data.address||"").trim(), contact: (data.contact||"").trim(), phone: (data.phone||"").trim(), notes: (data.notes||"").trim(), businessEmail: (data.businessEmail||"").trim(), website: (data.website||"").trim(), source: (data.source||"").trim(), sourceAccountId: (data.sourceAccountId||"").trim(), clientKey: buildClientKeyFromStop(data), clientMode: cleanStr(data.clientMode) || (((data.sourceAccountId||"").trim() || (data.businessEmail||"").trim()) ? 'canonical-account' : 'independent-stop'), serviceArea: (data.serviceArea||"").trim(), routeHint: (data.routeHint||"").trim(), accountStatus: (data.accountStatus||"").trim(), aeName: (data.aeName||"").trim(), exactAddress: (data.exactAddress||"").trim(), addressMode: (data.addressMode||((data.address||"").trim() ? "manual" : "")).trim(), locationNotes: (data.locationNotes||"").trim(), accessNotes: cleanStr(data.accessNotes), appointmentWindowStart: cleanStr(data.appointmentWindowStart), appointmentWindowEnd: cleanStr(data.appointmentWindowEnd), territoryZones: territoryListFromValue(data.territoryZones), territoryPrimary: cleanStr(data.territoryPrimary), tags: normalizeTags(data.tags), status: "pending", outcomeNote: (data.outcomeNote||"").trim(), attemptCount: num(data.attemptCount) ? Math.max(0, Math.round(num(data.attemptCount))) : 0, successfulVisitCount: num(data.successfulVisitCount) ? Math.max(0, Math.round(num(data.successfulVisitCount))) : 0, arrivedOdo: cleanStr(data.arrivedOdo), departureOdo: cleanStr(data.departureOdo), serviceStartAt: cleanStr(data.serviceStartAt), serviceEndAt: cleanStr(data.serviceEndAt), createdAt: nowISO(), arrivedAt: "", deliveredAt: "", completedAt: "", updatedAt: nowISO() }; await put(APP.db, "stops", s); toast("Stop added.", "good"); await refreshCache(); await persistRouteSnapshot(routeId); await appendRouteEvent(routeId, "stop-added", `Stop added: ${s.label}`, { stopId: s.id, stopLabel: s.label, clientKey: s.clientKey, clientMode: s.clientMode }); return s; } async function updateStop(id, patch){ const s = await get(APP.db, "stops", id); if(!s) return; const nextPatch = { ...(patch && typeof patch === 'object' ? patch : {}) }; if(!cleanStr(nextPatch.clientKey)) nextPatch.clientKey = buildClientKeyFromStop({ ...s, ...nextPatch }); if(!cleanStr(nextPatch.clientMode)) nextPatch.clientMode = cleanStr(s.clientMode) || (((cleanStr(nextPatch.sourceAccountId) || cleanStr(s.sourceAccountId) || cleanStr(nextPatch.businessEmail) || cleanStr(s.businessEmail))) ? 'canonical-account' : 'independent-stop'); Object.assign(s, nextPatch, { updatedAt: nowISO() }); await put(APP.db, "stops", s); await refreshCache(); await persistRouteSnapshot(s.routeId); } async function deleteStop(id){ const stop = APP.cached.stops.find(s=>s.id===id); const ps = APP.cached.photos.filter(p=>p.stopId===id); for(const p of ps) await del(APP.db, "photos", p.id); await del(APP.db, "stops", id); toast("Stop deleted.", "warn"); await refreshCache(); if(stop){ await persistRouteSnapshot(stop.routeId); await appendRouteEvent(stop.routeId, "stop-deleted", `Stop deleted: ${stop.label}`, { stopId: stop.id, stopLabel: stop.label }); } } async function moveStop(routeId, stopId, dir){ const list = stopsForRoute(routeId); const idx = list.findIndex(s=>s.id===stopId); if(idx<0) return; const j = idx + dir; if(j<0 || j>=list.length) return; // swap seq const a = list[idx], b = list[j]; const tmp = a.seq; a.seq = b.seq; b.seq = tmp; await put(APP.db, "stops", { ...a, updatedAt: nowISO() }); await put(APP.db, "stops", { ...b, updatedAt: nowISO() }); await refreshCache(); await persistRouteSnapshot(routeId); await appendRouteEvent(routeId, "stop-reordered", `Stop order updated`, { movedStopId: stopId }); } async function addPhotos(routeId, stopId, files){ const maxEachMB = 10; for(const rawFile of files){ const f = await compressImageFile(rawFile); if(f.size > maxEachMB*1024*1024){ toast(`Skipped ${f.name}: >${maxEachMB}MB`, "warn", 3200); continue; } const p = { id: uid(), routeId, stopId, name: f.name || `photo_${Date.now()}.jpg`, type: f.type || "image/jpeg", size: f.size || 0, blob: f, // Blob createdAt: nowISO() }; await put(APP.db, "photos", p); } toast("Photos saved offline.", "good"); await refreshCache(); await persistRouteSnapshot(routeId); } async function deletePhoto(id){ const photo = APP.cached.photos.find(x=>x.id===id); await del(APP.db, "photos", id); toast("Photo removed.", "warn"); await refreshCache(); if(photo) await persistRouteSnapshot(photo.routeId); } // ========= Views ========= async function viewDashboard(){ setPage("Dashboard", "Today’s ops snapshot"); setPrimary("New Route", ()=> openNewRouteModal(), ``); const k = routeKPIs(); const routes = APP.cached.routes.slice(0, 6); const aeAccounts = getAEFlowAccounts(); const aeQueued = getAEFlowQueuedAccounts(); const command = buildCommandCenterStats(); const recent = routes.map(r=>{ const sc = stopsForRoute(r.id); const delivered = sc.filter(s=>s.status==="delivered").length; const total = sc.length; const metrics = computeRouteMetrics(r); const scorecard = getRouteScorecard(r); return `
${escapeHTML(r.name)} ${statusBadge(r.status)} ${escapeHTML(r.date||"")} ${escapeHTML(scorecard.grade)} • ${scorecard.score} ${(metrics.slaOverdue || metrics.slaDueSoon) ? `SLA ${metrics.slaOverdue}/${metrics.slaDueSoon}` : ``}
Driver: ${escapeHTML(r.driver||"—")} • Vehicle: ${escapeHTML(r.vehicle||"—")}
Stops: ${delivered}/${total} delivered • Miles: ${metrics.miles || 0} • Net: ${fmtMoney(metrics.net)} • Score: ${scorecard.score}/100 • Updated: ${fmt(r.updatedAt)}
Route time: ${metrics.totalRouteMinutes} min • Drive: ${metrics.driveMinutes} min • Break: ${metrics.breakMinutes} min • Phase: ${escapeHTML(metrics.routePhase || 'Planned')}
`; }).join(""); const unresolvedHtml = command.unresolvedStops.slice(0,6).map(stop=> `
#${stop.seq} ${escapeHTML(stop.label)} ${stopBadge(stop.status)}
${escapeHTML((APP.cached.routes.find(route => route.id === stop.routeId) || {}).name || 'Route')} • ${escapeHTML(stop.address || stop.serviceArea || 'No address')}${stop.outcomeNote ? ` • ${escapeHTML(stop.outcomeNote)}` : ''}
`).join(''); const balancesHtml = command.outstandingBalances.slice(0,6).map(row=> `
${escapeHTML(row.businessName)} ${fmtMoney(row.balanceDue)} due
${escapeHTML(row.routeName)}${row.invoiceRef ? ` • ${escapeHTML(row.invoiceRef)}` : ''}${row.note ? ` • ${escapeHTML(row.note)}` : ''}
`).join(''); const routeRiskHtml = command.routeRisks.slice(0,6).map(row=> `
${escapeHTML(row.route.name)} ${row.metrics.slaOverdue} overdue • ${row.metrics.slaDueSoon} due soon
${escapeHTML(row.route.date)} • Driver: ${escapeHTML(row.route.driver || '—')}
`).join(''); const vehicleAlertHtml = command.vehicleAlerts.slice(0,6).map(row=> `
${escapeHTML(row.profile.name)} ${escapeHTML(row.maintenance.label)}
Current odo: ${row.maintenance.currentOdo || '—'} • Since service: ${row.maintenance.milesSinceService || 0} mi
`).join(''); const cadenceHtml = aeAccounts.slice(0,10).map(account => { const insight = buildRevisitInsights(account); return `
${escapeHTML(account.business_name)} ${insight.isCold ? `Cold` : ``}${insight.needsRevisit ? `Revisit` : ``}${insight.lowYield ? `Weak yield` : ``}
${escapeHTML(insight.label)}${insight.lastVisitedText ? ` • last ${escapeHTML(insight.lastVisitedText)}` : ''}${insight.nextSuggestedDate ? ` • next ${escapeHTML(insight.nextSuggestedDate)}` : ''}
`; }).join(''); $("#content").innerHTML = `

Key metrics

${k.todayRoutes}
Routes scheduled today
${k.active}
In progress
${k.planned}
Planned
${k.done}
Completed
${k.delivered}
Stops delivered
${k.milesToday}
Miles logged today
Tip: Use Routes to build stop lists. Each stop can now carry proof docs, signatures, reminders, and route-economics audit detail fully offline.
${aeAccounts.length} AE FLOW account${aeAccounts.length===1 ? "" : "s"} ready
${aeQueued.length ? `
${aeQueued.length} queued for Routex
` : ``}

Daily command center

${fmtMoney(command.totals.spend)}
Spend today
${fmtMoney(command.totals.revenue)}
Revenue logged
${fmtMoney(command.totals.net)}
Net today
${fmtMoney(command.totals.collections)}
Collections logged
${fmtMoney(command.totals.balanceDue)}
Balance still due
${fmtMoney(command.totals.materials)}
Material cost
${command.todayTaskCreates}
Follow-ups created today
${command.dueTodayTasks}
Open tasks due today
${command.unresolvedStops.length}
Unresolved stops today
${command.revisitStops.length}
Revisit / failed stops
${command.totals.slaOverdue}
Overdue stop windows
${command.totals.slaDueSoon}
Due-soon windows
Strongest territory: ${command.strongestTerritory ? `${command.strongestTerritory.territory} (${fmtMoney(command.strongestTerritory.net)})` : '—'} • Weakest territory: ${command.weakestTerritory ? `${command.weakestTerritory.territory} (${fmtMoney(command.weakestTerritory.net)})` : '—'}

Unresolved stops

${unresolvedHtml || `
No unresolved stops for today.
`}

Outstanding balances

${balancesHtml || `
No outstanding balances logged today.
`}

Route SLA risk

${routeRiskHtml || `
No current stop-window risk detected.
`}

Vehicle maintenance

${vehicleAlertHtml || `
No mileage-based maintenance alert yet.
`}

Cadence intelligence

${cadenceHtml || `
No account cadence history yet.
`}

Recent routes

${recent || `
No routes yet. Create one to start selling “proof-first delivery ops.”
`}
`; $$('[data-open-route]').forEach(btn=>{ btn.onclick = ()=>{ APP.routeId = btn.getAttribute('data-open-route'); APP.view = 'routes'; window.location.hash = 'routes'; render(); }; }); if($('#dashOpenAEFlow')) $('#dashOpenAEFlow').onclick = ()=>{ APP.view = 'ae-flow'; window.location.hash = 'ae-flow'; render(); }; if($('#dashDayLedger')) $('#dashDayLedger').onclick = ()=> openDayLedgerModal(dayISO()); if($('#dashDriverAnalytics')) $('#dashDriverAnalytics').onclick = ()=> openDriverAnalyticsModal(); if($('#dashVehicleAnalytics')) $('#dashVehicleAnalytics').onclick = ()=> openVehicleAnalyticsModal(); if($('#dashTerritoryAnalytics')) $('#dashTerritoryAnalytics').onclick = ()=> openTerritoryAnalyticsModal(); if($('#dashDateCompare')) $('#dashDateCompare').onclick = ()=> openDateComparisonModal(); if($('#dashReminders')) $('#dashReminders').onclick = ()=> openReminderCenterModal(); } function openNewRouteModal(){ openModal( "New Route", `
Create a route, then add stops. Everything stores offline on this device.
`, ` ` ); $("#nr_date").value = dayISO(); if($("#nr_vehicle_profile")){ $("#nr_vehicle_profile").addEventListener("change", ()=>{ const profile = getVehicleProfileById($("#nr_vehicle_profile").value); if(!profile) return; if(!cleanStr($("#nr_vehicle").value)) $("#nr_vehicle").value = vehicleLabel(profile); if(!cleanStr($("#nr_driver").value) && cleanStr(profile.defaultDriver)) $("#nr_driver").value = profile.defaultDriver; }); } $("#nr_create").onclick = async ()=>{ const data = { name: $("#nr_name").value, date: $("#nr_date").value || dayISO(), driver: $("#nr_driver").value, vehicle: $("#nr_vehicle").value, vehicleProfileId: $("#nr_vehicle_profile") ? $("#nr_vehicle_profile").value : "", territory: $("#nr_territory").value, startOdo: $("#nr_odo").value, revenue: $("#nr_revenue").value, fuelGallons: $("#nr_fuelGallons").value, fuelTotal: $("#nr_fuelTotal").value, fuelPricePerGallon: $("#nr_fuelPpg").value, fuelStation: $("#nr_fuelStation").value, fuelNote: $("#nr_fuelNote").value, miscExpense: $("#nr_miscExpense").value, routeNotes: $("#nr_routeNotes").value }; const r = await createRoute(data); closeModal(); APP.routeId = r.id; APP.view = "routes"; window.location.hash = "routes"; render(); }; } async function viewRoutes(){ setPage("Routes", "Create & manage routes"); setPrimary("New Route", ()=> openNewRouteModal(), ``); const routes = APP.cached.routes; const aeAccounts = getAEFlowAccounts(); const aeQueued = getAEFlowQueuedAccounts(); if(APP.routeId){ const r = routes.find(x=>x.id===APP.routeId); if(!r){ APP.routeId = null; }else{ await viewRouteDetail(r); return; } } const rows = routes.map(r=>{ const sc = stopsForRoute(r.id); const delivered = sc.filter(s=>s.status==="delivered").length; const total = sc.length; const metrics = computeRouteMetrics(r); return `
${escapeHTML(r.name)} ${statusBadge(r.status)} ${escapeHTML(r.date||"")}
Driver: ${escapeHTML(r.driver||"—")} • Vehicle: ${escapeHTML(r.vehicle||"—")}
Stops: ${delivered}/${total} delivered • Miles: ${metrics.miles || 0} • Miles/stop: ${metrics.milesPerStop || 0} • Net: ${fmtMoney(metrics.net)} • Created: ${fmt(r.createdAt)}
Route time: ${metrics.totalRouteMinutes} min • Drive: ${metrics.driveMinutes} min • Break: ${metrics.breakMinutes} min • Phase: ${escapeHTML(metrics.routePhase || 'Planned')}
`; }).join(""); $("#content").innerHTML = `

Routes

Open a route to manage stops, timestamps, and proof photos. Routes are stored offline on this device.
${aeAccounts.length} stored AE FLOW account${aeAccounts.length===1 ? "" : "s"}
${aeQueued.length ? `
${aeQueued.length} queued for Routex
` : ``}
${rows || `
No routes yet. Create your first route.
`}
`; $$("[data-open-route]").forEach(btn=>{ btn.onclick = ()=>{ APP.routeId = btn.getAttribute("data-open-route"); render(); }; }); $$("[data-edit-route]").forEach(btn=>{ btn.onclick = ()=> openEditRouteModal(btn.getAttribute("data-edit-route")); }); if($("#routesOpenAEFlow")){ $("#routesOpenAEFlow").onclick = ()=>{ APP.view = "ae-flow"; window.location.hash = "ae-flow"; render(); }; } } async function openEditRouteModal(routeId){ const r = APP.cached.routes.find(x=>x.id===routeId); if(!r) return; openModal( "Edit Route", `
Update route details, or delete it (deletes stops + photos).
`, `
` ); $("#er_status").value = r.status || "planned"; if($("#er_vehicle_profile")){ $("#er_vehicle_profile").value = r.vehicleProfileId || ""; $("#er_vehicle_profile").addEventListener("change", ()=>{ const profile = getVehicleProfileById($("#er_vehicle_profile").value); if(!profile) return; if(!cleanStr($("#er_vehicle").value)) $("#er_vehicle").value = vehicleLabel(profile); if(!cleanStr($("#er_driver").value) && cleanStr(profile.defaultDriver)) $("#er_driver").value = profile.defaultDriver; }); } $("#er_save").onclick = async ()=>{ const startOdo = $("#er_startOdo").value; const endOdo = $("#er_endOdo").value; if(startOdo && endOdo && num(endOdo) < num(startOdo)){ toast("End odometer cannot be lower than start odometer.", "bad"); return; } await updateRoute(routeId, { name: $("#er_name").value, date: $("#er_date").value, status: $("#er_status").value, driver: $("#er_driver").value, vehicle: $("#er_vehicle").value, vehicleProfileId: $("#er_vehicle_profile") ? $("#er_vehicle_profile").value : "", territory: $("#er_territory").value, startOdo, endOdo, revenue: $("#er_revenue").value, fuelGallons: $("#er_fuelGallons").value, fuelTotal: $("#er_fuelTotal").value, fuelPricePerGallon: $("#er_fuelPpg").value, fuelStation: $("#er_fuelStation").value, fuelNote: $("#er_fuelNote").value, miscExpense: $("#er_miscExpense").value, routeNotes: $("#er_routeNotes").value }); await appendRouteEvent(routeId, "route-edited", "Route details updated", { territory: $("#er_territory").value, previousStartOdo: cleanStr(r.startOdo), previousEndOdo: cleanStr(r.endOdo), newStartOdo: cleanStr(startOdo), newEndOdo: cleanStr(endOdo) }); toast("Route updated.", "good"); closeModal(); render(); }; $("#er_delete").onclick = async ()=>{ const ok = confirm("Delete route? This deletes ALL stops and photos in it."); if(!ok) return; await deleteRoute(routeId); closeModal(); if(APP.routeId === routeId) APP.routeId = null; render(); }; } async function viewRouteDetail(r){ setPage("Route", `${r.name} • ${r.date || ""}`); setPrimary("Add Stop", ()=> openNewStopModal(r.id), ``); const stops = stopsForRoute(r.id); const delivered = stops.filter(s=>s.status==="delivered").length; const total = stops.length; const metrics = computeRouteMetrics(r); const rows = stops.map(s=>{ const ph = photosForStop(s.id); const maps = s.address ? `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(s.address)}` : ""; return `
#${s.seq} ${escapeHTML(s.label)} ${stopBadge(s.status)} ${ph.length} photo${ph.length===1?"":"s"} ${s.source ? `${escapeHTML(s.source)}` : ""}
${s.address ? `Address: ${escapeHTML(s.address)}${s.addressMode==="fallback" ? ` Area fallback` : s.addressMode ? ` ${escapeHTML(s.addressMode)}` : ""}
` : ""} ${s.locationNotes ? `Location notes: ${escapeHTML(s.locationNotes)}
` : ""} ${s.accessNotes ? `Site access: ${escapeHTML(s.accessNotes)}
` : ``} ${(s.appointmentWindowStart || s.appointmentWindowEnd) ? `Appointment window: ${escapeHTML(s.appointmentWindowStart || '—')}${escapeHTML(s.appointmentWindowEnd || '—')}${getStopWindowTiming(s).label ? ` ${escapeHTML(getStopWindowTiming(s).label)}` : ''}
` : ``} ${s.contact ? `Contact: ${escapeHTML(s.contact)}${s.phone?` • ${escapeHTML(s.phone)}`:""}
` : ""} ${s.businessEmail ? `Email: ${escapeHTML(s.businessEmail)}
` : ""} ${s.website ? `Website: ${escapeHTML(s.website)}
` : ""} ${(s.serviceArea || s.routeHint) ? `Service area: ${escapeHTML(s.serviceArea || "—")}${s.routeHint ? ` • Route hint: ${escapeHTML(s.routeHint)}` : ""}
` : ""} ${(Array.isArray(s.territoryZones) && s.territoryZones.length) ? `Territories: ${escapeHTML(s.territoryZones.join(", "))}
` : ``} ${(s.accountStatus || s.aeName) ? `Account: ${escapeHTML(s.accountStatus || "—")}${s.aeName ? ` • AE: ${escapeHTML(s.aeName)}` : ""}${s.sourceAccountId ? ` • Attempts: ${getAccountAttemptStats({ id: s.sourceAccountId, business_email: s.businessEmail }).attempts} • Successful visits: ${getAccountAttemptStats({ id: s.sourceAccountId, business_email: s.businessEmail }).successful}` : ''}
` : ""} ${(Array.isArray(s.tags) && s.tags.length) ? `Tags: ${escapeHTML(s.tags.join(", "))}
` : ""} Created: ${fmt(s.createdAt)} • Arrived: ${fmt(s.arrivedAt)} • Delivered: ${fmt(s.deliveredAt)} • Completed: ${fmt(s.completedAt)}
Odometer: ${escapeHTML(s.arrivedOdo || '—')}${escapeHTML(s.departureOdo || '—')} • Leg miles: ${getStopLegMiles(r.id, s.id)} • On-site miles: ${getStopOnSiteMiles(s)}
Dwell: ${getStopDwellMinutes(s)} min • Service: ${getStopServiceMinutes(s)} min • Stop attempts: ${s.attemptCount || 0}
Notes: ${escapeHTML(s.notes || "—")}${s.outcomeNote ? `
Outcome note: ${escapeHTML(s.outcomeNote)}` : ``}
${maps ? `Maps` : ""}
`; }).join(""); const fuelEntries = normalizeFuelEntries(r.fuelEntries); const expenseEntries = normalizeExpenseEntries(r.expenseEntries); const materialEntries = normalizeMaterialEntries(r.materialEntries); const collectionEntries = normalizeCollectionEntries(r.collectionEntries); const eventLog = Array.isArray(r.eventLog) ? r.eventLog : []; const vehicleProfile = getVehicleProfileById(r.vehicleProfileId || ""); const unresolvedCount = stops.filter(stop => !stopIsCompleted(stop.status)).length; const followupCount = stops.filter(stop => ["follow-up-needed","rescheduled","no-answer","wrong-address","gate-locked","site-closed"].includes(stop.status)).length; const headerCard = `

${escapeHTML(r.name)}

Date: ${escapeHTML(r.date||"")} • Status: ${statusBadge(r.status)} ${r.territory ? `${escapeHTML(r.territory)}` : ``}
Driver: ${escapeHTML(r.driver||"—")} • Vehicle: ${escapeHTML(r.vehicle||"—")}${vehicleProfile ? ` • Profile: ${escapeHTML(vehicleProfile.name)}` : ``}
Stops delivered: ${delivered}/${total} • Completed: ${metrics.completed}/${total} • Unresolved: ${unresolvedCount}
Miles: ${metrics.miles} • Revenue: ${fmtMoney(metrics.revenue)} • Cost: ${fmtMoney(metrics.totalCost)} • Net: ${fmtMoney(metrics.net)}
Miles/stop: ${metrics.milesPerStop} • Miles/completed stop: ${metrics.milesPerCompletedStop} • Miles/delivered stop: ${metrics.milesPerDeliveredStop}
Phase: ${escapeHTML(metrics.routePhase || 'Planned')} • Route time: ${metrics.totalRouteMinutes} min • Active: ${metrics.activeRouteMinutes} min • Drive: ${metrics.driveMinutes} min • Dwell: ${metrics.dwellMinutes} min • Break: ${metrics.breakMinutes} min • Pause: ${metrics.pauseMinutes} min ${metrics.collectionTotal ? `
Collections logged: ${fmtMoney(metrics.collectionTotal)}${metrics.balanceDueTotal ? ` • Balance due: ${fmtMoney(metrics.balanceDueTotal)}` : ``}` : ``} ${metrics.estimatedFuelUsed ? `
Est. fuel from MPG: ${metrics.estimatedFuelUsed} gal${metrics.fuelGallons ? ` • Logged delta: ${metrics.fuelDelta > 0 ? '+' : ''}${metrics.fuelDelta}` : ``}` : ``} ${r.routeNotes ? `
Route notes: ${escapeHTML(r.routeNotes)}` : ``} ${vehicleProfile && getVehicleMaintenanceStatus(vehicleProfile).label ? `
Maintenance: ${escapeHTML(getVehicleMaintenanceStatus(vehicleProfile).label)}` : ``} ${(metrics.slaOverdue || metrics.slaDueSoon) ? `
SLA risk: ${metrics.slaOverdue} overdue • ${metrics.slaDueSoon} due soon` : ``} ${metrics.odometerWarning ? `
${escapeHTML(metrics.odometerWarning)}` : ``}
Offline note: proof photos, outcome notes, route economics, and timestamps work without internet. The “Maps” button opens navigation when internet is available. ${r.fuelStation || r.fuelNote ? `
Fuel lane: ${escapeHTML(r.fuelStation || "—")}${r.fuelNote ? ` • ${escapeHTML(r.fuelNote)}` : ``}` : ``} ${r.closeoutAt ? `
Closeout: ${fmt(r.closeoutAt)}${r.closeoutNotes ? ` • ${escapeHTML(r.closeoutNotes)}` : ``}` : ``}
`; const scorecard = getRouteScorecard(r); const economicsCard = `

Route economics

${scorecard.score}
Route score
${escapeHTML(scorecard.grade)}
Scorecard grade
${fuelEntries.length}
Fuel entries
${expenseEntries.length}
Expense entries
${materialEntries.length}
Material entries
${collectionEntries.length}
Collection entries
${fmtMoney(metrics.costPerStop)}
Cost / stop
${fmtMoney(metrics.costPerProductiveStop)}
Cost / productive stop
${followupCount}
Follow-up / failed stops
Scorecard: ${escapeHTML(scorecard.grade)} (${scorecard.score}/100) • Delivered rate: ${scorecard.deliveryRate}% • Follow-up pressure: ${scorecard.followupCount}
Fuel total: ${fmtMoney(metrics.fuelTotal)} • Misc expense: ${fmtMoney(metrics.miscExpense)} • Materials: ${fmtMoney(metrics.materialTotal)} • Productive stops: ${metrics.productive}
Collections: ${fmtMoney(metrics.collectionTotal)} • Balance due: ${fmtMoney(metrics.balanceDueTotal)}${metrics.estimatedFuelUsed ? ` • Est. fuel use: ${metrics.estimatedFuelUsed} gal${metrics.fuelGallons ? ` • Delta: ${metrics.fuelDelta > 0 ? '+' : ''}${metrics.fuelDelta}` : ``}` : ``} ${vehicleProfile && vehicleProfile.mpg ? `
Vehicle MPG reference: ${escapeHTML(vehicleProfile.mpg)} • Tank size: ${escapeHTML(vehicleProfile.tankSize || '—')}` : ``}
${(fuelEntries.length || expenseEntries.length || materialEntries.length || collectionEntries.length) ? `
${fuelEntries.slice(0,2).map(entry => `
Fuel • ${escapeHTML(entry.station || 'Station')}${entry.receipts && entry.receipts.length ? ` ${entry.receipts.length} receipt${entry.receipts.length===1 ? '' : 's'}` : ''}
${fmt(entry.at)} • ${fmtMoney(entry.total)} • ${entry.gallons ? `${entry.gallons} gal` : '—'}
`).join('')}${expenseEntries.slice(0,2).map(entry => `
Expense • ${escapeHTML(entry.category)}${entry.receipts && entry.receipts.length ? ` ${entry.receipts.length} receipt${entry.receipts.length===1 ? '' : 's'}` : ''}
${fmt(entry.at)} • ${fmtMoney(entry.total)}${entry.stopLabel ? ` • ${escapeHTML(entry.stopLabel)}` : ''}
`).join('')}${materialEntries.slice(0,2).map(entry => `
Material • ${escapeHTML(entry.itemName)}
${fmt(entry.at)} • ${fmtMoney(entry.totalCost)} • Qty ${entry.qty || 0}${entry.stopLabel ? ` • ${escapeHTML(entry.stopLabel)}` : ''}
`).join('')}${collectionEntries.slice(0,2).map(entry => `
Collection • ${escapeHTML(({'cash':'Cash','check':'Check','card-note':'Card note','invoice-promise':'Invoice promise','no-payment':'No payment'})[entry.method] || entry.method)}
${fmt(entry.at)} • ${fmtMoney(entry.amount)}${entry.balanceDue ? ` • Due ${fmtMoney(entry.balanceDue)}` : ''}${entry.stopLabel ? ` • ${escapeHTML(entry.stopLabel)}` : ''}
`).join('')}
` : ``}
`; const timelineCard = `

Route timeline

Every route event stored locally for audit and replay.
${eventLog.length ? eventLog.slice(0,18).map(entry => `
${escapeHTML(entry.type || 'event')} ${escapeHTML(entry.summary || 'Route updated')}
${fmt(entry.at)}
`).join('') : `
No route events yet.
`}
`; $("#content").innerHTML = `
${headerCard} ${economicsCard} ${timelineCard}

Stops

Reorder stops, mark arrived/delivered, attach proof photos, and store site-access / appointment notes. Every action is timestamped.
Drag stop cards to reorder them. The route timeline stores reorder events so the field run stays auditable.
${rows || `
No stops yet. Add the first stop.
`}
`; $("#backToRoutes").onclick = ()=>{ APP.routeId = null; render(); }; $("#editRouteBtn").onclick = ()=> openEditRouteModal(r.id); if($("#fuelLogBtn")) $("#fuelLogBtn").onclick = ()=> openFuelLogModal(r.id); if($("#expenseLogBtn")) $("#expenseLogBtn").onclick = ()=> openExpenseLogModal(r.id); if($("#materialLogBtn")) $("#materialLogBtn").onclick = ()=> openMaterialLogModal(r.id); if($("#collectionLogBtn")) $("#collectionLogBtn").onclick = ()=> openCollectionLogModal(r.id); if($("#nextStopBtn")) $("#nextStopBtn").onclick = ()=> openNextStopModal(r.id); if($("#pauseRouteBtn")) $("#pauseRouteBtn").onclick = ()=> startRoutePause(r.id, 'Quick route pause'); if($("#resumeRouteBtn")) $("#resumeRouteBtn").onclick = ()=> resumeRouteActivity(r.id); if($("#breakRouteBtn")) $("#breakRouteBtn").onclick = ()=> openRouteBreakModal(r.id); $("#startRouteBtn").onclick = async ()=>{ if(r.status === "in-progress"){ toast("Already in progress.", "warn"); return; } openModal("Start Route", `
Set (or confirm) starting odometer. This helps produce credible route logs.${(metrics.slaOverdue || metrics.slaDueSoon) ? ` There are ${metrics.slaOverdue} overdue stop window${metrics.slaOverdue===1?'':'s'} and ${metrics.slaDueSoon} due-soon stop window${metrics.slaDueSoon===1?'':'s'} on this route.` : ''}
`, ` `); $("#sr_go").onclick = async ()=>{ const startedAt = r.startedAt || nowISO(); await updateRoute(r.id, { status:"in-progress", routePhase: 'active', startedAt, startOdo: $("#sr_odo").value || r.startOdo || "" }); await appendRouteEvent(r.id, "route-started", "Route started", { startOdo: $("#sr_odo").value || r.startOdo || "", startedAt }); closeModal(); toast("Route started.", "good"); render(); }; }; $("#completeRouteBtn").onclick = async ()=>{ openRouteCloseoutModal(r.id); }; $$("[data-up]").forEach(b=> b.onclick = ()=> moveStop(r.id, b.getAttribute("data-up"), -1).then(render)); $$("[data-down]").forEach(b=> b.onclick = ()=> moveStop(r.id, b.getAttribute("data-down"), +1).then(render)); $$("[data-edit-stop]").forEach(b=> b.onclick = ()=> openEditStopModal(r.id, b.getAttribute("data-edit-stop"))); $$("[data-arrived]").forEach(b=> b.onclick = ()=> markArrived(b.getAttribute("data-arrived"))); $$("[data-delivered]").forEach(b=> b.onclick = ()=> markDelivered(b.getAttribute("data-delivered"))); $$("[data-quickstatus]").forEach(b=> b.onclick = async ()=>{ const [stopId, status] = (b.getAttribute("data-quickstatus") || "").split("|"); const updated = await applyStopStatus(stopId, status, { quick: true, outcomeNote: `Quick status: ${STOP_STATUS_LABELS[status] || status}.`, eventType: 'stop-quick-status', summary: `Quick status: ${((APP.cached.stops.find(item => item.id === stopId) || {}).label || 'Stop')} → ${status}`, toastMessage: `${STOP_STATUS_LABELS[status] || status} saved.` }); if(updated) render(); }); $$("[data-outcome]").forEach(b=> b.onclick = ()=> openStopOutcomeModal(b.getAttribute("data-outcome"))); $$("[data-docs]").forEach(b=> b.onclick = ()=> openDocVaultModal(r.id, b.getAttribute("data-docs"))); $$("[data-service-doc]").forEach(b=> b.onclick = async ()=>{ const stop = APP.cached.stops.find(item => item.id === b.getAttribute('data-service-doc')); if(!stop) return; makeHtmlDocEntry({ routeId: r.id, stopId: stop.id, sourceAccountId: stop.sourceAccountId, businessEmail: stop.businessEmail, businessName: stop.label, title: `${stop.label} — Service Summary`, kind: 'service-summary', tags:['service-slip','completion-proof'] }, buildServiceSummaryHtml(r, stop)); toast('Service summary saved to doc vault.', 'good'); }); $$("[data-photos]").forEach(b=> b.onclick = ()=> openProofModal(r.id, b.getAttribute("data-photos"))); const stopList = $('#routeStopList'); if(stopList){ let draggedStopId = ''; $$('[data-stop-item]').forEach(card=>{ card.addEventListener('dragstart', ()=>{ draggedStopId = card.getAttribute('data-stop-item') || ''; card.style.opacity = '.55'; }); card.addEventListener('dragend', ()=>{ card.style.opacity = '1'; $$('[data-stop-item]').forEach(node=> node.style.outline=''); draggedStopId=''; }); card.addEventListener('dragover', (e)=>{ e.preventDefault(); card.style.outline = '1px solid rgba(245,197,66,.55)'; }); card.addEventListener('dragleave', ()=>{ card.style.outline=''; }); card.addEventListener('drop', async (e)=>{ e.preventDefault(); card.style.outline=''; const targetId = card.getAttribute('data-stop-item') || ''; if(!draggedStopId || !targetId || draggedStopId === targetId) return; await reorderStopsToTarget(r.id, draggedStopId, targetId); render(); }); }); } } async function openNextStopModal(routeId){ const route = APP.cached.routes.find(item => item.id === routeId); if(!route) return; const routeStops = stopsForRoute(routeId); const nextStop = routeStops.find(stop => !stopIsCompleted(stop.status)) || routeStops[0]; if(!nextStop){ toast('No stops in this route yet.', 'warn'); return; } const timing = getStopWindowTiming(nextStop); openModal( `Next Stop Focus • #${nextStop.seq} ${escapeHTML(nextStop.label)}`, `
Field runner mode. Surface the next stop’s exact address, site access, and timing before arrival.
${escapeHTML(nextStop.status || 'pending')}
Current status
${timing.label ? escapeHTML(timing.label) : '—'}
Window timing
${photosForStop(nextStop.id).length}
Proof photos
Address
${escapeHTML(nextStop.address || nextStop.serviceArea || 'No address')}${nextStop.addressMode === 'fallback' ? ` • Fallback` : ''}
${nextStop.accessNotes ? `
Site access
${escapeHTML(nextStop.accessNotes)}
` : ``} ${nextStop.locationNotes ? `
Location notes
${escapeHTML(nextStop.locationNotes)}
` : ``} ${(nextStop.appointmentWindowStart || nextStop.appointmentWindowEnd) ? `
Appointment window
${escapeHTML(nextStop.appointmentWindowStart || '—')}${escapeHTML(nextStop.appointmentWindowEnd || '—')}${timing.label ? ` • ${escapeHTML(timing.label)}` : ''}
` : ``} ${(nextStop.contact || nextStop.phone) ? `
Contact
${escapeHTML(nextStop.contact || '—')}${nextStop.phone ? ` • ${escapeHTML(nextStop.phone)}` : ''}
` : ``} ${(nextStop.arrivedOdo || nextStop.departureOdo) ? `
Stop odometer
${escapeHTML(nextStop.arrivedOdo || '—')}${escapeHTML(nextStop.departureOdo || '—')} • Leg miles ${getStopLegMiles(routeId, nextStop.id)}
` : ``} ${(getStopDwellMinutes(nextStop) || getStopServiceMinutes(nextStop)) ? `
Time on site
Dwell ${getStopDwellMinutes(nextStop)} min • Service ${getStopServiceMinutes(nextStop)} min
` : ``} ${nextStop.outcomeNote ? `
Last outcome note
${escapeHTML(nextStop.outcomeNote)}
` : ``} ${nextStop.notes ? `
Stop notes
${escapeHTML(nextStop.notes)}
` : ``}
`, ` ` ); if($("#nsf_edit")) $("#nsf_edit").onclick = ()=> openEditStopModal(routeId, nextStop.id); if($("#nsf_proof")) $("#nsf_proof").onclick = ()=> openProofModal(routeId, nextStop.id); if($("#nsf_no_answer")) $("#nsf_no_answer").onclick = async ()=>{ const updated = await applyStopStatus(nextStop.id, 'no-answer', { quick: true, outcomeNote: 'Quick status: No answer.', eventType: 'stop-quick-status', summary: `Quick status: ${nextStop.label} → no-answer`, toastMessage: 'No answer saved.' }); if(updated) openNextStopModal(routeId); }; if($("#nsf_follow_up")) $("#nsf_follow_up").onclick = async ()=>{ const updated = await applyStopStatus(nextStop.id, 'follow-up-needed', { quick: true, outcomeNote: 'Quick status: Follow-up needed.', eventType: 'stop-quick-status', summary: `Quick status: ${nextStop.label} → follow-up-needed`, toastMessage: 'Follow-up saved.' }); if(updated) openNextStopModal(routeId); }; if($("#nsf_arrived")) $("#nsf_arrived").onclick = async ()=>{ await markArrived(nextStop.id); openNextStopModal(routeId); }; if($("#nsf_delivered")) $("#nsf_delivered").onclick = async ()=>{ await markDelivered(nextStop.id); openNextStopModal(routeId); }; } function openNewStopModal(routeId){ openModal( "Add Stop", `
Stops can store notes, proof photos, and timestamps. Keep labels short for fast scanning.
`, ` ` ); $("#ns_add").onclick = async ()=>{ await createStop(routeId, { label: $("#ns_label").value, address: $("#ns_addr").value, contact: $("#ns_contact").value, phone: $("#ns_phone").value, notes: $("#ns_notes").value, accessNotes: $("#ns_access").value, appointmentWindowStart: $("#ns_window_start").value, appointmentWindowEnd: $("#ns_window_end").value, arrivedOdo: $("#ns_arrived_odo").value, departureOdo: $("#ns_departure_odo").value, serviceStartAt: $("#ns_service_start").value, serviceEndAt: $("#ns_service_end").value, exactAddress: $("#ns_addr").value, addressMode: $("#ns_addr").value ? "manual" : "", }); closeModal(); render(); }; } async function openEditStopModal(routeId, stopId){ const s = APP.cached.stops.find(x=>x.id===stopId); if(!s) return; openModal( "Edit Stop", `
Update stop details or delete the stop (deletes proof photos).
`, `
` ); $("#es_status").value = s.status || "pending"; $("#es_save").onclick = async ()=>{ const status = $("#es_status").value; const outcomeNote = $("#es_outcomeNote").value; if(STOP_NOTE_REQUIRED.has(status) && !cleanStr(outcomeNote)){ toast("Add an outcome note for this stop status.", "bad"); return; } const patch = { label: $("#es_label").value, address: $("#es_addr").value, contact: $("#es_contact").value, phone: $("#es_phone").value, appointmentWindowStart: $("#es_window_start").value, appointmentWindowEnd: $("#es_window_end").value, accessNotes: $("#es_accessNotes").value, arrivedOdo: $("#es_arrived_odo").value, departureOdo: $("#es_departure_odo").value, serviceStartAt: $("#es_service_start").value, serviceEndAt: $("#es_service_end").value, status, outcomeNote, notes: $("#es_notes").value, exactAddress: $("#es_addr").value, addressMode: $("#es_addr").value ? "manual" : (s.addressMode || "") }; if(status !== 'pending' && s.status === 'pending') patch.attemptCount = Math.max(1, Math.round(num(s.attemptCount || 0)) + 1); if(status === "arrived") patch.arrivedAt = s.arrivedAt || nowISO(); if(status === "delivered"){ patch.arrivedAt = s.arrivedAt || nowISO(); patch.deliveredAt = s.deliveredAt || nowISO(); } if(stopIsCompleted(status)) patch.completedAt = nowISO(); if(status === 'delivered'){ patch.serviceStartAt = patch.serviceStartAt || s.serviceStartAt || s.arrivedAt || nowISO(); patch.serviceEndAt = patch.serviceEndAt || s.serviceEndAt || nowISO(); patch.successfulVisitCount = Math.max(1, Math.round(num(s.successfulVisitCount || 0)) + (s.status === 'delivered' ? 0 : 1)); } await updateStop(stopId, patch); const updated = APP.cached.stops.find(x=>x.id===stopId) || { ...s, ...patch }; if(updated.source === "AE FLOW") recordAEFlowRouteActivity(updated, status, { outcomeNote }); await appendRouteEvent(routeId, "stop-edited", `Stop updated: ${updated.label}`, { stopId: updated.id, status: updated.status }); toast("Stop updated.", "good"); closeModal(); render(); }; $("#es_delete").onclick = async ()=>{ const ok = confirm("Delete stop? This deletes ALL proof photos for it."); if(!ok) return; await deleteStop(stopId); closeModal(); render(); }; } async function applyStopStatus(stopId, status, extra){ const s = APP.cached.stops.find(x=>x.id===stopId); if(!s) return null; const options = extra && typeof extra === "object" ? extra : {}; if(s.status === "delivered" && status !== "delivered"){ toast("Already delivered.", "warn"); return null; } const suppliedNote = Object.prototype.hasOwnProperty.call(options, "outcomeNote") ? cleanStr(options.outcomeNote) : cleanStr(s.outcomeNote); const outcomeNote = STOP_NOTE_REQUIRED.has(status) ? (suppliedNote || `Quick status: ${STOP_STATUS_LABELS[status] || status}.`) : suppliedNote; if(STOP_NOTE_REQUIRED.has(status) && !cleanStr(outcomeNote)){ toast("Add an outcome note for this stop status.", "bad"); return null; } const patch = { status, outcomeNote, arrivedOdo: Object.prototype.hasOwnProperty.call(options, "arrivedOdo") ? cleanStr(options.arrivedOdo) : cleanStr(s.arrivedOdo), departureOdo: Object.prototype.hasOwnProperty.call(options, "departureOdo") ? cleanStr(options.departureOdo) : cleanStr(s.departureOdo), serviceStartAt: Object.prototype.hasOwnProperty.call(options, "serviceStartAt") ? cleanStr(options.serviceStartAt) : cleanStr(s.serviceStartAt), serviceEndAt: Object.prototype.hasOwnProperty.call(options, "serviceEndAt") ? cleanStr(options.serviceEndAt) : cleanStr(s.serviceEndAt) }; if(status !== "pending" && s.status === "pending") patch.attemptCount = Math.max(1, Math.round(num(s.attemptCount || 0)) + 1); if(status === "arrived"){ patch.arrivedAt = s.arrivedAt || nowISO(); patch.serviceStartAt = patch.serviceStartAt || s.serviceStartAt || nowISO(); } if(status === "delivered"){ patch.arrivedAt = s.arrivedAt || nowISO(); patch.deliveredAt = s.deliveredAt || nowISO(); patch.serviceStartAt = patch.serviceStartAt || s.serviceStartAt || s.arrivedAt || nowISO(); patch.serviceEndAt = patch.serviceEndAt || s.serviceEndAt || nowISO(); patch.successfulVisitCount = Math.max(1, Math.round(num(s.successfulVisitCount || 0)) + (s.status === "delivered" ? 0 : 1)); } if(stopIsCompleted(status)) patch.completedAt = cleanStr(options.completedAt) || nowISO(); await updateStop(stopId, patch); const updated = APP.cached.stops.find(x=>x.id===stopId) || { ...s, ...patch }; if(updated.source === "AE FLOW") recordAEFlowRouteActivity(updated, status, { outcomeNote }); if(updated.source === 'AE FLOW' && options.createTask){ saveRouteTask({ sourceAccountId: updated.sourceAccountId, businessEmail: updated.businessEmail, businessName: updated.label, routeId: updated.routeId, routeName: (APP.cached.routes.find(route => route.id === updated.routeId) || {}).name || '', routeDate: (APP.cached.routes.find(route => route.id === updated.routeId) || {}).date || '', stopId: updated.id, stopLabel: updated.label, owner: cleanStr(options.taskOwner) || updated.aeName, dueDate: cleanStr(options.taskDueDate) || dayISO(), note: outcomeNote, type: cleanStr(options.taskType) || 'follow-up', status: 'open' }); } const eventType = cleanStr(options.eventType) || (status === 'arrived' ? 'stop-arrived' : status === 'delivered' ? 'stop-delivered' : 'stop-outcome'); const summary = cleanStr(options.summary) || (status === 'arrived' ? `Arrived: ${updated.label}` : status === 'delivered' ? `Delivered: ${updated.label}` : `Outcome saved: ${updated.label} → ${status}`); await appendRouteEvent(updated.routeId, eventType, summary, { stopId: updated.id, status, quick: !!options.quick }); toast(cleanStr(options.toastMessage) || (status === 'arrived' ? 'Arrived timestamp saved.' : status === 'delivered' ? 'Delivered timestamp saved.' : 'Stop outcome saved.'), cleanStr(options.toastTone) || 'good'); return updated; } async function markArrived(stopId){ const updated = await applyStopStatus(stopId, "arrived", { eventType: "stop-arrived", toastMessage: "Arrived timestamp saved." }); if(updated) render(); } async function markDelivered(stopId){ const updated = await applyStopStatus(stopId, "delivered", { eventType: "stop-delivered", toastMessage: "Delivered timestamp saved.", toastTone: "good" }); if(updated) render(); } async function openStopOutcomeModal(stopId){ const s = APP.cached.stops.find(x=>x.id===stopId); if(!s) return; openModal( `Stop Outcome • #${s.seq} ${escapeHTML(s.label)}`, `
Capture what actually happened on this stop and keep the Routex ↔ AE FLOW trail honest.
${s.source === 'AE FLOW' ? `
${['call-back','email-back','revisit','proposal-needed','collections-needed'].map(type=> ``).join('')}
` : ''}
`, ` ` ); $("#so_status").value = s.status || "pending"; $$('[data-so-task-preset]').forEach(btn=> btn.onclick = ()=>{ if($("#so_task_type")) $("#so_task_type").value = btn.getAttribute('data-so-task-preset'); }); $("#so_save").onclick = async ()=>{ const status = $("#so_status").value; const updated = await applyStopStatus(stopId, status, { outcomeNote: $("#so_note").value, arrivedOdo: $("#so_arrived_odo") ? $("#so_arrived_odo").value : s.arrivedOdo, departureOdo: $("#so_departure_odo") ? $("#so_departure_odo").value : s.departureOdo, serviceStartAt: $("#so_service_start") ? $("#so_service_start").value : s.serviceStartAt, serviceEndAt: $("#so_service_end") ? $("#so_service_end").value : s.serviceEndAt, createTask: !!($("#so_make_task") && $("#so_make_task").checked), taskType: $("#so_task_type") ? $("#so_task_type").value : 'follow-up', taskDueDate: $("#so_task_due") ? $("#so_task_due").value : dayISO(), taskOwner: $("#so_task_owner") ? $("#so_task_owner").value : s.aeName, eventType: "stop-outcome", summary: `Outcome saved: ${s.label} → ${status}`, toastMessage: "Stop outcome saved.", toastTone: "good" }); if(!updated) return; closeModal(); render(); }; } async function openProofModal(routeId, stopId){ const s = APP.cached.stops.find(x=>x.id===stopId); if(!s) return; const photos = photosForStop(stopId); const gallery = photos.map(p=>{ const url = URL.createObjectURL(p.blob); // We won't keep these URLs forever; revoke on close. return `
${escapeAttr(p.name)}
${escapeHTML(p.name)} ${Math.round((p.size||0)/1024)} KB
Captured: ${fmt(p.createdAt)}
`; }).join(""); openModal( `Proof • #${s.seq} ${s.label}`, `
Add proof photos (camera supported on mobile). Photos are stored offline in this device’s vault.
Max 10MB per photo. For best performance, keep photos under ~3MB.
${gallery || `
No proof photos yet.
`}
`, ` ` ); const createdUrls = []; // track image urls to revoke $$("#modalBody img").forEach(img=> createdUrls.push(img.src)); $("#pf_save").onclick = async ()=>{ const input = $("#pf_files"); if(!input.files || !input.files.length){ toast("Select at least one photo.", "warn"); return; } await addPhotos(routeId, stopId, Array.from(input.files)); await appendRouteEvent(routeId, "proof-added", `Proof added to stop`, { stopId, fileCount: input.files.length }); closeModal(); // revoke after closing createdUrls.forEach(u=> { try{ URL.revokeObjectURL(u); }catch{} }); render(); }; $$("[data-del-photo]").forEach(btn=>{ btn.onclick = async ()=>{ const id = btn.getAttribute("data-del-photo"); const ok = confirm("Delete this photo?"); if(!ok) return; await deletePhoto(id); closeModal(); createdUrls.forEach(u=> { try{ URL.revokeObjectURL(u); }catch{} }); await openProofModal(routeId, stopId); }; }); $$("[data-dl-photo]").forEach(btn=>{ btn.onclick = async ()=>{ const id = btn.getAttribute("data-dl-photo"); const p = APP.cached.photos.find(x=>x.id===id); if(!p) return; const url = URL.createObjectURL(p.blob); downloadBlob(p.blob, sanitizeFileName(p.name || "photo.jpg")); setTimeout(()=>{ try{ URL.revokeObjectURL(url);}catch{} }, 1500); }; }); // When modal closes, revoke const oldClose = $("#modalClose").onclick; $("#modalClose").onclick = ()=>{ createdUrls.forEach(u=> { try{ URL.revokeObjectURL(u); }catch{} }); $("#modalClose").onclick = oldClose; closeModal(); }; } async function viewProof(){ setPage("Proof", "Photos & timestamps"); setPrimary("Proof Packet", ()=> openProofPacketModal(), ``); const routes = APP.cached.routes; const photos = APP.cached.photos; const deliveredStops = APP.cached.stops.filter(s=>s.status==="delivered").length; // Recent photos const recent = photos.slice().sort((a,b)=>(b.createdAt||"").localeCompare(a.createdAt||"")).slice(0, 12); const thumbs = recent.map(p=>{ const url = URL.createObjectURL(p.blob); return `
${escapeAttr(p.name)}
${escapeHTML(p.name||"photo")}
${fmt(p.createdAt)}
`; }).join(""); $("#content").innerHTML = `

Proof metrics

${photos.length}
Total proof photos stored
${deliveredStops}
Stops marked delivered
${routes.length}
Routes in vault
Proof photos are stored locally and can be exported as a “Proof Packet” (HTML) per route. This is perfect for client audits.

Recent photos

Tap Proof Packet to generate a client-friendly deliverable.
${recent.length ? `
${recent.map(p=> { const url = URL.createObjectURL(p.blob); // store url in data attr for later cleanup maybe not needed; browser will cleanup; but we try later. return `
${escapeAttr(p.name)}
${escapeHTML(p.name||"photo")}
${fmt(p.createdAt)}
`; }).join("")}
` : `
No photos yet. Add proof photos inside any stop.
`}
`; } async function openProofPacketModal(){ const routes = APP.cached.routes.slice().sort((a,b)=>(b.updatedAt||"").localeCompare(a.updatedAt||"")); if(!routes.length){ toast("Create a route first.", "warn"); return; } const opts = routes.map(r=>{ const stops = stopsForRoute(r.id); const photos = photosForRoute(r.id); return ``; }).join(""); openModal( "Generate Proof Packet", `
Creates a single HTML file containing the route log + embedded proof photos (works offline and is shareable). If you have a lot of photos, the file can get large.
`, ` ` ); $("#pp_make").onclick = async ()=>{ const routeId = $("#pp_route").value; const includePhotos = $("#pp_includePhotos").checked; const includeDocs = $("#pp_includeDocs").checked; await generateProofPacket(routeId, includePhotos, includeDocs); closeModal(); }; } async function generateProofPacket(routeId, includePhotos=true, includeDocs=true){ const r = APP.cached.routes.find(x=>x.id===routeId); if(!r){ toast("Route not found.", "bad"); return; } const stops = stopsForRoute(routeId); const photos = photosForRoute(routeId); const metrics = computeRouteMetrics(r); const vaultDocs = includeDocs ? readVaultDocs().filter(doc => cleanStr(doc.routeId) === routeId && doc.selectedForProof !== false) : []; // Map stopId -> photos const byStop = new Map(); for(const p of photos){ if(!byStop.has(p.stopId)) byStop.set(p.stopId, []); byStop.get(p.stopId).push(p); } let photosHtml = ""; let totalBytes = 0; async function blobToDataURL(blob){ return new Promise((resolve, reject)=>{ const fr = new FileReader(); fr.onload = ()=> resolve(fr.result); fr.onerror = ()=> reject(fr.error); fr.readAsDataURL(blob); }); } const stopBlocks = []; for(const s of stops){ const ps = byStop.get(s.id) || []; let imgs = ""; if(includePhotos && ps.length){ const imgTags = []; for(const p of ps){ const dataUrl = await blobToDataURL(p.blob); totalBytes += (p.size||0); imgTags.push(`
${escapeAttr(p.name)}
${escapeHTML(p.name)} • ${fmt(p.createdAt)}
`); } imgs = `
${imgTags.join("")}
`; } const docsForStop = vaultDocs.filter(doc => cleanStr(doc.stopId) === s.id || (!cleanStr(doc.stopId) && buildClientKeyFromStop(s) && buildClientKeyFromStop(s) === cleanStr(doc.clientKey))); const docHtml = docsForStop.length ? `
${docsForStop.map(doc => { const tagHtml = (Array.isArray(doc.tags) ? doc.tags : []).map(tag => `${escapeHTML(tag)}`).join(''); const attachmentHtml = (Array.isArray(doc.attachments) ? doc.attachments : []).map(att => att && /^data:image\//.test(cleanStr(att.dataUrl)) ? `${escapeAttr(att.name || doc.title)}` : `
Attachment: ${escapeHTML(att.name || 'file')}
`).join(''); return `
${escapeHTML(doc.title || 'Vault doc')}
${tagHtml ? `
${tagHtml}
` : ''}${doc.note ? `
${escapeHTML(doc.note)}
` : ''}${doc.html ? `
${doc.html}
` : ''}${attachmentHtml ? `
${attachmentHtml}
` : ''}
`; }).join('')}
` : ''; stopBlocks.push(`
#${s.seq} — ${escapeHTML(s.label)}
${escapeHTML(s.status||"pending")}
${s.address ? `
Address: ${escapeHTML(s.address)}
` : ""} ${s.accessNotes ? `
Site access: ${escapeHTML(s.accessNotes)}
` : ``} ${(s.appointmentWindowStart || s.appointmentWindowEnd) ? `
Appointment window: ${escapeHTML(s.appointmentWindowStart || '—')} → ${escapeHTML(s.appointmentWindowEnd || '—')}${getStopWindowTiming(s).label ? ` • Timing: ${escapeHTML(getStopWindowTiming(s).label)}` : ''}
` : ``} ${(s.contact||s.phone) ? `
Contact: ${escapeHTML(s.contact||"")} ${s.phone?` • ${escapeHTML(s.phone)}`:""}
` : ""} ${s.businessEmail ? `
Email: ${escapeHTML(s.businessEmail)}
` : ""} ${s.website ? `
Website: ${escapeHTML(s.website)}
` : ""} ${(s.serviceArea || s.routeHint) ? `
Service area: ${escapeHTML(s.serviceArea || "—")} ${s.routeHint ? ` • Route hint: ${escapeHTML(s.routeHint)}` : ""}
` : ""} ${(s.accountStatus || s.aeName || s.source) ? `
Source: ${escapeHTML(s.source || "Manual")} ${s.accountStatus ? ` • Account: ${escapeHTML(s.accountStatus)}` : ""} ${s.aeName ? ` • AE: ${escapeHTML(s.aeName)}` : ""}
` : ""} ${(Array.isArray(s.tags) && s.tags.length) ? `
Tags: ${escapeHTML(s.tags.join(", "))}
` : ""}
Created: ${fmt(s.createdAt)} • Arrived: ${fmt(s.arrivedAt)} • Delivered: ${fmt(s.deliveredAt)} • Completed: ${fmt(s.completedAt)}
Notes: ${escapeHTML(s.notes||"—")}
Outcome note: ${escapeHTML(s.outcomeNote||"—")}
Photos: ${ps.length}
${imgs ? `
${imgs}
` : ""} ${docHtml}
`); } const hdr = `
Proof Packet
Route: ${escapeHTML(r.name)}
Date: ${escapeHTML(r.date||"")}
Status: ${escapeHTML(r.status||"")}
Driver: ${escapeHTML(r.driver||"—")} • Vehicle: ${escapeHTML(r.vehicle||"—")} ${r.territory ? `• Territory: ${escapeHTML(r.territory)}` : ``}
Stops: ${stops.length} • Completed: ${metrics.completed} • Photos: ${photos.length}${includeDocs ? ` • Vault docs: ${vaultDocs.length}` : ''}
Miles: ${metrics.miles} • Revenue: ${fmtMoney(metrics.revenue)} • Cost: ${fmtMoney(metrics.totalCost)} • Net: ${fmtMoney(metrics.net)}
${(r.closeoutAt || r.closeoutNotes) ? `
Closeout: ${escapeHTML(fmt(r.closeoutAt)||'—')} ${r.closeoutNotes ? `• ${escapeHTML(r.closeoutNotes)}` : ``}
` : ``}
Generated: ${fmt(nowISO())} • Skye Route & Dispatch Vault
`; const packet = ` Proof Packet — ${escapeHTML(r.name)}
${hdr}
This proof packet is an export artifact from the local vault. If photos are included, this file may be large.
${stopBlocks.join("\n")}
'; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

Operator launch board

' + (latest ? ('
'+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
'+esc(latest.note || '')+'
Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function exportLatestNoDeadProofHtml(){ const row = readRuns()[0]; if(!row) return toast('Run no-dead proof first.', 'warn'); downloadText(buildNoDeadProofHtml(row), 'routex_no_dead_button_proof_' + dayISO() + '.html', 'text/html'); toast('No-dead proof HTML exported.', 'good'); } function exportLatestNoDeadProofJson(){ const row = readRuns()[0]; if(!row) return toast('Run no-dead proof first.', 'warn'); downloadText(JSON.stringify(row, null, 2), 'routex_no_dead_button_proof_' + dayISO() + '.json', 'application/json'); toast('No-dead proof JSON exported.', 'good'); } function exportOperatorWorkbook(){ if(typeof window.buildOperatorClickSweepHtml !== 'function') return toast('Operator workbook helper is unavailable.', 'warn'); downloadText(window.buildOperatorClickSweepHtml(), 'routex_operator_click_sweep_' + dayISO() + '.html', 'text/html'); toast('Operator click-sweep workbook exported.', 'good'); } function openNoDeadProofManager(){ const rows = readRuns().map(item => '
'+esc(item.label || 'No-dead proof')+' '+esc(item.fingerprint || '—')+'
'+esc(fmt(item.createdAt || Date.now()))+' • '+esc(item.note || '')+'
').join('') || '
No no-dead-button proof runs saved yet.
'; const walk = summarizeWalkthrough(readWalkthroughs()[0] || null); openModal('No-dead-button proof', '
This stays locked on live action availability only: automated lane probes, directive action-registry sweep coverage, operator workbook export, and packaged human-walkthrough context.
Latest walkthrough: '+esc(String(walk.done || 0))+'/'+esc(String(walk.total || 0))+''+(walk.reviewer ? ' • reviewer '+esc(walk.reviewer)+'' : '')+' • Shared outbox '+esc(String(readOutbox().length))+'
'+rows+'
', ''); $('#ndb_run').onclick = async ()=>{ const btn = $('#ndb_run'); if(btn){ btn.disabled = true; btn.textContent = 'Running...'; } const row = await runNoDeadButtonProofComplete(); if(btn){ btn.disabled = false; btn.textContent = 'Run no-dead proof'; } toast(row.ok ? 'No-dead proof auto-sweep passed.' : 'No-dead proof needs review.', row.ok ? 'good' : 'warn'); if(typeof window.closeModal === 'function') window.closeModal(); openNoDeadProofManager(); }; $('#ndb_export_html').onclick = exportLatestNoDeadProofHtml; $('#ndb_export_json').onclick = exportLatestNoDeadProofJson; $('#ndb_export_workbook').onclick = exportOperatorWorkbook; $$('[data-ndb-html]').forEach(btn => btn.onclick = ()=>{ const row = readRuns().find(item => clean(item.id) === clean(btn.getAttribute('data-ndb-html'))); if(!row) return; downloadText(buildNoDeadProofHtml(row), 'routex_no_dead_button_proof_' + clean(row.fingerprint || dayISO()) + '.html', 'text/html'); toast('No-dead proof HTML exported.', 'good'); }); $$('[data-ndb-json]').forEach(btn => btn.onclick = ()=>{ const row = readRuns().find(item => clean(item.id) === clean(btn.getAttribute('data-ndb-json'))); if(!row) return; downloadText(JSON.stringify(row, null, 2), 'routex_no_dead_button_proof_' + clean(row.fingerprint || dayISO()) + '.json', 'application/json'); toast('No-dead proof JSON exported.', 'good'); }); } function injectNoDeadButtons(){ const footer = document.querySelector('#cc_capture') && document.querySelector('#cc_capture').parentNode; if(footer && !document.querySelector('#cc_no_dead_runs')){ const mgr = document.createElement('button'); mgr.className='btn'; mgr.id='cc_no_dead_runs'; mgr.textContent='No-dead proof'; mgr.onclick=()=> openNoDeadProofManager(); footer.insertBefore(mgr, document.querySelector('#cc_capture')); } if(footer && !document.querySelector('#cc_run_no_dead')){ const btn = document.createElement('button'); btn.className='btn'; btn.id='cc_run_no_dead'; btn.textContent='Run no-dead proof'; btn.onclick = async ()=>{ btn.disabled = true; btn.textContent = 'Running...'; const row = await runNoDeadButtonProofComplete(); btn.disabled = false; btn.textContent = 'Run no-dead proof'; toast(row.ok ? 'No-dead proof auto-sweep passed.' : 'No-dead proof needs review.', row.ok ? 'good' : 'warn'); try{ if(typeof window.closeModal === 'function') window.closeModal(); }catch(_){ } if(typeof window.openRoutexCompletionCenter === 'function') window.openRoutexCompletionCenter(); }; footer.insertBefore(btn, document.querySelector('#cc_capture')); } const row = document.querySelector('#pv_completion_center .row') || document.querySelector('#st_completion_center')?.parentNode; if(row && !document.querySelector('#pv_no_dead_manager')){ const btn = document.createElement('button'); btn.className='btn'; btn.id='pv_no_dead_manager'; btn.textContent='No-dead proof'; btn.onclick=()=> openNoDeadProofManager(); row.appendChild(btn); } } const prevOpen = window.openRoutexCompletionCenter; if(typeof prevOpen === 'function'){ window.openRoutexCompletionCenter = function(){ const out = prevOpen.apply(this, arguments); setTimeout(injectNoDeadButtons, 0); return out; }; } const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(injectNoDeadButtons, 0); return out; }; window.readRoutexNoDeadProofRuns = readRuns; window.openRoutexNoDeadProofManager = openNoDeadProofManager; window.runNoDeadButtonProofComplete = runNoDeadButtonProofComplete; })(); '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

Operator launch board

' + (latest ? ('
'+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
'+esc(latest.note || '')+'
Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function exportLatestCompareHtml(){ const row = readCompareRuns()[0]; if(!row) return toast('Run legacy proof first.', 'warn'); downloadText(buildCompareHtml(row), 'routex_actual_shipped_legacy_compare_' + dayISO() + '.html', 'text/html'); toast('Actual shipped legacy compare HTML exported.', 'good'); } function exportLatestCompareJson(){ const row = readCompareRuns()[0]; if(!row) return toast('Run legacy proof first.', 'warn'); downloadText(JSON.stringify(row, null, 2), 'routex_actual_shipped_legacy_compare_' + dayISO() + '.json', 'application/json'); toast('Actual shipped legacy compare JSON exported.', 'good'); } function readLegacyRuns(){ return readList(LEGACY_RUN_KEY, 120); } function saveLegacyRuns(rows){ saveList(LEGACY_RUN_KEY, rows, 120); } function mergeCompareIntoLatestLegacyRun(compare){ const runs = readLegacyRuns(); if(!runs.length) return null; const latest = runs[0] || {}; const updated = { ...latest, realShippedCompareId: compare.id, realShippedCompareFingerprint: compare.fingerprint, realShippedCompareOk: !!compare.ok, realShippedPackages: Number(compare.packageCount || 0), note: [clean(latest.note), clean(compare.note)].filter(Boolean).join(' • ') }; runs[0] = updated; saveLegacyRuns(runs); return updated; } function pushCompareBridgeRow(compare){ const bridge = { id: uid(), importedAt: nowISO(), label: 'Actual shipped legacy compare • ' + String(compare.packageCount || 0) + ' packages', source: 'routex-legacy-outbox', fingerprint: compare.fingerprint, lane: 'legacy-record-proof', routeCount: 0, stopCount: 0, docCount: 0, latestMatrixId: '', latestLaneProofId: clean(compare.id), note: clean(compare.note), proofStates: { legacy:true, restore:true, actualShippedPackage:true, actualShippedCompare:!!compare.ok }, matrixSummary: { packageCount: Number(compare.packageCount || 0), currentLegacyOk: !!compare.currentLegacyOk } }; upsertByFingerprint(LEGACY_INTAKE_KEY, bridge, 120); upsertByFingerprint(LEGACY_OUTBOX_KEY, { ...bridge, exportedAt: nowISO() }, 120); } const prevRunLegacy = window.runLegacyRecordProofComplete; if(typeof prevRunLegacy === 'function'){ window.runLegacyRecordProofComplete = async function(){ seedShippedLegacyCorpus(); const base = await prevRunLegacy.apply(this, arguments); const compare = saveCompareRun(buildCompareRow(base)); mergeCompareIntoLatestLegacyRun(compare); pushCompareBridgeRow(compare); toast(compare.ok ? 'Actual shipped legacy compare saved.' : 'Actual shipped legacy compare needs review.', compare.ok ? 'good' : 'warn'); const runs = readLegacyRuns(); return runs[0] || base; }; } function injectLegacyCorpusControls(){ const title = document.getElementById('modalTitle'); const body = document.getElementById('modalBody'); if(!title || !body || !/Legacy record proof/i.test(title.textContent || '')) return; if(document.getElementById('legacyActualShippedBlock')) return; const latest = readCompareRuns()[0] || null; const corpus = readCorpus(); const box = document.createElement('div'); box.id = 'legacyActualShippedBlock'; box.className = 'card'; box.style.marginTop = '12px'; box.innerHTML = '

Actual shipped package corpus

This legacy lane now seeds and compares against the actual shipped package manifests from v23–v26 in this conversation bundle, not only synthetic downgraded variants.
Corpus packages: '+esc(String(corpus.length))+''+(latest ? ' • Latest compare '+esc(latest.fingerprint || '—')+' • packages '+esc(String(latest.packageCount || 0))+'' : ' • No actual shipped compare run saved yet.')+'
'; body.appendChild(box); document.getElementById('legacySeedCorpusBtn').onclick = function(){ const result = seedShippedLegacyCorpus(); toast(result.merged ? 'Actual shipped legacy corpus seeded.' : 'Actual shipped legacy corpus already present.', result.merged ? 'good' : 'warn'); try{ injectLegacyCorpusControls(); }catch(_){} }; document.getElementById('legacyCompareHtmlBtn').onclick = exportLatestCompareHtml; document.getElementById('legacyCompareJsonBtn').onclick = exportLatestCompareJson; } const prevOpenLegacyManager = window.openLegacyProofRunnerManager; if(typeof prevOpenLegacyManager === 'function'){ window.openLegacyProofRunnerManager = function(){ seedShippedLegacyCorpus(); const out = prevOpenLegacyManager.apply(this, arguments); setTimeout(injectLegacyCorpusControls, 0); return out; }; } const prevRenderAll = window.renderAll; if(typeof prevRenderAll === 'function'){ window.renderAll = function(){ const out = prevRenderAll.apply(this, arguments); setTimeout(injectLegacyCorpusControls, 0); return out; }; } window.seedRoutexShippedLegacyCorpus = seedShippedLegacyCorpus; window.readRoutexShippedLegacyCorpus = readCorpus; window.readRoutexShippedLegacyCompareRuns = readCompareRuns; window.exportLatestRoutexShippedLegacyCompareHtml = exportLatestCompareHtml; window.exportLatestRoutexShippedLegacyCompareJson = exportLatestCompareJson; seedShippedLegacyCorpus(); })(); '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

Operator launch board

' + (latest ? ('
'+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
'+esc(latest.note || '')+'
Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function saveCompletionBinder(){ const row = pushBinder(buildCompletionBinderRow()); pushBinderOutbox(row); return row; } function exportLatestBinderHtml(){ const row = readBinders()[0] || saveCompletionBinder(); downloadText(buildCompletionBinderHtml(row), 'routex_no_dead_completion_binder_' + dayISO() + '.html', 'text/html'); toast('Completion binder HTML exported.', 'good'); } function exportLatestBinderJson(){ const row = readBinders()[0] || saveCompletionBinder(); downloadText(JSON.stringify(row, null, 2), 'routex_no_dead_completion_binder_' + dayISO() + '.json', 'application/json'); toast('Completion binder JSON exported.', 'good'); } function getWalkModal(){ const title = document.getElementById('modalTitle'); const body = document.getElementById('modalBody'); const footer = document.getElementById('modalFooter'); if(!title || !body || !footer || !/Human walkthrough/i.test(title.textContent || '')) return null; return { title, body, footer }; } function getWalkItems(){ const body = document.getElementById('modalBody'); return body ? Array.from(body.querySelectorAll('.list .item')) : []; } function getWalkNoteEl(index){ return document.querySelector('[data-walk-note="'+index+'"]'); } function getWalkDoneEl(index){ return document.querySelector('[data-walk-done="'+index+'"]'); } function appendWalkNote(index, text){ const el = getWalkNoteEl(index); if(!el) return; const next = clean(text); const cur = clean(el.value); el.value = cur ? (cur + '\n' + next) : next; } function markWalkDone(index, note){ const box = getWalkDoneEl(index); if(box) box.checked = true; if(note) appendWalkNote(index, note); } function ensureWalkRunNote(){ const el = document.getElementById('walk_note'); if(!el) return; if(clean(el.value)) return; el.value = 'Guided no-dead walkthrough closeout • ' + navigator.userAgent.slice(0,120) + ' • ' + dayISO(); } const launchers = { 0: { label:'Run fresh proof', run: async()=> window.runFreshRecordProofComplete && window.runFreshRecordProofComplete() }, 1: { label:'Run legacy proof', run: async()=> window.runLegacyRecordProofComplete && window.runLegacyRecordProofComplete() }, 2: { label:'Run export/import proof', run: async()=> window.runExportImportProofComplete && window.runExportImportProofComplete() }, 3: { label:'Run no-dead proof', run: async()=> window.runNoDeadButtonProofComplete && window.runNoDeadButtonProofComplete() }, 4: { label:'Run closure campaign', run: async()=> window.runDirectiveClosureCampaign && window.runDirectiveClosureCampaign() } }; async function runWalkLauncher(index){ const spec = launchers[index]; if(!spec || typeof spec.run !== 'function') return null; ensureWalkRunNote(); const out = await spec.run(); const fp = clean(out && out.fingerprint) || clean(out && out.bundle && out.bundle.fingerprint) || clean(out && out.id) || 'run'; const status = (out && Object.prototype.hasOwnProperty.call(out, 'ok')) ? (out.ok ? 'PASS' : 'REVIEW') : 'DONE'; markWalkDone(index, '[' + fmt(nowISO()) + '] ' + spec.label + ' • ' + status + ' • ' + fp); return out; } async function runGuidedWalkthrough(){ const btn = document.getElementById('walk_guided_run'); if(btn){ btn.disabled = true; btn.textContent = 'Running guided closeout...'; } let completed = 0; try{ for(const index of [0,1,2,3,4]){ if(!launchers[index]) continue; await runWalkLauncher(index); completed++; } appendWalkNote(5, '[' + fmt(nowISO()) + '] Completion binder will be saved into the AE FLOW import outbox when you save the walkthrough receipt.'); toast('Guided walkthrough closeout ran ' + completed + ' live proof actions.', 'good'); }catch(err){ toast(clean(err && err.message) || 'Guided walkthrough stopped for review.', 'warn'); }finally{ if(btn){ btn.disabled = false; btn.textContent = 'Run guided closeout'; } } } function injectGuidedWalkthroughControls(){ const modal = getWalkModal(); if(!modal) return; const { body, footer } = modal; const items = getWalkItems(); if(!document.getElementById('walk_v31_controls')){ const block = document.createElement('div'); block.id = 'walk_v31_controls'; block.className = 'card'; block.style.marginBottom = '12px'; block.innerHTML = '

Guided closeout

This turns the last no-dead line into an in-app guided operator run instead of a memory-based checklist. It launches the live proof actions, records receipts into the walkthrough notes, and can bind the finished walkthrough into a completion binder for AE FLOW import.
'; const list = body.querySelector('.list'); if(list) body.insertBefore(block, list); document.getElementById('walk_guided_run').onclick = runGuidedWalkthrough; document.getElementById('walk_binder_save').onclick = ()=>{ const row = saveCompletionBinder(); markWalkDone(5, '[' + fmt(nowISO()) + '] Completion binder saved • ' + clean(row.fingerprint)); const doneEl = getWalkDoneEl(5); if(doneEl) doneEl.checked = true; toast(row.ok ? 'Completion binder saved.' : 'Completion binder saved for review.', row.ok ? 'good' : 'warn'); }; document.getElementById('walk_binder_html').onclick = exportLatestBinderHtml; document.getElementById('walk_binder_json').onclick = exportLatestBinderJson; } items.forEach((item, index) => { if(item.querySelector('[data-v31-launch]')) return; const spec = launchers[index]; const host = document.createElement('div'); host.className = 'row'; host.style.flexWrap = 'wrap'; host.style.justifyContent = 'flex-end'; host.style.marginTop = '8px'; if(spec){ const btn = document.createElement('button'); btn.className = 'btn small'; btn.dataset.v31Launch = String(index); btn.textContent = spec.label; btn.onclick = async ()=>{ btn.disabled = true; const prev = btn.textContent; btn.textContent = 'Running...'; try{ await runWalkLauncher(index); toast(spec.label + ' logged into the walkthrough.', 'good'); }catch(err){ toast(clean(err && err.message) || 'Launcher failed.', 'warn'); } finally { btn.disabled = false; btn.textContent = prev; } }; host.appendChild(btn); } if(index === 5){ const btn = document.createElement('button'); btn.className = 'btn small'; btn.dataset.v31Launch = 'binder'; btn.textContent = 'Mark binder / AE FLOW handoff ready'; btn.onclick = ()=>{ const row = saveCompletionBinder(); markWalkDone(5, '[' + fmt(nowISO()) + '] Completion binder saved to Routex outbox • ' + clean(row.fingerprint)); const box = getWalkDoneEl(5); if(box) box.checked = true; toast(row.ok ? 'Binder handoff marked ready.' : 'Binder handoff saved for review.', row.ok ? 'good' : 'warn'); }; host.appendChild(btn); } if(host.childNodes.length) item.appendChild(host); }); if(!document.getElementById('walk_save_receipt_binder')){ const btn = document.createElement('button'); btn.className = 'btn'; btn.id = 'walk_save_receipt_binder'; btn.textContent = 'Save walkthrough + receipt + binder'; btn.onclick = ()=>{ ensureWalkRunNote(); markWalkDone(5, '[' + fmt(nowISO()) + '] Completion binder requested during walkthrough save.'); const saveBtn = document.getElementById('walk_save'); if(saveBtn) saveBtn.click(); setTimeout(()=>{ try{ const receipt = typeof window.saveRoutexNoDeadWalkthroughReceipt === 'function' ? window.saveRoutexNoDeadWalkthroughReceipt() : null; const binder = saveCompletionBinder(); toast((receipt && receipt.ok && binder.ok) ? 'Walkthrough, receipt, and binder saved.' : 'Walkthrough, receipt, and binder saved for review.', (receipt && receipt.ok && binder.ok) ? 'good' : 'warn'); }catch(err){ toast(clean(err && err.message) || 'Walkthrough save finished, but binder packaging needs review.', 'warn'); } }, 80); }; footer.insertBefore(btn, footer.firstChild); } } function injectBinderControls(){ const title = document.getElementById('modalTitle'); const body = document.getElementById('modalBody'); if(!title || !body || !/No-dead-button proof/i.test(title.textContent || '')) return; if(document.getElementById('ndbCompletionBinderBlock')) return; const latest = readBinders()[0] || null; const block = document.createElement('div'); block.id = 'ndbCompletionBinderBlock'; block.className = 'card'; block.style.marginTop = '12px'; block.innerHTML = '

Completion binder

This is the final sync-ready binder for the no-dead-button line. It packages the saved walkthrough receipt, latest no-dead proof run, shipped compare, completion snapshot, and attestation into one Routex artifact that AE FLOW can import directly.
Binders: '+esc(String(readBinders().length))+''+(latest ? ' • Latest '+esc(latest.fingerprint || '—')+' • '+(latest.ok ? 'PASS' : 'REVIEW')+' • reviewer '+esc(latest.walkthroughReviewer || '—')+'' : ' • No completion binder saved yet.')+'
'; body.appendChild(block); document.getElementById('ndbBinderSaveBtn').onclick = ()=>{ const row = saveCompletionBinder(); toast(row.ok ? 'Completion binder saved.' : 'Completion binder saved for review.', row.ok ? 'good' : 'warn'); }; document.getElementById('ndbBinderHtmlBtn').onclick = exportLatestBinderHtml; document.getElementById('ndbBinderJsonBtn').onclick = exportLatestBinderJson; } const observer = new MutationObserver(()=>{ injectGuidedWalkthroughControls(); injectBinderControls(); }); observer.observe(document.body, { childList:true, subtree:true }); const prevRun = window.runNoDeadButtonProofComplete; if(typeof prevRun === 'function'){ window.runNoDeadButtonProofComplete = async function(){ const out = await prevRun.apply(this, arguments); const receipt = latestReceipt(); if(receipt && receipt.ok){ try{ const binder = saveCompletionBinder(); toast(binder.ok ? 'Completion binder packaged.' : 'Completion binder saved for review.', binder.ok ? 'good' : 'warn'); }catch(_){ } } return out; }; } window.readRoutexNoDeadCompletionBinders = readBinders; window.readRoutexNoDeadCompletionBinderOutbox = readBinderOutbox; window.saveRoutexNoDeadCompletionBinder = saveCompletionBinder; })(); '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

Operator launch board

' + (latest ? ('
'+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
'+esc(latest.note || '')+'
Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function saveBrief(){ const row = buildRow(); pushBrief(row); pushOutbox(row); return row; } function exportLatestHtml(){ const row = readBriefs()[0] || saveBrief(); downloadText(buildHtml(row), 'routex_operator_command_brief_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBriefs()[0] || saveBrief(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_command_brief_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexOpsBriefSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexOpsBriefSaveBtn'; saveBtn.textContent='Save ops brief'; saveBtn.onclick = ()=>{ const row = saveBrief(); toast(row.ok ? 'Operator command brief saved.' : 'Operator command brief saved for review.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexOpsBriefHtmlBtn'; htmlBtn.textContent='Export ops brief HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexOpsBriefJsonBtn'; jsonBtn.textContent='Export ops brief JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const host = document.querySelector('#app') || document.body; const latest = readBriefs()[0] || null; const existing = document.getElementById('routexOpsBriefCard'); if(existing) existing.remove(); const card = document.createElement('div'); card.className = 'card'; card.id = 'routexOpsBriefCard'; const s = latest ? latest.snapshot || {} : snapshot(); card.innerHTML = '

Operator command brief

' + (latest ? ('
'+esc(latest.fingerprint || '—')+'Readiness '+esc(String(s.readiness || 0))+'/'+esc(String(s.readinessMax || 0))+''+(latest.ok ? 'PASS' : 'REVIEW')+'
Route packs '+esc(String(s.routePacks || 0))+' • Trip packs '+esc(String(s.tripPacks || 0))+' • Binders '+esc(String(s.binders || 0))+' • Receipts '+esc(String(s.receipts || 0))+'
'+esc(latest.note || '')+'
') : 'No operator command brief saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorCommandBriefs = readBriefs; window.readRoutexOperatorCommandBriefOutbox = readOutbox; window.saveRoutexOperatorCommandBrief = saveBrief; })(); '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

Operator launch board

' + (latest ? ('
'+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
'+esc(latest.note || '')+'
Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); `; const fname = sanitizeFileName(`proof_packet_${r.date||dayISO()}_${r.name}.html`); downloadText(packet, fname, "text/html"); toast(includePhotos ? `Proof packet generated (${Math.round(totalBytes/1024/1024)} MB photos embedded).` : "Proof packet generated (no photos).", "good", 4200); } async function viewExport(){ setPage("Export", "Logs / backups"); setPrimary("Export CSV", ()=> exportAllRoutesCSV(), ``); const routes = APP.cached.routes; const stops = APP.cached.stops; const photos = APP.cached.photos; const sizeMB = await estimateDBSizeMB(); const tips = `
Exports are local downloads (CSV/JSON/HTML). For a sellable workflow: route logs + proof packets = client-grade audit trail.
`; $("#content").innerHTML = `

Export route logs

${tips}
Vault storage estimate: ${sizeMB.toFixed(1)} MB • Routes: ${routes.length} • Stops: ${stops.length} • Photos: ${photos.length} • Vault docs: ${readVaultDocs().length} • Open tasks: ${readRouteTasks().filter(task => task.status !== 'done').length} • Templates: ${readRouteTemplates().length} • Inventory items: ${readInventoryCatalog().length} • Today miles: ${routeKPIs().milesToday}

Proof Packet

Generate a client-ready proof deliverable (HTML). This can be your core “sellable” artifact.
`; $("#ex_csv").onclick = exportAllRoutesCSV; $("#ex_econ").onclick = exportEconomicsCSV; $("#ex_day_ledger").onclick = ()=> openDayLedgerModal(dayISO()); $("#ex_json").onclick = ()=> exportBackupJSON(false); $("#ex_json_ph").onclick = ()=> exportBackupJSON(true); if($("#ex_json_enc")) $("#ex_json_enc").onclick = ()=> exportBackupJSON(true, true); $("#ex_import").onclick = openImportModal; $("#ex_tasks").onclick = exportTasksCSV; $("#ex_inventory").onclick = exportInventoryCSV; if($("#ex_reorder")) $("#ex_reorder").onclick = exportReorderCSV; if($("#ex_route_pack")) $("#ex_route_pack").onclick = openRoutePackExportModal; if($("#ex_route_pack_import")) $("#ex_route_pack_import").onclick = openRoutePackImportModal; $("#ex_packet").onclick = openProofPacketModal; } async function exportAllRoutesCSV(){ const routes = APP.cached.routes; const stops = APP.cached.stops; const header = [ "route_id","route_name","route_date","route_status","driver","vehicle","route_vehicle_profile_id","route_territory","route_notes","start_odometer","end_odometer","route_miles","route_revenue","route_fuel_gallons","route_fuel_total","route_fuel_price_per_gallon","route_fuel_station","route_misc_expense","route_material_total","route_collection_total","route_balance_due_total","route_estimated_fuel_used","route_fuel_delta","route_sla_overdue","route_sla_due_soon","route_total_cost","route_net","route_cost_per_stop","route_cost_per_productive_stop","route_total_minutes","route_active_minutes","route_drive_minutes","route_dwell_minutes","route_service_minutes","route_break_minutes","route_pause_minutes","route_phase","route_pause_count","route_break_count","route_odometer_warning","route_fuel_entry_count","route_expense_entry_count","route_material_entry_count","route_collection_entry_count","route_closeout_at","route_closeout_notes", "stop_seq","stop_id","stop_label","stop_address","stop_exact_address","stop_address_mode","stop_location_notes","stop_access_notes","stop_window_start","stop_window_end","stop_window_status","stop_window_actual_at","stop_contact","stop_phone","stop_business_email","stop_website","stop_source","stop_source_account_id","stop_service_area","stop_route_hint","stop_territories","stop_account_status","stop_ae_name","stop_tags","stop_status","stop_attempt_count","stop_successful_visit_count","stop_arrival_odometer","stop_departure_odometer","stop_leg_miles","stop_on_site_miles","stop_dwell_minutes","stop_service_minutes","account_attempt_count","account_successful_visit_count","stop_created_at","stop_arrived_at","stop_delivered_at","stop_completed_at","stop_outcome_note","stop_notes", "photo_count" ]; const rows = [header.join(",")]; for(const r of routes){ const ss = stopsForRoute(r.id); const metrics = computeRouteMetrics(r); if(!ss.length){ rows.push(csvRow([ r.id, r.name, r.date, r.status, r.driver, r.vehicle, r.vehicleProfileId, r.territory, r.routeNotes, r.startOdo, r.endOdo, metrics.miles, metrics.revenue, r.fuelGallons, metrics.fuelTotal, r.fuelPricePerGallon, r.fuelStation, r.miscExpense, metrics.materialTotal, metrics.collectionTotal, metrics.balanceDueTotal, metrics.estimatedFuelUsed, metrics.fuelDelta, metrics.slaOverdue, metrics.slaDueSoon, metrics.totalCost, metrics.net, metrics.costPerStop, metrics.costPerProductiveStop, metrics.totalRouteMinutes, metrics.activeRouteMinutes, metrics.driveMinutes, metrics.dwellMinutes, metrics.serviceMinutes, metrics.breakMinutes, metrics.pauseMinutes, metrics.routePhase, metrics.pauseCount, metrics.breakCount, metrics.odometerWarning, Array.isArray(r.fuelEntries) ? r.fuelEntries.length : 0, Array.isArray(r.expenseEntries) ? r.expenseEntries.length : 0, Array.isArray(r.materialEntries) ? r.materialEntries.length : 0, Array.isArray(r.collectionEntries) ? r.collectionEntries.length : 0, r.closeoutAt, r.closeoutNotes, "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 0 ])); }else{ for(const s of ss){ const pc = photosForStop(s.id).length; rows.push(csvRow([ r.id, r.name, r.date, r.status, r.driver, r.vehicle, r.vehicleProfileId, r.territory, r.routeNotes, r.startOdo, r.endOdo, metrics.miles, metrics.revenue, r.fuelGallons, metrics.fuelTotal, r.fuelPricePerGallon, r.fuelStation, r.miscExpense, metrics.materialTotal, metrics.collectionTotal, metrics.balanceDueTotal, metrics.estimatedFuelUsed, metrics.fuelDelta, metrics.slaOverdue, metrics.slaDueSoon, metrics.totalCost, metrics.net, metrics.costPerStop, metrics.costPerProductiveStop, metrics.totalRouteMinutes, metrics.activeRouteMinutes, metrics.driveMinutes, metrics.dwellMinutes, metrics.serviceMinutes, metrics.breakMinutes, metrics.pauseMinutes, metrics.routePhase, metrics.pauseCount, metrics.breakCount, metrics.odometerWarning, Array.isArray(r.fuelEntries) ? r.fuelEntries.length : 0, Array.isArray(r.expenseEntries) ? r.expenseEntries.length : 0, Array.isArray(r.materialEntries) ? r.materialEntries.length : 0, Array.isArray(r.collectionEntries) ? r.collectionEntries.length : 0, r.closeoutAt, r.closeoutNotes, s.seq, s.id, s.label, s.address, s.exactAddress, s.addressMode, s.locationNotes, s.accessNotes, s.appointmentWindowStart, s.appointmentWindowEnd, getStopWindowTiming(s).label, getStopWindowTiming(s).actualAt, s.contact, s.phone, s.businessEmail, s.website, s.source, s.sourceAccountId, s.serviceArea, s.routeHint, Array.isArray(s.territoryZones) ? s.territoryZones.join("|") : "", s.accountStatus, s.aeName, Array.isArray(s.tags) ? s.tags.join("|") : "", s.status, s.attemptCount, s.successfulVisitCount, s.arrivedOdo, s.departureOdo, getStopLegMiles(r.id, s.id), getStopOnSiteMiles(s), getStopDwellMinutes(s), getStopServiceMinutes(s), getAccountAttemptStats({ id: s.sourceAccountId, business_email: s.businessEmail }).attempts, getAccountAttemptStats({ id: s.sourceAccountId, business_email: s.businessEmail }).successful, s.createdAt, s.arrivedAt, s.deliveredAt, s.completedAt, s.outcomeNote, s.notes, pc ])); } } } const csv = rows.join("\n"); downloadText(csv, `skye_route_logs_${dayISO()}.csv`, "text/csv"); toast("CSV exported.", "good"); } async function exportEconomicsCSV(){ const header = [ "record_type","route_id","route_name","route_date","route_status","driver","vehicle","route_vehicle_profile_id","route_territory", "entry_id","entry_at","entry_category","entry_total","entry_gallons","entry_price_per_gallon","entry_station","entry_note","receipt_count","entry_qty","entry_unit","entry_balance_due","entry_invoice_ref", "stop_id","stop_label","source_account_id","business_email","route_closeout_at" ]; const rows = [header.join(",")]; for(const route of APP.cached.routes){ const stops = stopsForRoute(route.id); const stopMap = new Map(stops.map(stop => [stop.id, stop])); const fuelEntries = normalizeFuelEntries(route.fuelEntries); const expenseEntries = normalizeExpenseEntries(route.expenseEntries); const materialEntries = normalizeMaterialEntries(route.materialEntries); const collectionEntries = normalizeCollectionEntries(route.collectionEntries); for(const entry of fuelEntries){ rows.push(csvRow([ "fuel", route.id, route.name, route.date, route.status, route.driver, route.vehicle, route.vehicleProfileId, route.territory, entry.id, entry.at, "fuel", entry.total, entry.gallons, entry.pricePerGallon, entry.station, entry.note, Array.isArray(entry.receipts) ? entry.receipts.length : 0, "", "", "", "", "", "", "", "", route.closeoutAt ])); } for(const entry of expenseEntries){ const stop = stopMap.get(entry.stopId); rows.push(csvRow([ "expense", route.id, route.name, route.date, route.status, route.driver, route.vehicle, route.vehicleProfileId, route.territory, entry.id, entry.at, entry.category, entry.total, "", "", "", entry.note, Array.isArray(entry.receipts) ? entry.receipts.length : 0, "", "", "", "", entry.stopId, entry.stopLabel || (stop && stop.label) || "", entry.sourceAccountId || (stop && stop.sourceAccountId) || "", (stop && stop.businessEmail) || "", route.closeoutAt ])); } for(const entry of materialEntries){ const stop = stopMap.get(entry.stopId); rows.push(csvRow([ "material", route.id, route.name, route.date, route.status, route.driver, route.vehicle, route.vehicleProfileId, route.territory, entry.id, entry.at, entry.itemName, entry.totalCost, "", "", "", entry.note, 0, entry.qty, entry.unit, "", "", entry.stopId, entry.stopLabel || (stop && stop.label) || "", entry.sourceAccountId || (stop && stop.sourceAccountId) || "", (stop && stop.businessEmail) || "", route.closeoutAt ])); } for(const entry of collectionEntries){ const stop = stopMap.get(entry.stopId); rows.push(csvRow([ "collection", route.id, route.name, route.date, route.status, route.driver, route.vehicle, route.vehicleProfileId, route.territory, entry.id, entry.at, entry.method, entry.amount, "", "", "", entry.note, 0, "", "", entry.balanceDue, entry.invoiceRef, entry.stopId, entry.stopLabel || (stop && stop.label) || "", entry.sourceAccountId || (stop && stop.sourceAccountId) || "", (stop && stop.businessEmail) || "", route.closeoutAt ])); } } downloadText(rows.join("\n"), `skye_route_economics_${dayISO()}.csv`, "text/csv"); toast("Economics CSV exported.", "good"); } function exportTasksCSV(){ const header = ["task_id","business_name","business_email","task_type","task_status","due_date","owner","route_id","route_name","route_date","stop_id","stop_label","note","created_at","updated_at","completed_at"]; const rows = [header.join(',')]; readRouteTasks().forEach(task=>{ rows.push(csvRow([task.id, task.businessName, task.businessEmail, task.typeLabel, task.status, task.dueDate, task.owner, task.routeId, task.routeName, task.routeDate, task.stopId, task.stopLabel, task.note, task.createdAt, task.updatedAt, task.completedAt])); }); downloadText(rows.join("\n"), `skye_route_tasks_${dayISO()}.csv`, 'text/csv'); toast('Tasks CSV exported.', 'good'); } function exportInventoryCSV(){ const header = ["item_id","name","sku","unit","qty_on_hand","unit_cost","reorder_level","territory","notes","updated_at"]; const rows = [header.join(",")]; readInventoryCatalog().forEach(item=> rows.push(csvRow([item.id, item.name, item.sku, item.unit, item.qtyOnHand, item.unitCost, item.reorderLevel, item.territory, item.notes, item.updatedAt]))); downloadText(rows.join("\n"), `skye_inventory_${dayISO()}.csv`, "text/csv"); toast("Inventory CSV exported.", "good"); } function analyticsRowsHTML(rows, mode){ return rows.map(row=> `
${escapeHTML(row.name)} Score ${Math.round(row.avgScore || 0)}
Routes: ${row.completedRoutes}/${row.routes} completed • Stops: ${row.completedStops}/${row.stops} completed • Delivered: ${row.deliveredStops}
Miles: ${row.miles} • Fuel: ${fmtMoney(row.fuelCost)} • Revenue: ${fmtMoney(row.revenue)} • Net: ${fmtMoney(row.net)}
Follow-ups: ${row.followups} • Task creates: ${row.taskCreates} • On-site: ${row.hoursOnSite} hr • Cost/productive stop: ${fmtMoney(row.costPerProductiveStop)} ${mode === 'territory' ? `
Revisit need: ${row.revisitNeed} • Balance due: ${fmtMoney(row.balanceDue)}` : ``}
`).join(''); } function openDriverAnalyticsModal(){ const analytics = buildPerformanceAnalytics(); const rows = analytics.drivers; const totalHours = round2(rows.reduce((sum, row)=> sum + num(row.hoursOnSite), 0)); openModal( 'Driver / AE analytics', `
Fully local rollup from stored routes, stops, closeouts, and follow-up tasks.
${rows.length}
Drivers / AEs
${rows.reduce((sum, row)=> sum + row.completedRoutes, 0)}
Completed routes
${rows.reduce((sum, row)=> sum + row.completedStops, 0)}
Completed stops
${totalHours}
On-site hours
${analyticsRowsHTML(rows, 'driver') || `
No driver analytics yet.
`}
`, `` ); } function openVehicleAnalyticsModal(){ const analytics = buildPerformanceAnalytics(); const rows = analytics.vehicles; openModal( 'Vehicle analytics', `
Local performance by saved vehicle label/profile, including miles, spend, and route score.
${analyticsRowsHTML(rows, 'vehicle') || `
No vehicle analytics yet.
`}
`, `` ); } function openTerritoryAnalyticsModal(){ const analytics = buildPerformanceAnalytics(); const rows = analytics.territories; openModal( 'Territory analytics', `
Track territory performance by stops, completions, revenue, cost, revisit need, and balances due.
${analyticsRowsHTML(rows, 'territory') || `
No territory analytics yet.
`}
`, `` ); } function openReorderListModal(){ const rows = buildInventoryReorderList(); const totalCost = round2(rows.reduce((sum, row)=> sum + num(row.estimatedRestockCost), 0)); openModal( 'Inventory reorder list', `
Low-stock offline purchase list built from your saved inventory catalog and reorder levels.
${rows.length}
Items to restock
${fmtMoney(totalCost)}
Estimated restock cost
${rows.map(item=> `
${escapeHTML(item.name)} ${item.status === 'out' ? 'Out' : 'Low'}
On hand: ${item.qtyOnHand} ${escapeHTML(item.unit || 'ea')} • Reorder level: ${item.reorderLevel} • Need: ${item.neededQty} • Est. cost: ${fmtMoney(item.estimatedRestockCost)}${item.territory ? ` • Territory: ${escapeHTML(item.territory)}` : ''}
`).join('') || `
No items are currently below reorder level.
`}
`, `` ); if($("#reorder_export")) $("#reorder_export").onclick = ()=> exportReorderCSV(); } function exportReorderCSV(){ const rows = buildInventoryReorderList(); const header = ['item_id','name','sku','unit','qty_on_hand','reorder_level','needed_qty','unit_cost','estimated_restock_cost','territory','status','notes','updated_at']; const out = [header.join(',')]; rows.forEach(item=> out.push(csvRow([item.id, item.name, item.sku, item.unit, item.qtyOnHand, item.reorderLevel, item.neededQty, item.unitCost, item.estimatedRestockCost, item.territory, item.status, item.notes, item.updatedAt]))); downloadText(out.join('\n'), `skye_inventory_reorder_${dayISO()}.csv`, 'text/csv'); toast('Reorder CSV exported.', 'good'); } function collectRoutePackData(routeIds){ const idSet = new Set((routeIds || []).map(cleanStr).filter(Boolean)); const routes = APP.cached.routes.filter(route => idSet.has(route.id)); const stops = APP.cached.stops.filter(stop => idSet.has(stop.routeId)); const stopIds = new Set(stops.map(stop => stop.id)); const tasks = readRouteTasks().filter(task => idSet.has(cleanStr(task.routeId)) || stopIds.has(cleanStr(task.stopId))); const photos = APP.cached.photos.filter(photo => stopIds.has(cleanStr(photo.stopId)) || idSet.has(cleanStr(photo.routeId))); return { routes, stops, tasks, photos }; } async function exportRoutePackJSON(routeIds, includePhotos){ const pack = collectRoutePackData(routeIds); if(!pack.routes.length){ toast('Select at least one route.', 'warn'); return; } const photoPayload = []; if(includePhotos){ for(const photo of pack.photos){ if(!photo.blob) continue; const dataUrl = await blobToDataURL(photo.blob); photoPayload.push({ id: photo.id, routeId: photo.routeId, stopId: photo.stopId, name: photo.name, type: photo.type, size: photo.size || photo.blob.size, createdAt: photo.createdAt, dataUrl }); } } const data = { type: 'skye-route-pack-v1', exportedAt: nowISO(), includePhotos: !!includePhotos, routes: pack.routes, stops: pack.stops, photos: includePhotos ? photoPayload : [], tasks: pack.tasks }; const label = pack.routes.length === 1 ? sanitizeFileName(pack.routes[0].name || 'route_pack') : `route_pack_${pack.routes.length}_routes`; const packetText = JSON.stringify(data, null, 2); if(!applyImageStorageWarning(packetText.length, 'Route pack')) return; downloadText(packetText, `${label}_${dayISO()}.json`, 'application/json'); toast('Route pack exported.', 'good'); } function openRoutePackExportModal(){ const routes = APP.cached.routes.slice().sort((a,b)=> `${cleanStr(b.date)}${cleanStr(b.updatedAt)}`.localeCompare(`${cleanStr(a.date)}${cleanStr(a.updatedAt)}`)); openModal( 'Route pack export', `
Export selected routes, stops, linked proof photos, and route-linked tasks as an offline transfer pack.
${routes.map(route=>{ const metrics = computeRouteMetrics(route); return ``; }).join('') || `
No routes available yet.
`}
`, `` ); if($("#rp_select_all")) $("#rp_select_all").onclick = ()=> $$('[data-route-pack]').forEach(box=> box.checked = true); if($("#rp_clear_all")) $("#rp_clear_all").onclick = ()=> $$('[data-route-pack]').forEach(box=> box.checked = false); if($("#rp_export_go")) $("#rp_export_go").onclick = async ()=>{ const ids = $$('[data-route-pack]:checked').map(box => box.getAttribute('data-route-pack')); await exportRoutePackJSON(ids, !!$("#rp_include_photos")?.checked); closeModal(); }; } function openRoutePackImportModal(){ openModal( 'Import route pack', `
Import a route transfer pack exported from this app. Imported routes are added as new local records so the current device history stays intact.
`, `` ); if($("#rp_import_go")) $("#rp_import_go").onclick = async ()=>{ const inp = $("#rp_import_file"); if(!inp.files || !inp.files.length){ toast('Choose a file.', 'warn'); return; } let data; if(inp.files[0].size > LARGE_IMPORT_WARN_BYTES){ const ok = confirm(`This route pack is about ${Math.round(inp.files[0].size/1024/1024)} MB. Continue?`); if(!ok) return; } try{ data = JSON.parse(await inp.files[0].text()); }catch{ toast('Invalid JSON.', 'bad'); return; } await importRoutePack(data); closeModal(); render(); }; } async function importRoutePack(data){ if(!data || cleanStr(data.type) !== 'skye-route-pack-v1' || !Array.isArray(data.routes) || !Array.isArray(data.stops)){ toast('Route pack format not recognized.', 'bad'); return; } const existingKeys = new Set(APP.cached.routes.map(route => `${cleanStr(route.date)}|${cleanStr(route.name).toLowerCase()}`)); const duplicateCount = data.routes.filter(route => existingKeys.has(`${cleanStr(route.date)}|${cleanStr(route.name).toLowerCase()}`)).length; if(duplicateCount){ const ok = confirm(`${duplicateCount} imported route${duplicateCount===1?'':'s'} match an existing route name/date on this device. Continue and add imported copies anyway?`); if(!ok) return; } const routeIdMap = new Map(); const stopIdMap = new Map(); for(const route of data.routes){ const nextId = uid(); routeIdMap.set(route.id, nextId); const importedEvent = { id: uid(), type: 'imported-route-pack', summary: 'Imported from route pack', at: nowISO(), meta: { sourceRouteId: cleanStr(route.id), importedAt: nowISO() } }; const eventLog = [importedEvent, ...(Array.isArray(route.eventLog) ? route.eventLog : [])].slice(0, 250); await put(APP.db, 'routes', { ...route, id: nextId, eventLog, updatedAt: nowISO() }); } for(const stop of data.stops){ const nextId = uid(); stopIdMap.set(stop.id, nextId); await put(APP.db, 'stops', { ...stop, id: nextId, routeId: routeIdMap.get(stop.routeId) || stop.routeId, updatedAt: nowISO() }); } if(Array.isArray(data.photos)){ for(const photo of data.photos){ if(!photo.dataUrl) continue; const blob = dataURLToBlob(photo.dataUrl); await put(APP.db, 'photos', { id: uid(), routeId: routeIdMap.get(photo.routeId) || photo.routeId, stopId: stopIdMap.get(photo.stopId) || photo.stopId, name: photo.name, type: photo.type, size: photo.size || blob.size, blob, createdAt: photo.createdAt || nowISO() }); } } if(Array.isArray(data.tasks)){ const importedTasks = data.tasks.map(task => normalizeRouteTask({ ...task, id: uid(), routeId: routeIdMap.get(task.routeId) || task.routeId, stopId: stopIdMap.get(task.stopId) || task.stopId, updatedAt: nowISO(), createdAt: task.createdAt || nowISO() })); writeRouteTasks([...importedTasks, ...readRouteTasks()].slice(0, 1000)); } toast('Route pack import complete.', 'good'); await refreshCache(); } function openDayLedgerModal(defaultDate){ openModal( "Day Ledger Export", `
Generate a printable offline HTML ledger for one day of route work.
`, ` ` ); $("#dl_make").onclick = async ()=>{ await generateDayLedgerHTML($("#dl_date").value || dayISO()); closeModal(); }; } async function generateDayLedgerHTML(date){ const ledger = buildDayLedger(date); if(!ledger.routes.length){ toast("No routes found for that date.", "warn"); return; } const routeBlocks = ledger.routes.map(row=>{ const route = row.route; const snapshot = row.snapshot; const unresolvedList = row.unresolvedStops.concat(row.followups.filter(stop => !row.unresolvedStops.find(item => item.id === stop.id))); return `
${escapeHTML(route.name)}
Status: ${escapeHTML(route.status || 'planned')} • Driver: ${escapeHTML(route.driver || '—')} • Vehicle: ${escapeHTML(route.vehicle || '—')}${route.routeNotes ? ` • Route notes: ${escapeHTML(route.routeNotes)}` : ''}
Closed: ${fmt(route.closeoutAt)}
Stops: ${snapshot.stopsAttempted} attempted • ${snapshot.stopsCompleted} completed • ${snapshot.stopsDelivered} delivered • ${snapshot.followupStops} follow-up
Miles: ${snapshot.miles} • Fuel: ${fmtMoney(snapshot.fuelTotal)} • Materials: ${fmtMoney(snapshot.materialTotal || 0)} • Collections: ${fmtMoney(snapshot.collectionTotal || 0)} • Due: ${fmtMoney(snapshot.balanceDueTotal || 0)}
Total cost: ${fmtMoney(snapshot.totalCost)} • Revenue: ${fmtMoney(snapshot.revenue)} • Net: ${fmtMoney(snapshot.net)}${snapshot.estimatedFuelUsed ? ` • Est. fuel: ${snapshot.estimatedFuelUsed} gal${snapshot.fuelDelta ? ` • Delta: ${snapshot.fuelDelta > 0 ? '+' : ''}${snapshot.fuelDelta}` : ``}` : ``}
Route time: ${snapshot.totalRouteMinutes || 0} min • Drive: ${snapshot.driveMinutes || 0} min • Dwell: ${snapshot.dwellMinutes || 0} min • Service: ${snapshot.serviceMinutes || 0} min • Break: ${snapshot.breakMinutes || 0} min • Pause: ${snapshot.pauseMinutes || 0} min
Closeout notes: ${escapeHTML(route.closeoutNotes || '—')}
Unresolved / follow-up stops
${unresolvedList.length ? `` : `
No unresolved stops for this route.
`}
`; }).join("\n"); const html = ` Day Ledger — ${escapeHTML(ledger.date)}
Day Ledger
Date: ${escapeHTML(ledger.date)}
Routes run: ${ledger.summary.routes} • Stops attempted: ${ledger.summary.stopsAttempted} • Completed: ${ledger.summary.stopsCompleted} • Delivered: ${ledger.summary.stopsDelivered}
Miles: ${ledger.summary.miles} • Fuel cost: ${fmtMoney(ledger.summary.fuelTotal)} • Material cost: ${fmtMoney(ledger.summary.materialTotal)} • Collections: ${fmtMoney(ledger.summary.collectionTotal)} • Due: ${fmtMoney(ledger.summary.balanceDueTotal)}
Total cost: ${fmtMoney(ledger.summary.totalCost)} • Revenue: ${fmtMoney(ledger.summary.revenue)} • Net: ${fmtMoney(ledger.summary.net)}
Route time: ${ledger.summary.totalRouteMinutes} min • Drive: ${ledger.summary.driveMinutes} min • Dwell: ${ledger.summary.dwellMinutes} min • Service: ${ledger.summary.serviceMinutes} min • Break: ${ledger.summary.breakMinutes} min • Pause: ${ledger.summary.pauseMinutes} min
Unresolved stops: ${ledger.summary.unresolvedStops} • Follow-up stops: ${ledger.summary.followupStops}
Generated: ${fmt(nowISO())} • Skye Route & Dispatch Vault
${ledger.summary.routes}
Routes
${ledger.summary.miles}
Miles
${fmtMoney(ledger.summary.net)}
Net
${routeBlocks}
'; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

Operator launch board

' + (latest ? ('
'+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
'+esc(latest.note || '')+'
Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function exportLatestNoDeadProofHtml(){ const row = readRuns()[0]; if(!row) return toast('Run no-dead proof first.', 'warn'); downloadText(buildNoDeadProofHtml(row), 'routex_no_dead_button_proof_' + dayISO() + '.html', 'text/html'); toast('No-dead proof HTML exported.', 'good'); } function exportLatestNoDeadProofJson(){ const row = readRuns()[0]; if(!row) return toast('Run no-dead proof first.', 'warn'); downloadText(JSON.stringify(row, null, 2), 'routex_no_dead_button_proof_' + dayISO() + '.json', 'application/json'); toast('No-dead proof JSON exported.', 'good'); } function exportOperatorWorkbook(){ if(typeof window.buildOperatorClickSweepHtml !== 'function') return toast('Operator workbook helper is unavailable.', 'warn'); downloadText(window.buildOperatorClickSweepHtml(), 'routex_operator_click_sweep_' + dayISO() + '.html', 'text/html'); toast('Operator click-sweep workbook exported.', 'good'); } function openNoDeadProofManager(){ const rows = readRuns().map(item => '
'+esc(item.label || 'No-dead proof')+' '+esc(item.fingerprint || '—')+'
'+esc(fmt(item.createdAt || Date.now()))+' • '+esc(item.note || '')+'
').join('') || '
No no-dead-button proof runs saved yet.
'; const walk = summarizeWalkthrough(readWalkthroughs()[0] || null); openModal('No-dead-button proof', '
This stays locked on live action availability only: automated lane probes, directive action-registry sweep coverage, operator workbook export, and packaged human-walkthrough context.
Latest walkthrough: '+esc(String(walk.done || 0))+'/'+esc(String(walk.total || 0))+''+(walk.reviewer ? ' • reviewer '+esc(walk.reviewer)+'' : '')+' • Shared outbox '+esc(String(readOutbox().length))+'
'+rows+'
', ''); $('#ndb_run').onclick = async ()=>{ const btn = $('#ndb_run'); if(btn){ btn.disabled = true; btn.textContent = 'Running...'; } const row = await runNoDeadButtonProofComplete(); if(btn){ btn.disabled = false; btn.textContent = 'Run no-dead proof'; } toast(row.ok ? 'No-dead proof auto-sweep passed.' : 'No-dead proof needs review.', row.ok ? 'good' : 'warn'); if(typeof window.closeModal === 'function') window.closeModal(); openNoDeadProofManager(); }; $('#ndb_export_html').onclick = exportLatestNoDeadProofHtml; $('#ndb_export_json').onclick = exportLatestNoDeadProofJson; $('#ndb_export_workbook').onclick = exportOperatorWorkbook; $$('[data-ndb-html]').forEach(btn => btn.onclick = ()=>{ const row = readRuns().find(item => clean(item.id) === clean(btn.getAttribute('data-ndb-html'))); if(!row) return; downloadText(buildNoDeadProofHtml(row), 'routex_no_dead_button_proof_' + clean(row.fingerprint || dayISO()) + '.html', 'text/html'); toast('No-dead proof HTML exported.', 'good'); }); $$('[data-ndb-json]').forEach(btn => btn.onclick = ()=>{ const row = readRuns().find(item => clean(item.id) === clean(btn.getAttribute('data-ndb-json'))); if(!row) return; downloadText(JSON.stringify(row, null, 2), 'routex_no_dead_button_proof_' + clean(row.fingerprint || dayISO()) + '.json', 'application/json'); toast('No-dead proof JSON exported.', 'good'); }); } function injectNoDeadButtons(){ const footer = document.querySelector('#cc_capture') && document.querySelector('#cc_capture').parentNode; if(footer && !document.querySelector('#cc_no_dead_runs')){ const mgr = document.createElement('button'); mgr.className='btn'; mgr.id='cc_no_dead_runs'; mgr.textContent='No-dead proof'; mgr.onclick=()=> openNoDeadProofManager(); footer.insertBefore(mgr, document.querySelector('#cc_capture')); } if(footer && !document.querySelector('#cc_run_no_dead')){ const btn = document.createElement('button'); btn.className='btn'; btn.id='cc_run_no_dead'; btn.textContent='Run no-dead proof'; btn.onclick = async ()=>{ btn.disabled = true; btn.textContent = 'Running...'; const row = await runNoDeadButtonProofComplete(); btn.disabled = false; btn.textContent = 'Run no-dead proof'; toast(row.ok ? 'No-dead proof auto-sweep passed.' : 'No-dead proof needs review.', row.ok ? 'good' : 'warn'); try{ if(typeof window.closeModal === 'function') window.closeModal(); }catch(_){ } if(typeof window.openRoutexCompletionCenter === 'function') window.openRoutexCompletionCenter(); }; footer.insertBefore(btn, document.querySelector('#cc_capture')); } const row = document.querySelector('#pv_completion_center .row') || document.querySelector('#st_completion_center')?.parentNode; if(row && !document.querySelector('#pv_no_dead_manager')){ const btn = document.createElement('button'); btn.className='btn'; btn.id='pv_no_dead_manager'; btn.textContent='No-dead proof'; btn.onclick=()=> openNoDeadProofManager(); row.appendChild(btn); } } const prevOpen = window.openRoutexCompletionCenter; if(typeof prevOpen === 'function'){ window.openRoutexCompletionCenter = function(){ const out = prevOpen.apply(this, arguments); setTimeout(injectNoDeadButtons, 0); return out; }; } const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(injectNoDeadButtons, 0); return out; }; window.readRoutexNoDeadProofRuns = readRuns; window.openRoutexNoDeadProofManager = openNoDeadProofManager; window.runNoDeadButtonProofComplete = runNoDeadButtonProofComplete; })(); '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

Operator launch board

' + (latest ? ('
'+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
'+esc(latest.note || '')+'
Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function exportLatestCompareHtml(){ const row = readCompareRuns()[0]; if(!row) return toast('Run legacy proof first.', 'warn'); downloadText(buildCompareHtml(row), 'routex_actual_shipped_legacy_compare_' + dayISO() + '.html', 'text/html'); toast('Actual shipped legacy compare HTML exported.', 'good'); } function exportLatestCompareJson(){ const row = readCompareRuns()[0]; if(!row) return toast('Run legacy proof first.', 'warn'); downloadText(JSON.stringify(row, null, 2), 'routex_actual_shipped_legacy_compare_' + dayISO() + '.json', 'application/json'); toast('Actual shipped legacy compare JSON exported.', 'good'); } function readLegacyRuns(){ return readList(LEGACY_RUN_KEY, 120); } function saveLegacyRuns(rows){ saveList(LEGACY_RUN_KEY, rows, 120); } function mergeCompareIntoLatestLegacyRun(compare){ const runs = readLegacyRuns(); if(!runs.length) return null; const latest = runs[0] || {}; const updated = { ...latest, realShippedCompareId: compare.id, realShippedCompareFingerprint: compare.fingerprint, realShippedCompareOk: !!compare.ok, realShippedPackages: Number(compare.packageCount || 0), note: [clean(latest.note), clean(compare.note)].filter(Boolean).join(' • ') }; runs[0] = updated; saveLegacyRuns(runs); return updated; } function pushCompareBridgeRow(compare){ const bridge = { id: uid(), importedAt: nowISO(), label: 'Actual shipped legacy compare • ' + String(compare.packageCount || 0) + ' packages', source: 'routex-legacy-outbox', fingerprint: compare.fingerprint, lane: 'legacy-record-proof', routeCount: 0, stopCount: 0, docCount: 0, latestMatrixId: '', latestLaneProofId: clean(compare.id), note: clean(compare.note), proofStates: { legacy:true, restore:true, actualShippedPackage:true, actualShippedCompare:!!compare.ok }, matrixSummary: { packageCount: Number(compare.packageCount || 0), currentLegacyOk: !!compare.currentLegacyOk } }; upsertByFingerprint(LEGACY_INTAKE_KEY, bridge, 120); upsertByFingerprint(LEGACY_OUTBOX_KEY, { ...bridge, exportedAt: nowISO() }, 120); } const prevRunLegacy = window.runLegacyRecordProofComplete; if(typeof prevRunLegacy === 'function'){ window.runLegacyRecordProofComplete = async function(){ seedShippedLegacyCorpus(); const base = await prevRunLegacy.apply(this, arguments); const compare = saveCompareRun(buildCompareRow(base)); mergeCompareIntoLatestLegacyRun(compare); pushCompareBridgeRow(compare); toast(compare.ok ? 'Actual shipped legacy compare saved.' : 'Actual shipped legacy compare needs review.', compare.ok ? 'good' : 'warn'); const runs = readLegacyRuns(); return runs[0] || base; }; } function injectLegacyCorpusControls(){ const title = document.getElementById('modalTitle'); const body = document.getElementById('modalBody'); if(!title || !body || !/Legacy record proof/i.test(title.textContent || '')) return; if(document.getElementById('legacyActualShippedBlock')) return; const latest = readCompareRuns()[0] || null; const corpus = readCorpus(); const box = document.createElement('div'); box.id = 'legacyActualShippedBlock'; box.className = 'card'; box.style.marginTop = '12px'; box.innerHTML = '

Actual shipped package corpus

This legacy lane now seeds and compares against the actual shipped package manifests from v23–v26 in this conversation bundle, not only synthetic downgraded variants.
Corpus packages: '+esc(String(corpus.length))+''+(latest ? ' • Latest compare '+esc(latest.fingerprint || '—')+' • packages '+esc(String(latest.packageCount || 0))+'' : ' • No actual shipped compare run saved yet.')+'
'; body.appendChild(box); document.getElementById('legacySeedCorpusBtn').onclick = function(){ const result = seedShippedLegacyCorpus(); toast(result.merged ? 'Actual shipped legacy corpus seeded.' : 'Actual shipped legacy corpus already present.', result.merged ? 'good' : 'warn'); try{ injectLegacyCorpusControls(); }catch(_){} }; document.getElementById('legacyCompareHtmlBtn').onclick = exportLatestCompareHtml; document.getElementById('legacyCompareJsonBtn').onclick = exportLatestCompareJson; } const prevOpenLegacyManager = window.openLegacyProofRunnerManager; if(typeof prevOpenLegacyManager === 'function'){ window.openLegacyProofRunnerManager = function(){ seedShippedLegacyCorpus(); const out = prevOpenLegacyManager.apply(this, arguments); setTimeout(injectLegacyCorpusControls, 0); return out; }; } const prevRenderAll = window.renderAll; if(typeof prevRenderAll === 'function'){ window.renderAll = function(){ const out = prevRenderAll.apply(this, arguments); setTimeout(injectLegacyCorpusControls, 0); return out; }; } window.seedRoutexShippedLegacyCorpus = seedShippedLegacyCorpus; window.readRoutexShippedLegacyCorpus = readCorpus; window.readRoutexShippedLegacyCompareRuns = readCompareRuns; window.exportLatestRoutexShippedLegacyCompareHtml = exportLatestCompareHtml; window.exportLatestRoutexShippedLegacyCompareJson = exportLatestCompareJson; seedShippedLegacyCorpus(); })(); '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

Operator launch board

' + (latest ? ('
'+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
'+esc(latest.note || '')+'
Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function saveCompletionBinder(){ const row = pushBinder(buildCompletionBinderRow()); pushBinderOutbox(row); return row; } function exportLatestBinderHtml(){ const row = readBinders()[0] || saveCompletionBinder(); downloadText(buildCompletionBinderHtml(row), 'routex_no_dead_completion_binder_' + dayISO() + '.html', 'text/html'); toast('Completion binder HTML exported.', 'good'); } function exportLatestBinderJson(){ const row = readBinders()[0] || saveCompletionBinder(); downloadText(JSON.stringify(row, null, 2), 'routex_no_dead_completion_binder_' + dayISO() + '.json', 'application/json'); toast('Completion binder JSON exported.', 'good'); } function getWalkModal(){ const title = document.getElementById('modalTitle'); const body = document.getElementById('modalBody'); const footer = document.getElementById('modalFooter'); if(!title || !body || !footer || !/Human walkthrough/i.test(title.textContent || '')) return null; return { title, body, footer }; } function getWalkItems(){ const body = document.getElementById('modalBody'); return body ? Array.from(body.querySelectorAll('.list .item')) : []; } function getWalkNoteEl(index){ return document.querySelector('[data-walk-note="'+index+'"]'); } function getWalkDoneEl(index){ return document.querySelector('[data-walk-done="'+index+'"]'); } function appendWalkNote(index, text){ const el = getWalkNoteEl(index); if(!el) return; const next = clean(text); const cur = clean(el.value); el.value = cur ? (cur + '\n' + next) : next; } function markWalkDone(index, note){ const box = getWalkDoneEl(index); if(box) box.checked = true; if(note) appendWalkNote(index, note); } function ensureWalkRunNote(){ const el = document.getElementById('walk_note'); if(!el) return; if(clean(el.value)) return; el.value = 'Guided no-dead walkthrough closeout • ' + navigator.userAgent.slice(0,120) + ' • ' + dayISO(); } const launchers = { 0: { label:'Run fresh proof', run: async()=> window.runFreshRecordProofComplete && window.runFreshRecordProofComplete() }, 1: { label:'Run legacy proof', run: async()=> window.runLegacyRecordProofComplete && window.runLegacyRecordProofComplete() }, 2: { label:'Run export/import proof', run: async()=> window.runExportImportProofComplete && window.runExportImportProofComplete() }, 3: { label:'Run no-dead proof', run: async()=> window.runNoDeadButtonProofComplete && window.runNoDeadButtonProofComplete() }, 4: { label:'Run closure campaign', run: async()=> window.runDirectiveClosureCampaign && window.runDirectiveClosureCampaign() } }; async function runWalkLauncher(index){ const spec = launchers[index]; if(!spec || typeof spec.run !== 'function') return null; ensureWalkRunNote(); const out = await spec.run(); const fp = clean(out && out.fingerprint) || clean(out && out.bundle && out.bundle.fingerprint) || clean(out && out.id) || 'run'; const status = (out && Object.prototype.hasOwnProperty.call(out, 'ok')) ? (out.ok ? 'PASS' : 'REVIEW') : 'DONE'; markWalkDone(index, '[' + fmt(nowISO()) + '] ' + spec.label + ' • ' + status + ' • ' + fp); return out; } async function runGuidedWalkthrough(){ const btn = document.getElementById('walk_guided_run'); if(btn){ btn.disabled = true; btn.textContent = 'Running guided closeout...'; } let completed = 0; try{ for(const index of [0,1,2,3,4]){ if(!launchers[index]) continue; await runWalkLauncher(index); completed++; } appendWalkNote(5, '[' + fmt(nowISO()) + '] Completion binder will be saved into the AE FLOW import outbox when you save the walkthrough receipt.'); toast('Guided walkthrough closeout ran ' + completed + ' live proof actions.', 'good'); }catch(err){ toast(clean(err && err.message) || 'Guided walkthrough stopped for review.', 'warn'); }finally{ if(btn){ btn.disabled = false; btn.textContent = 'Run guided closeout'; } } } function injectGuidedWalkthroughControls(){ const modal = getWalkModal(); if(!modal) return; const { body, footer } = modal; const items = getWalkItems(); if(!document.getElementById('walk_v31_controls')){ const block = document.createElement('div'); block.id = 'walk_v31_controls'; block.className = 'card'; block.style.marginBottom = '12px'; block.innerHTML = '

Guided closeout

This turns the last no-dead line into an in-app guided operator run instead of a memory-based checklist. It launches the live proof actions, records receipts into the walkthrough notes, and can bind the finished walkthrough into a completion binder for AE FLOW import.
'; const list = body.querySelector('.list'); if(list) body.insertBefore(block, list); document.getElementById('walk_guided_run').onclick = runGuidedWalkthrough; document.getElementById('walk_binder_save').onclick = ()=>{ const row = saveCompletionBinder(); markWalkDone(5, '[' + fmt(nowISO()) + '] Completion binder saved • ' + clean(row.fingerprint)); const doneEl = getWalkDoneEl(5); if(doneEl) doneEl.checked = true; toast(row.ok ? 'Completion binder saved.' : 'Completion binder saved for review.', row.ok ? 'good' : 'warn'); }; document.getElementById('walk_binder_html').onclick = exportLatestBinderHtml; document.getElementById('walk_binder_json').onclick = exportLatestBinderJson; } items.forEach((item, index) => { if(item.querySelector('[data-v31-launch]')) return; const spec = launchers[index]; const host = document.createElement('div'); host.className = 'row'; host.style.flexWrap = 'wrap'; host.style.justifyContent = 'flex-end'; host.style.marginTop = '8px'; if(spec){ const btn = document.createElement('button'); btn.className = 'btn small'; btn.dataset.v31Launch = String(index); btn.textContent = spec.label; btn.onclick = async ()=>{ btn.disabled = true; const prev = btn.textContent; btn.textContent = 'Running...'; try{ await runWalkLauncher(index); toast(spec.label + ' logged into the walkthrough.', 'good'); }catch(err){ toast(clean(err && err.message) || 'Launcher failed.', 'warn'); } finally { btn.disabled = false; btn.textContent = prev; } }; host.appendChild(btn); } if(index === 5){ const btn = document.createElement('button'); btn.className = 'btn small'; btn.dataset.v31Launch = 'binder'; btn.textContent = 'Mark binder / AE FLOW handoff ready'; btn.onclick = ()=>{ const row = saveCompletionBinder(); markWalkDone(5, '[' + fmt(nowISO()) + '] Completion binder saved to Routex outbox • ' + clean(row.fingerprint)); const box = getWalkDoneEl(5); if(box) box.checked = true; toast(row.ok ? 'Binder handoff marked ready.' : 'Binder handoff saved for review.', row.ok ? 'good' : 'warn'); }; host.appendChild(btn); } if(host.childNodes.length) item.appendChild(host); }); if(!document.getElementById('walk_save_receipt_binder')){ const btn = document.createElement('button'); btn.className = 'btn'; btn.id = 'walk_save_receipt_binder'; btn.textContent = 'Save walkthrough + receipt + binder'; btn.onclick = ()=>{ ensureWalkRunNote(); markWalkDone(5, '[' + fmt(nowISO()) + '] Completion binder requested during walkthrough save.'); const saveBtn = document.getElementById('walk_save'); if(saveBtn) saveBtn.click(); setTimeout(()=>{ try{ const receipt = typeof window.saveRoutexNoDeadWalkthroughReceipt === 'function' ? window.saveRoutexNoDeadWalkthroughReceipt() : null; const binder = saveCompletionBinder(); toast((receipt && receipt.ok && binder.ok) ? 'Walkthrough, receipt, and binder saved.' : 'Walkthrough, receipt, and binder saved for review.', (receipt && receipt.ok && binder.ok) ? 'good' : 'warn'); }catch(err){ toast(clean(err && err.message) || 'Walkthrough save finished, but binder packaging needs review.', 'warn'); } }, 80); }; footer.insertBefore(btn, footer.firstChild); } } function injectBinderControls(){ const title = document.getElementById('modalTitle'); const body = document.getElementById('modalBody'); if(!title || !body || !/No-dead-button proof/i.test(title.textContent || '')) return; if(document.getElementById('ndbCompletionBinderBlock')) return; const latest = readBinders()[0] || null; const block = document.createElement('div'); block.id = 'ndbCompletionBinderBlock'; block.className = 'card'; block.style.marginTop = '12px'; block.innerHTML = '

Completion binder

This is the final sync-ready binder for the no-dead-button line. It packages the saved walkthrough receipt, latest no-dead proof run, shipped compare, completion snapshot, and attestation into one Routex artifact that AE FLOW can import directly.
Binders: '+esc(String(readBinders().length))+''+(latest ? ' • Latest '+esc(latest.fingerprint || '—')+' • '+(latest.ok ? 'PASS' : 'REVIEW')+' • reviewer '+esc(latest.walkthroughReviewer || '—')+'' : ' • No completion binder saved yet.')+'
'; body.appendChild(block); document.getElementById('ndbBinderSaveBtn').onclick = ()=>{ const row = saveCompletionBinder(); toast(row.ok ? 'Completion binder saved.' : 'Completion binder saved for review.', row.ok ? 'good' : 'warn'); }; document.getElementById('ndbBinderHtmlBtn').onclick = exportLatestBinderHtml; document.getElementById('ndbBinderJsonBtn').onclick = exportLatestBinderJson; } const observer = new MutationObserver(()=>{ injectGuidedWalkthroughControls(); injectBinderControls(); }); observer.observe(document.body, { childList:true, subtree:true }); const prevRun = window.runNoDeadButtonProofComplete; if(typeof prevRun === 'function'){ window.runNoDeadButtonProofComplete = async function(){ const out = await prevRun.apply(this, arguments); const receipt = latestReceipt(); if(receipt && receipt.ok){ try{ const binder = saveCompletionBinder(); toast(binder.ok ? 'Completion binder packaged.' : 'Completion binder saved for review.', binder.ok ? 'good' : 'warn'); }catch(_){ } } return out; }; } window.readRoutexNoDeadCompletionBinders = readBinders; window.readRoutexNoDeadCompletionBinderOutbox = readBinderOutbox; window.saveRoutexNoDeadCompletionBinder = saveCompletionBinder; })(); '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

Operator launch board

' + (latest ? ('
'+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
'+esc(latest.note || '')+'
Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function saveBrief(){ const row = buildRow(); pushBrief(row); pushOutbox(row); return row; } function exportLatestHtml(){ const row = readBriefs()[0] || saveBrief(); downloadText(buildHtml(row), 'routex_operator_command_brief_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBriefs()[0] || saveBrief(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_command_brief_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexOpsBriefSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexOpsBriefSaveBtn'; saveBtn.textContent='Save ops brief'; saveBtn.onclick = ()=>{ const row = saveBrief(); toast(row.ok ? 'Operator command brief saved.' : 'Operator command brief saved for review.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexOpsBriefHtmlBtn'; htmlBtn.textContent='Export ops brief HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexOpsBriefJsonBtn'; jsonBtn.textContent='Export ops brief JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const host = document.querySelector('#app') || document.body; const latest = readBriefs()[0] || null; const existing = document.getElementById('routexOpsBriefCard'); if(existing) existing.remove(); const card = document.createElement('div'); card.className = 'card'; card.id = 'routexOpsBriefCard'; const s = latest ? latest.snapshot || {} : snapshot(); card.innerHTML = '

Operator command brief

' + (latest ? ('
'+esc(latest.fingerprint || '—')+'Readiness '+esc(String(s.readiness || 0))+'/'+esc(String(s.readinessMax || 0))+''+(latest.ok ? 'PASS' : 'REVIEW')+'
Route packs '+esc(String(s.routePacks || 0))+' • Trip packs '+esc(String(s.tripPacks || 0))+' • Binders '+esc(String(s.binders || 0))+' • Receipts '+esc(String(s.receipts || 0))+'
'+esc(latest.note || '')+'
') : 'No operator command brief saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorCommandBriefs = readBriefs; window.readRoutexOperatorCommandBriefOutbox = readOutbox; window.saveRoutexOperatorCommandBrief = saveBrief; })(); '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

Operator launch board

' + (latest ? ('
'+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
'+esc(latest.note || '')+'
Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); `; downloadText(html, `skye_day_ledger_${ledger.date}.html`, "text/html"); toast("Day ledger exported.", "good"); } function csvRow(vals){ return vals.map(v=>{ const s = (v===null || v===undefined) ? "" : String(v); if(/[",\n]/.test(s)) return `"${s.replace(/"/g,'""')}"`; return s; }).join(","); } async function buildBackupPayload(includePhotos, scope){ const opts = scope && typeof scope === 'object' ? scope : {}; const routeIds = new Set(Array.isArray(opts.routeIds) ? opts.routeIds.map(cleanStr).filter(Boolean) : []); const scopedRoutes = routeIds.size ? APP.cached.routes.filter(route => routeIds.has(cleanStr(route.id))) : APP.cached.routes.slice(); const stopIds = new Set(APP.cached.stops.filter(stop => !routeIds.size || routeIds.has(cleanStr(stop.routeId))).map(stop => cleanStr(stop.id)).filter(Boolean)); const routes = scopedRoutes; const stops = APP.cached.stops.filter(stop => !routeIds.size || routeIds.has(cleanStr(stop.routeId))); const photos = APP.cached.photos.filter(photo => !routeIds.size || routeIds.has(cleanStr(photo.routeId)) || stopIds.has(cleanStr(photo.stopId))); const backup = { app: "Skye Route & Dispatch Vault", version: 2, exportedAt: nowISO(), includePhotos: !!includePhotos, routes, stops, photos: [], followupTasks: readRouteTasks().filter(task => !routeIds.size || routeIds.has(cleanStr(task.routeId)) || stopIds.has(cleanStr(task.stopId))), routeTemplates: readRouteTemplates(), inventoryCatalog: readInventoryCatalog(), docVault: readVaultDocs().filter(doc => !routeIds.size || routeIds.has(cleanStr(doc.routeId)) || stopIds.has(cleanStr(doc.stopId))), savedViews: readSavedViews(), localReminders: readLocalReminders().filter(rem => !routeIds.size || routeIds.has(cleanStr(rem.routeId)) || routeIds.has(cleanStr(rem.refId)) || stopIds.has(cleanStr(rem.stopId)) || stopIds.has(cleanStr(rem.refId))) }; if(includePhotos){ for(const p of photos){ const dataUrl = p.blob ? await blobToDataURL(p.blob) : ''; backup.photos.push({ id: p.id, routeId: p.routeId, stopId: p.stopId, name: p.name, type: p.type, size: p.size, createdAt: p.createdAt, dataUrl }); } }else{ backup.photos = photos.map(p=>({ id: p.id, routeId: p.routeId, stopId: p.stopId, name: p.name, type: p.type, size: p.size, createdAt: p.createdAt })); } return backup; } function buildRestoreCounts(){ return { routes: APP.cached.routes.length, stops: APP.cached.stops.length, docs: readVaultDocs().length, tasks: readRouteTasks().length, packs: readRoutePackIndex().length, trips: readTripPacks().length }; } async function runBackupRoundtripProof(lane){ const target = cleanStr(lane); let pair = null; if(['route-pack','service-summary','voice-note','pseudo-map-board','trip-pack'].includes(target)) pair = await ensureProofRouteAndStop(`Restore proof • ${target} • ${dayISO()}`, false); const routeIds = pair && pair.route ? [pair.route.id] : []; const backup = await buildBackupPayload(false, { routeIds }); if(!Array.isArray(backup.routes) || !backup.routes.length) return { ok:false, restore:false, note:'No backup payload could be built for restore proof.' }; const before = buildRestoreCounts(); await importBackup(backup); await refreshCache(); const after = buildRestoreCounts(); const ok = after.routes >= before.routes && after.stops >= before.stops && after.docs >= before.docs; return { ok, restore: ok, note:`Restore roundtrip ${ok ? 'passed' : 'needs review'} • routes ${before.routes}→${after.routes} • stops ${before.stops}→${after.stops} • docs ${before.docs}→${after.docs}` }; } async function runHistoricalRestoreLoopProof(lane){ const target = cleanStr(lane); const baseline = saveProofSnapshot(`Restore loop baseline • ${target || 'general'} • ${dayISO()}`); const freshNote = await seedValidationFixture(target, 'fresh'); await refreshCache(); const freshSnapshot = saveProofSnapshot(`Restore loop fresh • ${target || 'general'} • ${dayISO()}`); await importBackup(baseline.backup); await refreshCache(); const afterBaseline = buildRestoreCounts(); await importBackup(freshSnapshot.backup); await refreshCache(); const afterFresh = buildRestoreCounts(); const ok = afterBaseline.routes >= num(baseline.counts && baseline.counts.routes) && afterFresh.routes >= num(freshSnapshot.counts && freshSnapshot.counts.routes) && afterFresh.docs >= num(freshSnapshot.counts && freshSnapshot.counts.docs); const row = pushProofRestoreGeneration({ lane: target || 'general', proofKind: 'historical-restore-loop', baselineId: baseline.id, freshId: freshSnapshot.id, afterBaseline, afterFresh, freshNote, ok, note: `Baseline and fresh snapshots were restored sequentially for ${target || 'general'}.` }); return { ok, restore: ok, note:`Historical restore loop ${ok ? 'passed' : 'needs review'} • baseline routes ${afterBaseline.routes} • fresh routes ${afterFresh.routes} • log ${cleanStr(row.id)}` }; } function buildOperatorClickSweepHtml(focusLane){ const laneMap = { 'route-pack': ['Open export screen','Check route-pack export button','Open route-pack import modal','Verify import action + preview area'], 'service-summary': ['Open route detail','Open doc vault from a stop','Verify Service Summary and Delivery Confirmation actions'], 'account-code': ['Open lookup modal','Verify code input','Resolve payload/code to account'], 'voice-note': ['Open voice-note modal','Verify save control','Verify file fallback input'], 'heat-score': ['Open AE FLOW account lens','Sort by heat','Verify route detail heat summary'], 'pseudo-map-board': ['Open route detail','Open board modal','Verify save control and persisted note'], 'trip-pack': ['Open trip manager','Create/edit trip','Verify summary export and day rollups'] }; const lanes = focusLane ? [cleanStr(focusLane)] : Object.keys(laneMap); const sections = lanes.map(lane => { const items = (laneMap[lane] || []).map(item => `
  • Notes:
  • `).join(''); return `

    ${escapeHTML(lane)}

      ${items}
    `; }).join(''); return `Operator click sweep

    Operator click sweep

    Generated ${escapeHTML(fmt(nowISO()))}. Use this to manually walk the live UI and only mark directive items complete after the path survives fresh, reload, export/import, and restore checks.
    ${sections}
    \n '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); `; } async function runLaneActionProbe(lane){ const target = cleanStr(lane); let checks = []; const push = (label, ok)=> checks.push({ label, ok: !!ok }); const done = (extra)=> ({ ok: checks.every(item => item.ok), noDeadButtons: checks.every(item => item.ok), checks, note: `${target} action probe • ` + checks.map(item => `${item.label} ${item.ok ? 'ok' : 'missing'}`).join(' • ') + (extra ? ` • ${extra}` : '') }); if(target === 'route-pack'){ await viewExport(); push('export button', !!document.querySelector('#ex_route_pack')); push('import launcher', !!document.querySelector('#ex_route_pack_import')); openRoutePackImportModal(); push('import modal action', !!document.querySelector('#rp_import_go')); closeModal(); return done('Export/import controls rendered.'); } if(target === 'service-summary'){ const pair = await ensureProofRouteAndStop(`Action probe • ${target} • ${dayISO()}`, false); if(pair && pair.route){ await viewRouteDetail(pair.route); push('delivery confirmation button', !!document.querySelector('#routeConfirmBtn')); if(pair.stop){ openDocVaultModal(pair.route.id, pair.stop.id); push('service summary doc button', !!document.querySelector('#dv_service')); closeModal(); } } return done('Route detail and doc-vault controls rendered.'); } if(target === 'account-code'){ const pair = await ensureProofRouteAndStop(`Action probe • ${target} • ${dayISO()}`, false); if(pair && pair.route){ await viewRouteDetail(pair.route); push('lookup button', !!document.querySelector('#routeLookupBtn')); openLookupModal(); push('lookup modal input', !!document.querySelector('#lookup_input')); closeModal(); } return done('Lookup controls rendered.'); } if(target === 'voice-note'){ const pair = await ensureProofRouteAndStop(`Action probe • ${target} • ${dayISO()}`, false); if(pair && pair.route){ await viewRouteDetail(pair.route); push('voice button', !!document.querySelector(`[data-voice-stop="${pair.stop.id}"]`)); openVoiceNoteModal(pair.route.id, pair.stop.id); push('voice save', !!document.querySelector('#vn_save')); push('voice fallback input', !!document.querySelector('#vn_file')); closeModal(); } return done('Voice-note controls rendered.'); } if(target === 'heat-score'){ const heat = await runHeatScoreProof(); const route = APP.cached.routes.find(item => /Heat Proof/i.test(cleanStr(item.name || ''))); if(route){ await viewRouteDetail(route); } push('route heat summary', !!document.querySelector('#routeHeatSummary')); return done(heat && heat.note ? heat.note : 'Heat controls rendered.'); } if(target === 'pseudo-map-board'){ const pair = await ensureProofRouteAndStop(`Action probe • ${target} • ${dayISO()}`, false); if(pair && pair.route){ await viewRouteDetail(pair.route); push('board button', !!document.querySelector('#routeBoardBtn')); openPseudoMapBoardModal(pair.route.id); push('board save', !!document.querySelector('#board_save')); closeModal(); } return done('Pseudo-map controls rendered.'); } if(target === 'trip-pack'){ const pair = await ensureProofRouteAndStop(`Action probe • ${target} • ${dayISO()}`, false); if(pair && pair.route){ await viewRouteDetail(pair.route); push('trip button', !!document.querySelector('#routeTripBtn')); openTripPackManagerModal([pair.route.id]); push('trip save', !!document.querySelector('#trip_save')); closeModal(); } return done('Trip-pack controls rendered.'); } return { ok:false, noDeadButtons:false, checks, note:'No action probe available.' }; } function buildHybridGeocodePayload(routeId){ const route = APP.cached.routes.find(item => item.id === cleanStr(routeId)); if(!route) return null; const stops = stopsForRoute(route.id).map(stop => ({ stopId: stop.id, label: stop.label, address: stop.exactAddress || stop.address || stop.serviceArea || '', locationMode: stop.locationMode || inferLocationMode(stop), accountCode: stop.accountCode || deriveAccountCodeFromStop(stop) })); return { type:'skye-routex-hybrid-geocode-v1', routeId: route.id, routeName: route.name, exportedAt: nowISO(), stops }; } function queueHybridGeocodePayload(routeId){ const payload = buildHybridGeocodePayload(routeId); if(!payload) return null; const item = normalizeHybridQueueItem({ kind:'geocode-candidates', routeId: payload.routeId, label:`Geocode queue • ${payload.routeName}`, payload, note:'Offline geocode candidate export generated locally.' }); writeHybridGeocodeQueue([item, ...readHybridGeocodeQueue()].slice(0,120)); return item; } function queueHybridSyncSnapshot(routeId){ const route = APP.cached.routes.find(item => item.id === cleanStr(routeId)); if(!route) return null; const payload = { type:'skye-routex-hybrid-sync-snapshot-v1', routeId: route.id, routeName: route.name, exportedAt: nowISO(), route: hydrateRouteRecord(route), stops: stopsForRoute(route.id).map(hydrateStopRecord), docs: readVaultDocs().filter(doc => cleanStr(doc.routeId) === cleanStr(route.id)) }; const item = normalizeHybridQueueItem({ kind:'route-sync-snapshot', routeId: route.id, label:`Sync snapshot • ${route.name}`, payload, note:'Offline sync snapshot queued locally.' }); writeHybridSyncOutbox([item, ...readHybridSyncOutbox()].slice(0,120)); return item; } function exportOptionalHybridBundle(){ const payload = { type:'skye-routex-optional-hybrid-bundle-v1', exportedAt: nowISO(), plans: readOptionalHybridPlans(), geocodeQueue: readHybridGeocodeQueue(), syncOutbox: readHybridSyncOutbox() }; downloadText(JSON.stringify(payload, null, 2), `routex_optional_hybrid_${dayISO()}.json`, 'application/json'); toast('Optional hybrid bundle exported.', 'good'); } function importOptionalHybridBundle(data){ if(!data || cleanStr(data.type) !== 'skye-routex-optional-hybrid-bundle-v1') return { ok:false, note:'Hybrid bundle not recognized.' }; if(data.plans) writeOptionalHybridPlans(data.plans); if(Array.isArray(data.geocodeQueue)) writeHybridGeocodeQueue([...data.geocodeQueue.map(normalizeHybridQueueItem), ...readHybridGeocodeQueue()].slice(0,120)); if(Array.isArray(data.syncOutbox)) writeHybridSyncOutbox([...data.syncOutbox.map(normalizeHybridQueueItem), ...readHybridSyncOutbox()].slice(0,120)); return { ok:true, note:'Hybrid bundle imported.' }; } async function exportBackupJSON(includePhotos, encrypted=false){ const backup = await buildBackupPayload(includePhotos, {}); if(includePhotos) toast("Encoding photos… this can take a bit.", "warn", 4200); if(encrypted){ const passphrase = prompt('Enter a passphrase for this encrypted backup. You will need it to restore later.'); if(!cleanStr(passphrase)){ toast('Encrypted backup cancelled.', 'warn'); return; } const payload = await encryptBackupPayload(backup, passphrase); const encryptedText = JSON.stringify(payload, null, 2); if(!applyImageStorageWarning(encryptedText.length, 'Encrypted backup')) return; downloadText(encryptedText, `skye_backup_encrypted_${dayISO()}.json`, 'application/json'); toast('Encrypted backup exported.', 'good'); return; } const plainText = JSON.stringify(backup, null, 2); if(!applyImageStorageWarning(plainText.length, 'Backup')) return; const fname = includePhotos ? `skye_backup_with_photos_${dayISO()}.json` : `skye_backup_${dayISO()}.json`; downloadText(plainText, fname, "application/json"); toast("Backup exported.", "good"); } function blobToDataURL(blob){ return new Promise((resolve, reject)=>{ const fr = new FileReader(); fr.onload = ()=> resolve(fr.result); fr.onerror = ()=> reject(fr.error); fr.readAsDataURL(blob); }); } function openImportModal(){ openModal( "Import Backup", `
    Import a JSON backup exported from this app. Restore preview runs locally before merge. If the file is encrypted, enter the passphrase here first.
    Choose a backup file to preview what will be restored.
    `, ` ` ); const previewFile = async ()=>{ const inp = $("#im_file"); if(!inp.files || !inp.files.length) return; const file = inp.files[0]; if(file.size > LARGE_IMPORT_WARN_BYTES) $('#im_preview').innerHTML = `
    Large import
    ${escapeHTML(Math.round(file.size/1024/1024))} MB file selected.`; let data; try{ data = JSON.parse(await file.text()); }catch{ $('#im_preview').innerHTML = `
    Invalid JSON
    `; return; } if(cleanStr(data.type) === 'skye-encrypted-backup-v1'){ $('#im_preview').innerHTML = `
    Encrypted backup
    Enter the passphrase to restore this file. Exported: ${escapeHTML(fmt(data.exportedAt))}`; return; } const preview = buildRestorePreview(data); $('#im_preview').innerHTML = `
    ${preview.routes}
    Routes
    ${preview.stops}
    Stops
    ${preview.photos}
    Photos
    ${preview.docs}
    Vault docs
    Duplicate ids on this device — Routes: ${preview.routeDupes} • Stops: ${preview.stopDupes} • Photos: ${preview.photoDupes} • Docs: ${preview.docDupes}
    `; }; if($('#im_file')) $('#im_file').addEventListener('change', previewFile); $("#im_go").onclick = async ()=>{ const inp = $("#im_file"); if(!inp.files || !inp.files.length){ toast("Choose a file.", "warn"); return; } const file = inp.files[0]; if(file.size > LARGE_IMPORT_WARN_BYTES){ const ok = confirm(`This import file is about ${Math.round(file.size/1024/1024)} MB. Continue?`); if(!ok) return; } let data; try{ data = JSON.parse(await file.text()); }catch{ toast("Invalid JSON.", "bad"); return; } if(cleanStr(data.type) === 'skye-encrypted-backup-v1'){ const passphrase = cleanStr($('#im_passphrase').value); if(!passphrase){ toast('Enter the passphrase for this encrypted backup.', 'warn'); return; } try{ data = await decryptBackupPayload(data, passphrase); }catch(err){ toast('Unable to decrypt that backup.', 'bad'); return; } } await importBackup(data); closeModal(); render(); }; } async function importBackup(data){ if(!data || !Array.isArray(data.routes) || !Array.isArray(data.stops) || !Array.isArray(data.photos)){ toast("Backup format not recognized.", "bad"); return; } const existingRouteIds = new Set(APP.cached.routes.map(item => item.id)); const existingStopIds = new Set(APP.cached.stops.map(item => item.id)); const existingPhotoIds = new Set(APP.cached.photos.map(item => item.id)); const routeIdMap = new Map(); const stopIdMap = new Map(); const duplicateRoutes = data.routes.filter(route => existingRouteIds.has(route.id)); if(duplicateRoutes.length){ const ok = confirm(`${duplicateRoutes.length} route id duplicate${duplicateRoutes.length===1 ? '' : 's'} found in this backup. Restore as merged copies?`); if(!ok) return; } for(const r of data.routes){ if(!r.id) continue; const nextId = existingRouteIds.has(r.id) ? uid() : r.id; routeIdMap.set(r.id, nextId); existingRouteIds.add(nextId); await put(APP.db, "routes", { ...r, id: nextId, updatedAt: nowISO() }); } for(const s of data.stops){ if(!s.id) continue; const nextId = existingStopIds.has(s.id) ? uid() : s.id; stopIdMap.set(s.id, nextId); existingStopIds.add(nextId); await put(APP.db, "stops", { ...s, id: nextId, routeId: routeIdMap.get(s.routeId) || s.routeId, updatedAt: nowISO() }); } for(const p of data.photos){ if(!p.id || !p.dataUrl) continue; const blob = dataURLToBlob(p.dataUrl); const nextId = existingPhotoIds.has(p.id) ? uid() : p.id; existingPhotoIds.add(nextId); await put(APP.db, "photos", { id: nextId, routeId: routeIdMap.get(p.routeId) || p.routeId, stopId: stopIdMap.get(p.stopId) || p.stopId, name: p.name, type: p.type, size: p.size || blob.size, blob, createdAt: p.createdAt || nowISO() }); } if(Array.isArray(data.followupTasks)){ const mergedTasks = [...data.followupTasks.map(task => normalizeRouteTask({ ...task, id: readRouteTasks().find(existing => existing.id === task.id) ? uid() : task.id, routeId: routeIdMap.get(task.routeId) || task.routeId, stopId: stopIdMap.get(task.stopId) || task.stopId })), ...readRouteTasks()].reduce((acc,item)=>{ if(!acc.find(existing => existing.id === item.id)) acc.push(item); return acc; }, []); writeRouteTasks(mergedTasks); } if(Array.isArray(data.routeTemplates)) writeRouteTemplates([...data.routeTemplates.map(normalizeRouteTemplate), ...readRouteTemplates()].reduce((acc,item)=>{ if(!acc.find(existing => existing.id === item.id)) acc.push(item); return acc; }, [])); if(Array.isArray(data.inventoryCatalog)) writeInventoryCatalog([...data.inventoryCatalog.map(normalizeInventoryItem), ...readInventoryCatalog()].reduce((acc,item)=>{ if(!acc.find(existing => existing.id === item.id)) acc.push(item); return acc; }, [])); if(Array.isArray(data.docVault)){ const existingDocs = readVaultDocs(); const mergedDocs = [...data.docVault.map(doc => normalizeVaultDoc({ ...doc, id: existingDocs.find(existing => existing.id === doc.id) ? uid() : doc.id, routeId: routeIdMap.get(doc.routeId) || doc.routeId, stopId: stopIdMap.get(doc.stopId) || doc.stopId })), ...existingDocs].reduce((acc,item)=>{ if(!acc.find(existing => existing.id === item.id)) acc.push(item); return acc; }, []); writeVaultDocs(mergedDocs); } if(Array.isArray(data.savedViews)) writeSavedViews([...data.savedViews.map(normalizeSavedView), ...readSavedViews()].reduce((acc,item)=>{ if(!acc.find(existing => existing.id === item.id)) acc.push(item); return acc; }, [])); if(Array.isArray(data.localReminders)) writeLocalReminders([...data.localReminders.map(normalizeLocalReminder), ...readLocalReminders()].reduce((acc,item)=>{ if(!acc.find(existing => existing.id === item.id)) acc.push(item); return acc; }, [])); toast("Import complete.", "good"); await refreshCache(); } function dataURLToBlob(dataUrl){ const [meta, b64] = dataUrl.split(","); const mime = (meta.match(/data:(.*?);base64/)||[])[1] || "application/octet-stream"; const bin = atob(b64); const len = bin.length; const arr = new Uint8Array(len); for(let i=0;i sum + attachmentBytes(entry.receipts), 0); bytes += expenseEntries.reduce((sum, entry)=> sum + attachmentBytes(entry.receipts), 0); bytes += JSON.stringify(normalizeMaterialEntries(route.materialEntries)).length; bytes += JSON.stringify(normalizeCollectionEntries(route.collectionEntries)).length; } bytes += JSON.stringify(readInventoryCatalog()).length; // add overhead for routes/stops bytes += (APP.cached.routes.length*900) + (APP.cached.stops.length*900); return bytes / 1024 / 1024; } async function viewSettings(){ setPage("Settings", "PIN, branding, storage"); setPrimary("Save", ()=> saveSettings(), ``); const pinHash = await getSetting("pinHash", ""); const title = await getSetting("brandTitle", APP.brand.title); const subtitle = await getSetting("brandSubtitle", APP.brand.subtitle); const cosmos = await getSetting("prefCosmos", true); $("#content").innerHTML = `

    Branding

    These settings only affect this device build (useful for white-label deployments).

    Vault Gate (PIN)

    Set a PIN to require unlock on app launch. This is local-device security (not cloud auth).

    Vehicle profiles

    Saved locally for this device. These profiles can prefill route vehicle, driver, MPG, and maintenance notes.
    ${getVehicleProfiles().length} saved vehicle profile${getVehicleProfiles().length===1 ? '' : 's'}

    Inventory catalog

    Offline stock used by the materials lane. Track on-hand quantity, cost, and reorder level locally.
    ${readInventoryCatalog().length} saved inventory item${readInventoryCatalog().length===1 ? '' : 's'}

    Performance

    Disable the animated cosmos on low-power devices.

    Data

    This device only. Use Export → Backup before wiping.
    `; if($("#st_vehicleProfiles")) $("#st_vehicleProfiles").onclick = ()=> openVehicleProfilesModal(); if($("#st_vehicleAnalytics")) $("#st_vehicleAnalytics").onclick = ()=> openVehicleAnalyticsModal(); if($("#st_inventory")) $("#st_inventory").onclick = ()=> openInventoryCatalogModal(); if($("#st_reorder")) $("#st_reorder").onclick = ()=> openReorderListModal(); $("#st_backup").onclick = ()=>{ APP.view="export"; window.location.hash="export"; render(); }; $("#st_wipe").onclick = async ()=>{ const ok = confirm("Wipe ALL data on this device? This cannot be undone."); if(!ok) return; await wipeDB(); toast("Vault wiped.", "warn"); await refreshCache(); render(); }; $("#st_clearPin").onclick = async ()=>{ const ok = confirm("Clear PIN gate? Anyone using this device can open the app."); if(!ok) return; await setSetting("pinHash", ""); toast("PIN cleared.", "warn"); render(); }; } async function saveSettings(){ const title = ($("#st_title")?.value || APP.brand.title).trim() || APP.brand.title; const subtitle = ($("#st_sub")?.value || APP.brand.subtitle).trim() || APP.brand.subtitle; await setSetting("brandTitle", title); await setSetting("brandSubtitle", subtitle); const cosmos = !!$("#st_cosmos")?.checked; await setSetting("prefCosmos", cosmos); APP.prefs.cosmos = cosmos; const enablePin = !!$("#st_pinEnabled")?.checked; if(enablePin){ const p1 = ($("#st_pin1")?.value || "").trim(); const p2 = ($("#st_pin2")?.value || "").trim(); if(p1 || p2){ if(p1 !== p2){ toast("PINs do not match.", "bad"); return; } if(p1.length < 4){ toast("PIN must be at least 4 digits.", "warn"); return; } const h = await sha256(p1); await setSetting("pinHash", h); toast("PIN set.", "good"); }else{ // if enabling but no pin, keep existing if any const existing = await getSetting("pinHash", ""); if(!existing){ toast("Enter a new PIN to enable.", "warn"); return; } } }else{ await setSetting("pinHash", ""); toast("PIN disabled.", "warn"); } await loadBranding(); renderNav(); renderBrand(); toast("Settings saved.", "good"); } async function wipeDB(){ // clear object stores const t = tx(APP.db, ["routes","stops","photos"], "readwrite"); await Promise.all(["routes","stops","photos"].map(store=>{ return new Promise((resolve, reject)=>{ const r = t.objectStore(store).clear(); r.onsuccess = ()=> resolve(true); r.onerror = ()=> reject(r.error); }); })); localStorage.removeItem(ROUTEX_VEHICLES_KEY); localStorage.removeItem(ROUTEX_FOLLOWUP_TASKS_KEY); localStorage.removeItem(ROUTEX_ROUTE_TEMPLATES_KEY); localStorage.removeItem(ROUTEX_INVENTORY_CATALOG_KEY); localStorage.removeItem(ROUTEX_DOC_VAULT_KEY); localStorage.removeItem(ROUTEX_SAVED_VIEWS_KEY); localStorage.removeItem(ROUTEX_REMINDERS_KEY); } // ========= Download helpers ========= function downloadText(text, filename, mime="text/plain"){ const blob = new Blob([text], { type: mime }); downloadBlob(blob, filename); } function downloadBlob(blob, filename){ const a = document.createElement("a"); const url = URL.createObjectURL(blob); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); a.remove(); setTimeout(()=>{ try{ URL.revokeObjectURL(url); }catch{} }, 4000); } function sanitizeFileName(name){ return (name || "file").replace(/[<>:"/\\|?*\x00-\x1F]/g, "_").slice(0, 180); } // ========= Status + misc ========= function updateStatusLine(){ const online = navigator.onLine; $("#statusLine").textContent = online ? "Online • Offline-capable" : "Offline • Local vault active"; } window.addEventListener("online", ()=>{ updateStatusLine(); toast("Online.", "good"); }); window.addEventListener("offline", ()=>{ updateStatusLine(); toast("Offline mode.", "warn"); }); function escapeHTML(s){ return String(s ?? "").replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'"); } function escapeAttr(s){ return escapeHTML(s).replace(/"/g,"""); } async function loadBranding(){ APP.brand.title = await getSetting("brandTitle", APP.brand.title); APP.brand.subtitle = await getSetting("brandSubtitle", APP.brand.subtitle); $("#bootTitle").textContent = APP.brand.title; $("#brandTitle").textContent = APP.brand.title; $("#brandSub").textContent = APP.brand.subtitle; document.title = APP.brand.title; } function renderBrand(){ $("#brandTitle").textContent = APP.brand.title; $("#brandSub").textContent = APP.brand.subtitle; } // ========= NEW-SHIT2 DIRECTIVE-FIRST ENHANCEMENT BLOCK ========= const ROUTEX_ROUTE_PACK_INDEX_KEY = "skye_routex_route_pack_index_v2"; const ROUTEX_TRIP_PACKS_KEY = "skye_routex_trip_packs_v1"; const ROUTEX_PROOF_REGISTRY_KEY = "skye_routex_proof_registry_v1"; const ROUTEX_OPTIONAL_HYBRID_KEY = "skye_routex_optional_hybrid_v1"; const ROUTEX_LOOKUP_HISTORY_KEY = "skye_routex_lookup_history_v1"; const ROUTEX_AE_PACK_SEED_KEY = "skye_routex_ae_pack_seeds_v1"; const ROUTEX_PROOF_SNAPSHOTS_KEY = "skye_routex_proof_snapshots_v1"; const ROUTEX_ROUTE_PACK_TRANSFER_LOG_KEY = "skye_routex_route_pack_transfer_log_v1"; const ROUTEX_PROOF_RESTORE_GENERATIONS_KEY = "skye_routex_proof_restore_generations_v1"; const OUTCOME_DEFS = { arrived: { status:"arrived", label:"Arrived", requiresProof:false, requiresFollowupTask:false, countsAsAttempt:true, countsAsSuccess:true, countsAsDelivery:false }, delivered: { status:"delivered", label:"Delivered", requiresProof:true, requiresFollowupTask:false, countsAsAttempt:true, countsAsSuccess:true, countsAsDelivery:true }, no_answer: { status:"no-answer", label:"No answer", requiresProof:false, requiresFollowupTask:true, countsAsAttempt:true, countsAsSuccess:false, countsAsDelivery:false }, rescheduled: { status:"rescheduled", label:"Rescheduled", requiresProof:false, requiresFollowupTask:true, countsAsAttempt:true, countsAsSuccess:false, countsAsDelivery:false }, wrong_address: { status:"wrong-address", label:"Wrong address", requiresProof:false, requiresFollowupTask:true, countsAsAttempt:true, countsAsSuccess:false, countsAsDelivery:false }, gate_locked: { status:"gate-locked", label:"Gate locked", requiresProof:false, requiresFollowupTask:true, countsAsAttempt:true, countsAsSuccess:false, countsAsDelivery:false }, site_closed: { status:"site-closed", label:"Site closed", requiresProof:false, requiresFollowupTask:true, countsAsAttempt:true, countsAsSuccess:false, countsAsDelivery:false }, followup_needed: { status:"follow-up-needed", label:"Follow-up needed", requiresProof:false, requiresFollowupTask:true, countsAsAttempt:true, countsAsSuccess:false, countsAsDelivery:false }, not_qualified: { status:"not-qualified", label:"Not qualified", requiresProof:false, requiresFollowupTask:false, countsAsAttempt:true, countsAsSuccess:false, countsAsDelivery:false }, cancelled: { status:"cancelled", label:"Cancelled", requiresProof:false, requiresFollowupTask:false, countsAsAttempt:false, countsAsSuccess:false, countsAsDelivery:false }, pending: { status:"pending", label:"Pending", requiresProof:false, requiresFollowupTask:false, countsAsAttempt:false, countsAsSuccess:false, countsAsDelivery:false } }; const LOCATION_MODE_LABELS = { exact_address: 'Exact address', partial_address: 'Partial address', territory_fallback: 'Territory fallback', manual_stop_text: 'Manual stop text' }; function simpleHash36(input){ const str = cleanStr(input) || '0'; let h = 0; for(let i=0;i>> 0; return Math.abs(h >>> 0).toString(36).toUpperCase(); } function readPlainJSON(key, fallback){ try{ const raw = localStorage.getItem(key); return raw ? JSON.parse(raw) : fallback; }catch(_){ return fallback; } } function writePlainJSON(key, value){ try{ localStorage.setItem(key, JSON.stringify(value)); }catch(_){ } return value; } function uniqBy(items, keyFn){ const seen = new Set(); const out = []; (items || []).forEach(item => { const key = keyFn(item); if(seen.has(key)) return; seen.add(key); out.push(item); }); return out; } function deriveAccountCodeSeed(record){ const r = record && typeof record === 'object' ? record : {}; return [cleanStr(r.id || r.sourceAccountId), cleanStr(r.business_email || r.businessEmail).toLowerCase(), cleanStr(r.business_name || r.businessName || r.label).toLowerCase(), cleanStr(r.phone)].join('|'); } function deriveAccountCodeFromAccount(account){ return `AC-${simpleHash36(deriveAccountCodeSeed(account)).slice(0,8)}`; } function deriveAccountCodeFromStop(stop){ return `AC-${simpleHash36(deriveAccountCodeSeed({ sourceAccountId: stop && stop.sourceAccountId, businessEmail: stop && stop.businessEmail, businessName: stop && stop.label, phone: stop && stop.phone })).slice(0,8)}`; } function buildQRPayloadForRecord(record){ const code = deriveAccountCodeFromAccount(record || {}); const email = cleanStr(record && (record.business_email || record.businessEmail)).toLowerCase(); const biz = encodeURIComponent(cleanStr(record && (record.business_name || record.businessName || record.label))); return `SKYE://CLIENT/${code}|${email}|${biz}`; } function normalizeOutcomeCode(value){ const raw = cleanStr(value).replace(/-/g, '_').toLowerCase(); if(OUTCOME_DEFS[raw]) return raw; if(raw === 'followupneeded') return 'followup_needed'; if(raw === 'wrongaddress') return 'wrong_address'; if(raw === 'gatelocked') return 'gate_locked'; if(raw === 'siteclosed') return 'site_closed'; if(raw === 'notqualified') return 'not_qualified'; if(raw === 'noanswer') return 'no_answer'; return raw || 'pending'; } function outcomeDef(value){ return OUTCOME_DEFS[normalizeOutcomeCode(value)] || OUTCOME_DEFS.pending; } function statusFromOutcomeCode(value){ return outcomeDef(value).status; } function deriveLocationMode(record){ const r = record && typeof record === 'object' ? record : {}; const exact = cleanStr(r.exactAddress || buildAEFlowPreciseAddress(r)); const streetish = cleanStr(r.address || r.street_address || r.street); const cityish = cleanStr(r.city || r.state || r.zip || r.postal_code || r.address_unit || r.unit); const territoryish = cleanStr(r.serviceArea || r.service_area || r.routeHint || r.route || r.territoryPrimary || r.territory || ((Array.isArray(r.territoryZones) && r.territoryZones[0]) || '')); if(exact) return 'exact_address'; if(streetish && cityish) return 'partial_address'; if(territoryish) return 'territory_fallback'; return 'manual_stop_text'; } function locationModeLabel(recordOrMode){ const mode = typeof recordOrMode === 'string' ? cleanStr(recordOrMode) : deriveLocationMode(recordOrMode); return LOCATION_MODE_LABELS[mode] || LOCATION_MODE_LABELS.manual_stop_text; } function buildLocationLabel(record){ const r = record && typeof record === 'object' ? record : {}; return cleanStr(r.exactAddress || r.address || buildAEFlowPreciseAddress(r) || r.serviceArea || r.service_area || r.routeHint || r.route || r.locationNotes || r.address_notes || 'Manual stop text'); } function hydrateStopRecord(stop){ const s = stop && typeof stop === 'object' ? { ...stop } : {}; s.clientKey = cleanStr(s.clientKey) || buildClientKeyFromStop(s); s.accountCode = cleanStr(s.accountCode) || deriveAccountCodeFromStop(s); s.outcomeCode = normalizeOutcomeCode(s.outcomeCode || s.status); s.locationMode = cleanStr(s.locationMode) || deriveLocationMode(s); s.locationModeLabel = locationModeLabel(s.locationMode); s.addressMode = cleanStr(s.addressMode) || s.locationMode; return s; } function hydrateRouteRecord(route){ const r = route && typeof route === 'object' ? { ...route } : {}; if(!Array.isArray(r.boardLegNotes)) r.boardLegNotes = []; if(!Array.isArray(r.routePackRefs)) r.routePackRefs = []; if(!Array.isArray(r.tripRefs)) r.tripRefs = []; return r; } function normalizeRoutePackIndexRecord(entry){ const e = entry && typeof entry === 'object' ? entry : {}; return { id: cleanStr(e.id) || uid(), label: cleanStr(e.label) || 'Route pack', createdAt: cleanStr(e.createdAt) || nowISO(), exportedAt: cleanStr(e.exportedAt) || nowISO(), importedAt: cleanStr(e.importedAt), routeIds: Array.isArray(e.routeIds) ? e.routeIds.map(cleanStr).filter(Boolean) : [], clientKeys: Array.isArray(e.clientKeys) ? e.clientKeys.map(cleanStr).filter(Boolean) : [], accountCodes: Array.isArray(e.accountCodes) ? e.accountCodes.map(cleanStr).filter(Boolean) : [], source: cleanStr(e.source) || 'local', summaryName: cleanStr(e.summaryName), jsonName: cleanStr(e.jsonName), fingerprint: cleanStr(e.fingerprint), counts: e.counts && typeof e.counts === 'object' ? e.counts : {}, note: cleanStr(e.note) }; } function readRoutePackIndex(){ return (readPlainJSON(ROUTEX_ROUTE_PACK_INDEX_KEY, []) || []).map(normalizeRoutePackIndexRecord).slice(0, 300); } function writeRoutePackIndex(items){ return writePlainJSON(ROUTEX_ROUTE_PACK_INDEX_KEY, (items || []).map(normalizeRoutePackIndexRecord).slice(0, 300)); } function saveRoutePackIndexRecord(entry){ const item = normalizeRoutePackIndexRecord(entry); const items = readRoutePackIndex().filter(x => x.id !== item.id); writeRoutePackIndex([item, ...items]); return item; } function accountRoutePackRefs(account){ const key = buildClientKeyFromAccount(account); const code = deriveAccountCodeFromAccount(account); return readRoutePackIndex().filter(item => item.clientKeys.includes(key) || item.accountCodes.includes(code)); } function normalizeProofRegistryEntry(entry){ const e = entry && typeof entry === 'object' ? entry : {}; return { id: cleanStr(e.id) || uid(), lane: cleanStr(e.lane) || 'general', proofStates: e.proofStates && typeof e.proofStates === 'object' ? e.proofStates : { fresh:false, legacy:false, exportImport:false, restore:false, noDeadButtons:false }, note: cleanStr(e.note), updatedAt: cleanStr(e.updatedAt) || nowISO(), createdAt: cleanStr(e.createdAt) || nowISO() }; } function readProofRegistry(){ return (readPlainJSON(ROUTEX_PROOF_REGISTRY_KEY, []) || []).map(normalizeProofRegistryEntry).slice(0, 200); } function writeProofRegistry(items){ return writePlainJSON(ROUTEX_PROOF_REGISTRY_KEY, (items || []).map(normalizeProofRegistryEntry).slice(0, 200)); } function saveProofRegistryEntry(entry){ const item = normalizeProofRegistryEntry(entry); const items = readProofRegistry().filter(x => x.id !== item.id && x.lane !== item.lane); writeProofRegistry([item, ...items]); return item; } function normalizeAEPackSeed(seed){ const s = seed && typeof seed === 'object' ? seed : {}; return { id: cleanStr(s.id) || uid(), label: cleanStr(s.label) || 'AE FLOW Routex pack seed', created_at: cleanStr(s.created_at) || nowISO(), updated_at: cleanStr(s.updated_at) || nowISO(), accounts: Array.isArray(s.accounts) ? s.accounts.map(sanitizeAEFlowAccount).filter(a => a.id || a.business_email || a.business_name) : [], meta: s.meta && typeof s.meta === 'object' ? s.meta : {} }; } function readAEPackSeeds(){ return (readPlainJSON(ROUTEX_AE_PACK_SEED_KEY, []) || []).map(normalizeAEPackSeed).slice(0, 120); } function writeAEPackSeeds(items){ return writePlainJSON(ROUTEX_AE_PACK_SEED_KEY, Array.isArray(items) ? items.map(normalizeAEPackSeed).slice(0,120) : []); } function deleteAEPackSeed(id){ writeAEPackSeeds(readAEPackSeeds().filter(item => item.id !== cleanStr(id))); } function queueAEPackSeedAccounts(seed){ const current = getAEFlowQueuedAccounts(); const merged = mergeAEFlowAccounts(Array.isArray(seed && seed.accounts) ? seed.accounts : [], current); writeStoredJSON(ROUTEX_AE_QUEUE_KEY, merged); return merged.length; } function buildProofSnapshotPayload(label){ return { id: uid(), label: cleanStr(label) || `Proof snapshot ${dayISO()}`, createdAt: nowISO(), backup: { app: 'Skye Route & Dispatch Vault', version: 2, exportedAt: nowISO(), includePhotos: false, routes: APP.cached.routes.map(hydrateRouteRecord), stops: APP.cached.stops.map(hydrateStopRecord), photos: [], followupTasks: readRouteTasks(), routeTemplates: readRouteTemplates(), inventoryCatalog: readInventoryCatalog(), docVault: readVaultDocs(), savedViews: readSavedViews(), localReminders: readLocalReminders() }, counts: { routes: APP.cached.routes.length, stops: APP.cached.stops.length, docs: readVaultDocs().length, tasks: readRouteTasks().length, routePacks: readRoutePackIndex().length, tripPacks: readTripPacks().length } }; } function readProofSnapshots(){ return (readPlainJSON(ROUTEX_PROOF_SNAPSHOTS_KEY, []) || []).slice(0, 40); } function writeProofSnapshots(items){ return writePlainJSON(ROUTEX_PROOF_SNAPSHOTS_KEY, Array.isArray(items) ? items.slice(0,40) : []); } function saveProofSnapshot(label){ const snap = buildProofSnapshotPayload(label); writeProofSnapshots([snap, ...readProofSnapshots().filter(item => cleanStr(item.id) !== snap.id)].slice(0,40)); return snap; } async function restoreProofSnapshot(id){ const snap = readProofSnapshots().find(item => cleanStr(item.id) === cleanStr(id)); if(!snap || !snap.backup) return false; await importBackup(snap.backup); return true; } function readRoutePackTransferLog(){ return (readPlainJSON(ROUTEX_ROUTE_PACK_TRANSFER_LOG_KEY, []) || []).slice(0, 60); } function writeRoutePackTransferLog(items){ return writePlainJSON(ROUTEX_ROUTE_PACK_TRANSFER_LOG_KEY, Array.isArray(items) ? items.slice(0,60) : []); } function pushRoutePackTransferLog(entry){ const row = { id: uid(), createdAt: nowISO(), ...(entry || {}) }; writeRoutePackTransferLog([row, ...readRoutePackTransferLog().filter(item => cleanStr(item.id) !== row.id)].slice(0,60)); return row; } function readProofRestoreGenerations(){ return (readPlainJSON(ROUTEX_PROOF_RESTORE_GENERATIONS_KEY, []) || []).slice(0, 60); } function writeProofRestoreGenerations(items){ return writePlainJSON(ROUTEX_PROOF_RESTORE_GENERATIONS_KEY, Array.isArray(items) ? items.slice(0,60) : []); } function pushProofRestoreGeneration(entry){ const row = { id: uid(), createdAt: nowISO(), ...(entry || {}) }; writeProofRestoreGenerations([row, ...readProofRestoreGenerations().filter(item => cleanStr(item.id) !== row.id)].slice(0,60)); return row; } async function ensureProofRouteAndStop(label, legacyMode){ let route = APP.cached.routes[0] || null; let stop = route ? stopsForRoute(route.id)[0] : null; if(!route){ const accounts = getAEFlowAccounts(); if(accounts.length){ route = await createRouteFromAEFlowAccounts({ accounts: accounts.slice(0,1), name: label || `Proof Route • ${dayISO()}`, clearQueued:false, routeNotes:'Proof fixture route' }); }else{ route = await createRoute({ name: label || `Proof Route • ${dayISO()}`, date: dayISO(), territory:'Proof Territory', routeNotes:'Proof fixture route' }); stop = await createStop(route.id, { label:'Proof Client', address:'123 Proof Ave, Phoenix, AZ', contact:'Proof Contact', notes:'Proof fixture stop', businessEmail:'proof@example.com', serviceArea:'Phoenix', exactAddress:'123 Proof Ave, Phoenix, AZ' }); } await refreshCache(); } route = APP.cached.routes.find(item => item.id === (route && route.id)) || APP.cached.routes[0] || route; stop = stop || (route ? stopsForRoute(route.id)[0] : null); if(route && !stop){ stop = await createStop(route.id, { label:'Proof Client', address:'123 Proof Ave, Phoenix, AZ', contact:'Proof Contact', notes:'Proof fixture stop', businessEmail:'proof@example.com', serviceArea:'Phoenix', exactAddress:'123 Proof Ave, Phoenix, AZ' }); await refreshCache(); stop = APP.cached.stops.find(item => item.id === stop.id) || stop; } if(route && stop && legacyMode){ const raw = await get(APP.db, 'stops', stop.id); if(raw){ delete raw.accountCode; delete raw.locationMode; delete raw.outcomeCode; delete raw.addressMode; raw.updatedAt = nowISO(); await put(APP.db, 'stops', raw); await refreshCache(); } } return { route: route ? (APP.cached.routes.find(item => item.id === route.id) || route) : null, stop: stop ? (APP.cached.stops.find(item => item.id === stop.id) || stop) : null }; } async function seedValidationFixture(lane, mode){ const kind = cleanStr(mode) === 'legacy' ? 'legacy' : 'fresh'; const target = cleanStr(lane); if(target === 'account-code' || target === 'heat-score'){ const state = getAEFlowState(); const accounts = Array.isArray(state.accounts) ? state.accounts.slice() : []; if(!accounts.length){ accounts.unshift({ id: uid(), ae_name:'Proof AE', route:'Proof Territory', business_name: `Proof ${kind==='legacy' ? 'Legacy ' : ''}Account`, business_email:`proof_${kind}@example.com`, phone:'', contact_name:'Proof Contact', service_1:'Proof Service', service_area:'Phoenix', account_status:'Active (Vetted)', notes:'Proof fixture account', tags:['proof-fixture'] }); } if(kind === 'legacy' && accounts[0]){ delete accounts[0].account_code; delete accounts[0].location_mode; } writeStoredJSON(AE_FLOW_STORAGE_KEY, { ...state, accounts }); return `${target} ${kind} fixture ensured in AE FLOW storage.`; } const pair = await ensureProofRouteAndStop(`Proof ${target} • ${dayISO()}`, kind === 'legacy'); const route = pair.route, stop = pair.stop; if(!route) return 'Unable to seed proof fixture route.'; if(target === 'route-pack'){ const stops = stopsForRoute(route.id).map(hydrateStopRecord); if(kind === 'legacy'){ const raw = readPlainJSON(ROUTEX_ROUTE_PACK_INDEX_KEY, []); raw.unshift({ id: uid(), label:'Proof legacy route pack', routeIds:[route.id], createdAt: nowISO(), source:'proof-legacy' }); writePlainJSON(ROUTEX_ROUTE_PACK_INDEX_KEY, raw.slice(0,300)); }else{ saveRoutePackIndexRecord({ id: uid(), label:`Proof route pack ${dayISO()}`, routeIds:[route.id], clientKeys:stops.map(item => item.clientKey).filter(Boolean), accountCodes:stops.map(item => item.accountCode || deriveAccountCodeFromStop(item)).filter(Boolean), source:'proof-fixture', createdAt:nowISO(), exportedAt:nowISO(), counts:{ routes:1, stops:stops.length, docs:getVaultDocsForStop(stop || {}).length, reminders:0, tasks:0 }, note:'Proof fixture route-pack seed' }); } return `Route-pack ${kind} fixture seeded.`; } if(target === 'service-summary'){ if(kind === 'legacy'){ const raw = readPlainJSON(ROUTEX_DOC_VAULT_KEY, []); raw.unshift({ id: uid(), routeId: route.id, stopId: stop && stop.id, sourceAccountId: stop && stop.sourceAccountId, businessEmail: stop && stop.businessEmail, businessName: stop && stop.label, title:'Proof legacy service summary', kind:'service-summary', html: buildServiceSummaryHtml(route, stop || { label:'Proof Client' }), createdAt: nowISO() }); writePlainJSON(ROUTEX_DOC_VAULT_KEY, raw.slice(0,2000)); }else if(stop){ makeHtmlDocEntry({ routeId: route.id, stopId: stop.id, sourceAccountId: stop.sourceAccountId, businessEmail: stop.businessEmail, businessName: stop.label, title:`${stop.label} — Proof Service Summary`, kind:'service-summary', tags:['proof-fixture','service-slip'] }, buildServiceSummaryHtml(route, stop)); } return `Service-summary ${kind} fixture seeded.`; } if(target === 'voice-note'){ if(kind === 'legacy'){ const raw = readPlainJSON(ROUTEX_DOC_VAULT_KEY, []); raw.unshift({ id: uid(), routeId: route.id, stopId: stop && stop.id, sourceAccountId: stop && stop.sourceAccountId, businessEmail: stop && stop.businessEmail, businessName: stop && stop.label, title:'Proof legacy voice note', kind:'voice-note', note:'Legacy voice note fixture', createdAt: nowISO() }); writePlainJSON(ROUTEX_DOC_VAULT_KEY, raw.slice(0,2000)); }else{ saveVaultDoc({ routeId: route.id, stopId: stop && stop.id, sourceAccountId: stop && stop.sourceAccountId, businessEmail: stop && stop.businessEmail, businessName: stop && stop.label, title:'Proof Voice Note', kind:'voice-note', mime:'text/plain', tags:['proof-fixture','voice-note'], note:'Voice-note fixture created for proof registry.', html:'

    Proof voice-note fixture.

    '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); ' }); } return `Voice-note ${kind} fixture seeded.`; } if(target === 'pseudo-map-board'){ const board = Array.isArray(route.boardLegNotes) ? route.boardLegNotes.slice() : []; if(!board.length){ board.push({ id: uid(), fromStopId: stop && stop.id || '', toStopId:'', note:`${kind==='legacy' ? 'Legacy' : 'Fresh'} pseudo-map leg note`, createdAt: nowISO() }); await updateRoute(route.id, { boardLegNotes: board }); } return `Pseudo-map ${kind} fixture seeded.`; } if(target === 'trip-pack'){ if(kind === 'legacy'){ const raw = readPlainJSON(ROUTEX_TRIP_PACKS_KEY, []); raw.unshift({ id: uid(), title:'Proof legacy trip', routeIds:[route.id], dayBuckets:[{ routeIds:[route.id], date: route.date }], createdAt:nowISO() }); writePlainJSON(ROUTEX_TRIP_PACKS_KEY, raw.slice(0,100)); }else{ saveTripPack({ title:`Proof trip ${dayISO()}`, note:'Proof fixture trip pack', routeIds:[route.id], dayBuckets:[{ id:uid(), label:'Day 1', date:route.date, routeIds:[route.id], lodgingCost:'0', note:'Proof trip bucket', closedAt:'', closedNote:'' }], tripExpenses:[] }); } return `Trip-pack ${kind} fixture seeded.`; } return `${target} fixture lane not recognized.`; } function buildProofDiagnostics(){ const summary = buildValidationSummary(); return { generatedAt: nowISO(), proofRegistry: readProofRegistry(), summary, laneEligibility: buildProofLaneEligibility(), snapshots: readProofSnapshots().map(item => ({ id:item.id, label:item.label, createdAt:item.createdAt, counts:item.counts })), routePackIndex: readRoutePackIndex(), tripPacks: readTripPacks(), vaultDocs: readVaultDocs().map(doc => ({ id:doc.id, title:doc.title, kind:doc.kind, routeId:doc.routeId, stopId:doc.stopId, createdAt:doc.createdAt })), aePackSeeds: readAEPackSeeds().map(seed => ({ id:seed.id, label:seed.label, created_at:seed.created_at, count:seed.accounts.length, meta:seed.meta })), }; } function getTripRoutes(trip){ const ids = new Set((trip && Array.isArray(trip.routeIds) ? trip.routeIds : []).map(cleanStr).filter(Boolean)); return APP.cached.routes.filter(route => ids.has(route.id)); } function getTripDayRollups(trip){ const buckets = Array.isArray(trip && trip.dayBuckets) ? trip.dayBuckets : []; return buckets.map((bucket, idx) => { const routeIds = new Set((bucket.routeIds || []).map(cleanStr).filter(Boolean)); const routes = APP.cached.routes.filter(route => routeIds.has(route.id)); const metricsRows = routes.map(route => computeRouteMetrics(route)); const totals = metricsRows.reduce((acc, row) => { acc.routes += 1; acc.stops += num(row.stops); acc.completed += num(row.completed); acc.delivered += num(row.delivered); acc.miles += num(row.miles); acc.revenue += num(row.revenue); acc.cost += num(row.totalCost); acc.net += num(row.net); acc.collections += num(row.collectionTotal); acc.balanceDue += num(row.balanceDue); acc.materialCost += num(row.materialCost); return acc; }, { routes:0, stops:0, completed:0, delivered:0, miles:0, revenue:0, cost:0, net:0, collections:0, balanceDue:0, materialCost:0 }); const lodging = num(bucket.lodgingCost); return { id: cleanStr(bucket.id) || uid(), label: cleanStr(bucket.label) || `Day ${idx+1}`, date: cleanStr(bucket.date), routeIds: Array.from(routeIds), routes, lodging, note: cleanStr(bucket.note), closedAt: cleanStr(bucket.closedAt), closedNote: cleanStr(bucket.closedNote), totals: { ...totals, lodging, netAfterLodging: round2(totals.net - lodging) } }; }); } function getTripPackTotals(trip){ const dayRollups = getTripDayRollups(trip); const tripExpenses = Array.isArray(trip && trip.tripExpenses) ? trip.tripExpenses.map(item => ({ id: cleanStr(item.id) || uid(), label: cleanStr(item.label) || 'Trip expense', amount: num(item.amount), note: cleanStr(item.note), date: cleanStr(item.date) })) : []; const totals = dayRollups.reduce((acc, day) => { acc.routes += day.totals.routes; acc.stops += day.totals.stops; acc.completed += day.totals.completed; acc.delivered += day.totals.delivered; acc.miles += day.totals.miles; acc.revenue += day.totals.revenue; acc.cost += day.totals.cost; acc.net += day.totals.net; acc.collections += day.totals.collections; acc.balanceDue += day.totals.balanceDue; acc.materialCost += day.totals.materialCost; acc.lodging += day.totals.lodging; return acc; }, { routes:0, stops:0, completed:0, delivered:0, miles:0, revenue:0, cost:0, net:0, collections:0, balanceDue:0, materialCost:0, lodging:0 }); const tripExpenseTotal = round2(tripExpenses.reduce((sum, item) => sum + num(item.amount), 0)); return { dayRollups, tripExpenses, totals: { ...totals, tripExpenseTotal, grandCost: round2(totals.cost + totals.lodging + tripExpenseTotal), netAfterTrip: round2(totals.net - totals.lodging - tripExpenseTotal) } }; } function normalizeTripPack(entry){ const e = entry && typeof entry === 'object' ? entry : {}; const buckets = Array.isArray(e.dayBuckets) ? e.dayBuckets.map((bucket, idx) => ({ id: cleanStr(bucket.id) || uid(), label: cleanStr(bucket.label) || `Day ${idx+1}`, date: cleanStr(bucket.date), routeIds: Array.isArray(bucket.routeIds) ? bucket.routeIds.map(cleanStr).filter(Boolean) : [], lodgingCost: cleanStr(bucket.lodgingCost), note: cleanStr(bucket.note), closedAt: cleanStr(bucket.closedAt), closedNote: cleanStr(bucket.closedNote) })) : []; return { id: cleanStr(e.id) || uid(), title: cleanStr(e.title) || 'Trip Pack', note: cleanStr(e.note), routeIds: Array.isArray(e.routeIds) ? e.routeIds.map(cleanStr).filter(Boolean) : [], dayBuckets: buckets, tripExpenses: Array.isArray(e.tripExpenses) ? e.tripExpenses.map(item => ({ id: cleanStr(item.id) || uid(), label: cleanStr(item.label) || 'Trip expense', amount: cleanStr(item.amount), note: cleanStr(item.note), date: cleanStr(item.date) })) : [], createdAt: cleanStr(e.createdAt) || nowISO(), updatedAt: cleanStr(e.updatedAt) || nowISO() }; } function readTripPacks(){ return (readPlainJSON(ROUTEX_TRIP_PACKS_KEY, []) || []).map(normalizeTripPack).slice(0, 100); } function writeTripPacks(items){ return writePlainJSON(ROUTEX_TRIP_PACKS_KEY, (items || []).map(normalizeTripPack).slice(0, 100)); } function saveTripPack(entry){ const item = normalizeTripPack(entry); const items = readTripPacks().filter(x => x.id !== item.id); item.updatedAt = nowISO(); writeTripPacks([item, ...items]); return item; } const ROUTEX_HYBRID_GEOCODE_QUEUE_KEY = "skye_routex_hybrid_geocode_queue_v1"; const ROUTEX_HYBRID_SYNC_OUTBOX_KEY = "skye_routex_hybrid_sync_outbox_v1"; function normalizeOptionalHybridPlans(value){ const raw = value && typeof value === 'object' ? value : {}; return { geocoding: cleanStr(raw.geocoding), sync: cleanStr(raw.sync), calendar: cleanStr(raw.calendar), cloudBackup: cleanStr(raw.cloudBackup), messaging: cleanStr(raw.messaging), geocode_mode: cleanStr(raw.geocode_mode) || 'manual-export', geocode_provider: cleanStr(raw.geocode_provider) || 'none', geocode_endpoint: cleanStr(raw.geocode_endpoint), sync_mode: cleanStr(raw.sync_mode) || 'offline-outbox', sync_target: cleanStr(raw.sync_target), sync_endpoint: cleanStr(raw.sync_endpoint), sync_scope: cleanStr(raw.sync_scope) || 'route-packs', updatedAt: cleanStr(raw.updatedAt) || nowISO() }; } function readOptionalHybridPlans(){ return normalizeOptionalHybridPlans(readPlainJSON(ROUTEX_OPTIONAL_HYBRID_KEY, {})); } function writeOptionalHybridPlans(value){ return writePlainJSON(ROUTEX_OPTIONAL_HYBRID_KEY, normalizeOptionalHybridPlans(value || {})); } function normalizeHybridQueueItem(value){ const raw = value && typeof value === 'object' ? value : {}; return { id: cleanStr(raw.id) || uid(), routeId: cleanStr(raw.routeId), label: cleanStr(raw.label) || 'Hybrid queue item', kind: cleanStr(raw.kind) || 'route-snapshot', createdAt: cleanStr(raw.createdAt) || nowISO(), updatedAt: cleanStr(raw.updatedAt) || nowISO(), payload: raw.payload && typeof raw.payload === 'object' ? raw.payload : {}, note: cleanStr(raw.note) }; } function readHybridGeocodeQueue(){ return (readPlainJSON(ROUTEX_HYBRID_GEOCODE_QUEUE_KEY, []) || []).map(normalizeHybridQueueItem).slice(0,120); } function writeHybridGeocodeQueue(items){ return writePlainJSON(ROUTEX_HYBRID_GEOCODE_QUEUE_KEY, (Array.isArray(items) ? items : []).map(normalizeHybridQueueItem).slice(0,120)); } function readHybridSyncOutbox(){ return (readPlainJSON(ROUTEX_HYBRID_SYNC_OUTBOX_KEY, []) || []).map(normalizeHybridQueueItem).slice(0,120); } function writeHybridSyncOutbox(items){ return writePlainJSON(ROUTEX_HYBRID_SYNC_OUTBOX_KEY, (Array.isArray(items) ? items : []).map(normalizeHybridQueueItem).slice(0,120)); } function getAccountHeatData(account){ const a = sanitizeAEFlowAccount(account); const insight = buildRevisitInsights(a); const stats = getAccountAttemptStats(a); const history = getAccountVisitHistory(a); const routeMetrics = history.slice(0, 5).map(stop => APP.cached.routes.find(route => route.id === stop.routeId)).filter(Boolean).map(route => computeRouteMetrics(route)); const avgNet = routeMetrics.length ? round2(routeMetrics.reduce((sum, row)=> sum + num(row.net), 0) / routeMetrics.length) : 0; const collections = history.reduce((sum, stop)=> { const route = APP.cached.routes.find(item => item.id === stop.routeId); if(!route) return sum; return sum + normalizeCollectionEntries(route.collectionEntries).filter(entry => cleanStr(entry.stopId) === cleanStr(stop.id)).reduce((sub, entry)=> sub + num(entry.balanceDue), 0); }, 0); let score = 52; score += Math.min(18, stats.successful * 7); score -= Math.min(18, stats.failed * 6); score += history.length > 1 ? 8 : 0; score += insight.lowYield ? -10 : 6; score += insight.isCold ? -16 : 10; score += avgNet > 0 ? 10 : avgNet < 0 ? -8 : 0; score -= collections > 0 ? 8 : 0; if(num(insight.daysSinceVisit) <= 7) score += 8; if(num(insight.daysSinceVisit) >= 30) score -= 10; score = Math.max(0, Math.min(100, Math.round(score))); const band = score >= 80 ? 'Hot' : score >= 62 ? 'Warm' : score >= 45 ? 'Watch' : 'Cold'; const tone = score >= 80 ? 'good' : score >= 62 ? 'warn' : 'bad'; const explanation = [ stats.successful ? `${stats.successful} successful visit${stats.successful===1 ? '' : 's'}` : 'No successful visits yet', stats.failed ? `${stats.failed} failed attempt${stats.failed===1 ? '' : 's'}` : 'No failed attempts', insight.isCold ? 'Cold follow-up pressure' : 'Fresh account cadence', collections > 0 ? `${fmtMoney(collections)} still due` : 'Collections clean', avgNet ? `Recent avg net ${fmtMoney(avgNet)}` : 'No profitability history yet' ]; return { score, band, tone, explanation, avgNet, collectionsDue: round2(collections) }; } function buildValidationSummary(){ const entries = readProofRegistry(); const totals = { fresh:0, legacy:0, exportImport:0, restore:0, noDeadButtons:0 }; const latestByLane = {}; entries.forEach(entry => { Object.keys(totals).forEach(key => { if(entry.proofStates && entry.proofStates[key]) totals[key] += 1; }); const lane = cleanStr(entry.lane) || 'unknown'; if(!latestByLane[lane] || cleanStr(entry.createdAt) > cleanStr(latestByLane[lane].createdAt)) latestByLane[lane] = entry; }); return { entries, totals, latestByLane }; } function hashTextSmall(input){ const value = String(input || ''); let h = 2166136261; for(let i=0;i>> 0).toString(16)}`).slice(-8); } function buildRoutePackFingerprint(data){ const routes = (Array.isArray(data && data.routes) ? data.routes : []).map(route => ({ date: cleanStr(route.date), name: cleanStr(route.name).toLowerCase(), vehicle: cleanStr(route.vehicleProfileId || route.vehicle || '') })).sort((a,b)=> `${a.date}|${a.name}`.localeCompare(`${b.date}|${b.name}`)); const stops = (Array.isArray(data && data.stops) ? data.stops : []).map(stop => ({ route: cleanStr(stop.routeId), label: cleanStr(stop.label).toLowerCase(), code: cleanStr(stop.accountCode || deriveAccountCodeFromStop(stop)), loc: cleanStr(stop.locationMode || deriveLocationMode(stop)), status: cleanStr(stop.status || stop.outcomeCode) })).sort((a,b)=> `${a.route}|${a.label}|${a.code}`.localeCompare(`${b.route}|${b.label}|${b.code}`)); const docs = Array.isArray(data && data.docs) ? data.docs.map(doc => ({ kind: cleanStr(doc.kind), title: cleanStr(doc.title).toLowerCase() })).sort((a,b)=> `${a.kind}|${a.title}`.localeCompare(`${b.kind}|${b.title}`)) : []; const basis = JSON.stringify({ routes, stops, docs, tasks: Array.isArray(data && data.tasks) ? data.tasks.length : 0, reminders: Array.isArray(data && data.reminders) ? data.reminders.length : 0 }); return `rp-${hashTextSmall(basis)}`; } function buildRoutePackSummaryHtml(payload){ const routes = Array.isArray(payload.routes) ? payload.routes : []; const stops = Array.isArray(payload.stops) ? payload.stops : []; const docs = Array.isArray(payload.docs) ? payload.docs : []; const tasks = Array.isArray(payload.tasks) ? payload.tasks : []; const reminders = Array.isArray(payload.reminders) ? payload.reminders : []; return `${escapeHTML(payload.label || 'Route pack summary')}

    ${escapeHTML(payload.label || 'Route pack summary')}

    Exported ${escapeHTML(fmt(payload.exportedAt || nowISO()))} • Fingerprint ${escapeHTML(payload.fingerprint || buildRoutePackFingerprint(payload))} • Routes ${routes.length} • Stops ${stops.length} • Docs ${docs.length} • Tasks ${tasks.length} • Reminders ${reminders.length}
    ${routes.map(route => { const metrics = computeRouteMetrics(hydrateRouteRecord(route)); const routeStops = stops.filter(stop => cleanStr(stop.routeId) === cleanStr(route.id)); return ``; }).join('')}
    RouteDateStopsNet
    ${escapeHTML(route.name)}${escapeHTML(route.date || '')}${routeStops.length}${escapeHTML(fmtMoney(metrics.net))}
    ${stops.map(stop => { const hs = hydrateStopRecord(stop); return ``; }).join('')}
    StopLocation qualityAccount codeStatus
    ${escapeHTML(hs.label)}${escapeHTML(locationModeLabel(hs.locationMode))}${escapeHTML(hs.accountCode || '')}${escapeHTML(STOP_STATUS_LABELS[hs.status] || hs.status || 'Pending')}
    \n '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); `; } function buildDeliveryConfirmationHtml(route, stops){ const list = Array.isArray(stops) ? stops : [stops].filter(Boolean); const delivered = list.filter(item => cleanStr(item.status) === 'delivered' || normalizeOutcomeCode(item.outcomeCode) === 'delivered'); const top = delivered[0] || list[0] || {}; return `${escapeHTML((top.label || route.name || 'Delivery'))} — Delivery Confirmation

    Delivery Confirmation

    Route: ${escapeHTML(route.name || '—')} • Date: ${escapeHTML(route.date || '')}
    Client: ${escapeHTML(top.label || top.businessName || '—')}
    Account code: ${escapeHTML(top.accountCode || deriveAccountCodeFromStop(top))}
    Location quality: ${escapeHTML(locationModeLabel(top.locationMode || deriveLocationMode(top)))}
    Confirmed stops: ${delivered.map(item => `${escapeHTML(item.label)} (${escapeHTML(fmt(item.completedAt || item.deliveredAt || item.arrivedAt))})`).join('
    ') || 'No delivered stops captured yet.'}
    Route notes: ${escapeHTML(route.routeNotes || '—')}
    Prepared offline in Skye Routex
    \n '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); `; } function buildEnhancedServiceSummaryHtml(route, stop){ const code = cleanStr(stop.accountCode) || deriveAccountCodeFromStop(stop); const locationText = locationModeLabel(stop.locationMode || deriveLocationMode(stop)); return `${escapeHTML(stop.label)} — Service Summary

    Service Summary

    Route: ${escapeHTML(route.name)}
    Date: ${escapeHTML(route.date || '')}
    Client: ${escapeHTML(stop.label)}
    Account code: ${escapeHTML(code)}
    Location quality: ${escapeHTML(locationText)}
    Address: ${escapeHTML(stop.address || stop.serviceArea || '—')}
    Status: ${escapeHTML(STOP_STATUS_LABELS[stop.status] || stop.status || 'Pending')}
    Contact: ${escapeHTML(joinNonEmpty([stop.contact, stop.phone, stop.businessEmail], ' • ') || '—')}
    Notes: ${escapeHTML(stop.notes || '—')}
    Outcome: ${escapeHTML(stop.outcomeNote || '—')}
    Created: ${escapeHTML(fmt(stop.createdAt))} • Completed: ${escapeHTML(fmt(stop.completedAt || stop.deliveredAt || stop.arrivedAt))}
    Prepared offline in Skye Routex
    \n '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); `; } function routePackPreviewSummary(data){ const routes = Array.isArray(data.routes) ? data.routes : []; const existingKeys = new Set(APP.cached.routes.map(route => `${cleanStr(route.date)}|${cleanStr(route.name).toLowerCase()}`)); const dupes = routes.filter(route => existingKeys.has(`${cleanStr(route.date)}|${cleanStr(route.name).toLowerCase()}`)); const fingerprint = cleanStr(data.fingerprint) || buildRoutePackFingerprint(data); const sameFingerprint = readRoutePackIndex().filter(item => cleanStr(item.fingerprint) === fingerprint); return { routes: routes.length, stops: Array.isArray(data.stops) ? data.stops.length : 0, docs: Array.isArray(data.docs) ? data.docs.length : 0, reminders: Array.isArray(data.reminders) ? data.reminders.length : 0, tasks: Array.isArray(data.tasks) ? data.tasks.length : 0, duplicates: dupes.map(route => `${route.name} • ${route.date}`), fingerprint, sameFingerprint: sameFingerprint.map(item => item.label || 'Route pack') }; } async function dataAttachmentFromFile(file){ const dataUrl = await blobToDataURL(file); return { id: uid(), name: file.name || 'attachment', type: file.type || 'application/octet-stream', size: file.size || 0, dataUrl, createdAt: nowISO() }; } function openVoiceNoteModal(routeId, stopId, accountInput){ const stop = stopId ? APP.cached.stops.find(item => item.id === stopId) : null; const account = accountInput ? sanitizeAEFlowAccount(accountInput) : (stop ? { id: stop.sourceAccountId, business_email: stop.businessEmail, business_name: stop.label } : null); const supported = !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia && window.MediaRecorder); openModal('Voice note', `
    Record or attach a local voice note. When MediaRecorder is unavailable, attach an audio file instead.
    ${supported ? `
    Idle
    ` : `
    This device/browser does not expose MediaRecorder. Use the audio-file fallback above.
    `}`, ``); let recorder = null; let stream = null; let chunks = []; let recordedBlob = null; if(supported && $('#vn_record')){ $('#vn_record').onclick = async ()=>{ if(recorder && recorder.state === 'recording') return; try{ stream = await navigator.mediaDevices.getUserMedia({ audio:true }); recorder = new MediaRecorder(stream); chunks = []; recorder.ondataavailable = evt => { if(evt.data && evt.data.size) chunks.push(evt.data); }; recorder.onstop = ()=>{ recordedBlob = new Blob(chunks, { type: recorder.mimeType || 'audio/webm' }); const url = URL.createObjectURL(recordedBlob); const preview = $('#vn_preview'); if(preview){ preview.src = url; preview.style.display = 'block'; } if($('#vn_status')) $('#vn_status').textContent = 'Recorded'; if(stream){ stream.getTracks().forEach(track => track.stop()); stream = null; } }; recorder.start(); if($('#vn_status')) $('#vn_status').textContent = 'Recording'; }catch(err){ toast('Unable to access microphone on this device.', 'bad'); } }; $('#vn_stop').onclick = ()=>{ if(recorder && recorder.state === 'recording') recorder.stop(); }; } $('#vn_save').onclick = async ()=>{ let attachment = null; if(recordedBlob) attachment = { id: uid(), name: 'voice-note.webm', type: recordedBlob.type || 'audio/webm', size: recordedBlob.size || 0, dataUrl: await blobToDataURL(recordedBlob), createdAt: nowISO() }; if(!attachment && $('#vn_file') && $('#vn_file').files && $('#vn_file').files[0]) attachment = await dataAttachmentFromFile($('#vn_file').files[0]); if(!attachment){ toast('Record or attach an audio file first.', 'warn'); return; } saveVaultDoc({ routeId: cleanStr(routeId), stopId: cleanStr(stopId), sourceAccountId: stop ? stop.sourceAccountId : (account && account.id), businessEmail: stop ? stop.businessEmail : (account && account.business_email), businessName: stop ? stop.label : (account && account.business_name), title: cleanStr($('#vn_title').value) || 'Voice Note', kind:'voice-note', mime: attachment.type || 'audio/webm', tags:['voice-note','field-media'], note: cleanStr($('#vn_note').value), attachments:[attachment] }); toast('Voice note saved.', 'good'); closeModal(); }; } function lookupOfflineMatches(query){ const accounts = getAEFlowAccounts(); const stops = APP.cached.stops.map(hydrateStopRecord); const raw = cleanStr(query).trim(); if(!raw) return []; const normalized = raw.replace('SKYE://CLIENT/','').split('|')[0].trim().toUpperCase(); return uniqBy([ ...accounts.map(account => ({ type:'account', account, code: deriveAccountCodeFromAccount(account), score: getAccountHeatData(account).score })), ...stops.map(stop => ({ type:'stop', stop, code: cleanStr(stop.accountCode) || deriveAccountCodeFromStop(stop), score: stop.status === 'delivered' ? 100 : 50 })) ].filter(item => { const record = item.type === 'account' ? item.account : item.stop; const hay = [item.code, cleanStr(item.type === 'account' ? record.business_name : record.label), cleanStr(item.type === 'account' ? record.business_email : record.businessEmail), cleanStr(record.phone), buildQRPayloadForRecord(record)].join(' ').toUpperCase(); return hay.includes(normalized) || hay.includes(raw.toUpperCase()); }).sort((a,b)=> (b.score||0) - (a.score||0)), item => `${item.type}|${item.code}`); } function openLookupModal(){ const accounts = getAEFlowAccounts(); const stops = APP.cached.stops.map(hydrateStopRecord); openModal('Client lookup', `
    Find a client by account code, scan payload text, business name, email, phone, or route history.
    Type an account code or client detail.
    `, ``); const renderLookup = ()=>{ const query = cleanStr($('#lk_query').value).trim(); if(!query){ $('#lk_results').innerHTML = `
    Type an account code or client detail.
    `; return; } const rows = lookupOfflineMatches(query); $('#lk_results').innerHTML = rows.map(item => item.type === 'account' ? `
    ${escapeHTML(item.account.business_name)} ${escapeHTML(item.code)}
    ${escapeHTML(item.account.business_email || item.account.phone || item.account.service_area || 'No extra detail')}
    ` : `
    ${escapeHTML(item.stop.label)} ${escapeHTML(item.code)}
    ${escapeHTML(item.stop.address || item.stop.serviceArea || 'No address')} • ${escapeHTML(STOP_STATUS_LABELS[item.stop.status] || item.stop.status || 'Pending')}
    `).join('') || `
    No offline match for that code or client detail.
    `; $$('[data-lk-copy]').forEach(btn => btn.onclick = ()=> navigator.clipboard && navigator.clipboard.writeText(btn.getAttribute('data-lk-copy')).then(()=> toast('Code copied.', 'good')).catch(()=> toast('Copy failed.', 'warn'))); $$('[data-lk-build]').forEach(btn => btn.onclick = ()=>{ const account = accounts.find(item => getAEFlowAccountKey(item) === btn.getAttribute('data-lk-build')); if(account) openAEFlowRouteBuilderModal([account]); }); $$('[data-lk-open]').forEach(btn => btn.onclick = ()=>{ APP.routeId = btn.getAttribute('data-lk-open'); APP.view = 'routes'; window.location.hash = 'routes'; render(); closeModal(); }); }; $('#lk_query').addEventListener('input', renderLookup); } function getBoardLegNotes(route){ return Array.isArray(route && route.boardLegNotes) ? route.boardLegNotes : []; } function legNoteFor(route, fromStopId, toStopId){ return getBoardLegNotes(route).find(item => cleanStr(item.fromStopId) === cleanStr(fromStopId) && cleanStr(item.toStopId) === cleanStr(toStopId)); } function openPseudoMapBoardModal(routeId){ const route = APP.cached.routes.find(item => item.id === routeId); if(!route) return; const stops = stopsForRoute(routeId); const rows = stops.map((stop, idx) => { const next = stops[idx + 1]; const note = next ? legNoteFor(route, stop.id, next.id) : null; return `
    #${idx+1} ${escapeHTML(stop.label)} ${stopBadge(stop.status)} ${escapeHTML(stop.accountCode || deriveAccountCodeFromStop(stop))}
    ${escapeHTML(stop.address || stop.serviceArea || 'No address')} • ${escapeHTML(locationModeLabel(stop.locationMode || deriveLocationMode(stop)))}
    ${next ? `
    ` : `
    Final stop.
    `}
    `; }).join(''); openModal(`Pseudo-map board • ${escapeHTML(route.name)}`, `
    Offline board view for manual sequencing, travel notes, and next-stop focus. This stays text-driven on purpose.
    ${rows || `
    No stops yet.
    `}
    `, ``); $$('[data-board-up]').forEach(btn => btn.onclick = async ()=>{ await moveStop(routeId, btn.getAttribute('data-board-up'), -1); openPseudoMapBoardModal(routeId); }); $$('[data-board-down]').forEach(btn => btn.onclick = async ()=>{ await moveStop(routeId, btn.getAttribute('data-board-down'), 1); openPseudoMapBoardModal(routeId); }); $$('[data-board-open]').forEach(btn => btn.onclick = ()=> openEditStopModal(routeId, btn.getAttribute('data-board-open'))); $('#board_save').onclick = async ()=>{ const notes = $$('[data-board-note]').map(node => { const parts = (node.getAttribute('data-board-note') || '').split('|'); return { id: uid(), fromStopId: parts[0], toStopId: parts[1], note: cleanStr(node.value) }; }).filter(item => item.note); await updateRoute(routeId, { boardLegNotes: notes }); await appendRouteEvent(routeId, 'board-updated', 'Pseudo-map board notes updated', { noteCount: notes.length }); toast('Pseudo-map board saved.', 'good'); closeModal(); }; } function buildTripSummaryHtml(trip){ const data = getTripPackTotals(normalizeTripPack(trip)); const t = normalizeTripPack(trip); const totals = data.totals; return `${escapeHTML(t.title)}

    ${escapeHTML(t.title)}

    Created ${escapeHTML(fmt(t.createdAt || nowISO()))} • Updated ${escapeHTML(fmt(t.updatedAt || nowISO()))}
    ${escapeHTML(t.note || '')}
    ${totals.routes}
    Routes
    ${totals.stops}
    Stops
    ${fmtMoney(totals.revenue)}
    Revenue
    ${fmtMoney(totals.cost)}
    Route cost
    ${fmtMoney(totals.lodging)}
    Lodging
    ${fmtMoney(totals.tripExpenseTotal)}
    Trip expenses
    ${fmtMoney(totals.netAfterTrip)}
    Net after trip costs
    ${totals.delivered}
    Delivered stops

    Day rollups

    ${data.dayRollups.map(day => `${day.note || day.closedNote ? `` : ''}`).join('') || ``}
    DayDateRoutesStopsMilesRevenueRoute costLodgingNet after lodgingClosed
    ${escapeHTML(day.label)}${escapeHTML(day.date || '')}${day.totals.routes}${day.totals.stops}${escapeHTML(String(day.totals.miles))}${escapeHTML(fmtMoney(day.totals.revenue))}${escapeHTML(fmtMoney(day.totals.cost))}${escapeHTML(fmtMoney(day.totals.lodging))}${escapeHTML(fmtMoney(day.totals.netAfterLodging))}${day.closedAt ? escapeHTML(fmt(day.closedAt)) : 'Open'}
    ${day.note ? `Day note: ${escapeHTML(day.note)}` : ''}${day.note && day.closedNote ? ' • ' : ''}${day.closedNote ? `Closeout: ${escapeHTML(day.closedNote)}` : ''}
    No day buckets yet.

    Trip expenses

    ${data.tripExpenses.map(item => ``).join('') || ``}
    LabelDateAmountNote
    ${escapeHTML(item.label)}${escapeHTML(item.date || '')}${escapeHTML(fmtMoney(item.amount))}${escapeHTML(item.note || '')}
    No trip-level expenses logged.
    \n '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); `; } function openTripPackDetailModal(tripId){ const trip = readTripPacks().find(item => item.id === cleanStr(tripId)); if(!trip){ toast('Trip pack not found.', 'warn'); return; } const data = getTripPackTotals(trip); const totals = data.totals; const expenseRows = data.tripExpenses.map(item => `
    ${escapeHTML(item.label)}
    ${escapeHTML(item.date || 'No date')} • ${escapeHTML(fmtMoney(item.amount))}${item.note ? ` • ${escapeHTML(item.note)}` : ''}
    `).join('') || `
    No trip-level expenses yet.
    `; openModal(`Trip Pack • ${escapeHTML(trip.title)}`, `
    Edit multi-day trip buckets, lodging, and trip-level expenses without touching the underlying route history.
    ${totals.routes}
    Routes
    ${fmtMoney(totals.netAfterTrip)}
    Net after trip costs
    ${fmtMoney(totals.lodging)}
    Lodging
    ${fmtMoney(totals.tripExpenseTotal)}
    Trip expenses
    ${data.dayRollups.map((day, idx) => `
    ${day.routes.length}
    ${escapeHTML(fmtMoney(day.totals.netAfterLodging))}
    Routes: ${escapeHTML(day.routes.map(route => route.name).join(' • ') || 'None')}
    `).join('') || `
    No day buckets saved yet.
    `}
    ${expenseRows}
    `, ``); $$('[data-trip-day-close-now]').forEach(btn => btn.onclick = ()=>{ const id = btn.getAttribute('data-trip-day-close-now'); const input = $(`[data-trip-day-closed-at="${cssEscape(id)}"]`); if(input) input.value = nowISO().slice(0,16); }); $$('[data-trip-exp-del]').forEach(btn => btn.onclick = ()=>{ const next = normalizeTripPack({ ...trip, tripExpenses: (trip.tripExpenses || []).filter(item => item.id !== btn.getAttribute('data-trip-exp-del')) }); saveTripPack(next); toast('Trip expense removed.', 'warn'); openTripPackDetailModal(trip.id); }); if($('#td_add_expense')) $('#td_add_expense').onclick = ()=>{ const label = cleanStr($('#td_exp_label').value); const amount = cleanStr($('#td_exp_amount').value); if(!label || !amount){ toast('Add label and amount.', 'warn'); return; } const next = normalizeTripPack({ ...trip, tripExpenses: [...(trip.tripExpenses || []), { id: uid(), label, date: cleanStr($('#td_exp_date').value), amount, note: cleanStr($('#td_exp_note').value) }] }); saveTripPack(next); toast('Trip expense added.', 'good'); openTripPackDetailModal(trip.id); }; if($('#td_export')) $('#td_export').onclick = ()=>{ downloadText(buildTripSummaryHtml(readTripPacks().find(item => item.id === trip.id) || trip), `${sanitizeFileName(trip.title)}_${dayISO()}_trip_summary.html`, 'text/html'); toast('Trip summary exported.', 'good'); }; if($('#td_save')) $('#td_save').onclick = ()=>{ const current = readTripPacks().find(item => item.id === trip.id) || trip; const buckets = (current.dayBuckets || []).map((bucket, idx) => ({ ...bucket, label: cleanStr(($(`[data-trip-day-label="${cssEscape(bucket.id)}"]`) || {}).value) || `Day ${idx+1}`, date: cleanStr(($(`[data-trip-day-date="${cssEscape(bucket.id)}"]`) || {}).value), lodgingCost: cleanStr(($(`[data-trip-day-lodging="${cssEscape(bucket.id)}"]`) || {}).value), note: cleanStr(($(`[data-trip-day-note="${cssEscape(bucket.id)}"]`) || {}).value), closedNote: cleanStr(($(`[data-trip-day-close-note="${cssEscape(bucket.id)}"]`) || {}).value), closedAt: cleanStr(($(`[data-trip-day-closed-at="${cssEscape(bucket.id)}"]`) || {}).value) })); saveTripPack({ ...current, title: cleanStr($('#td_title').value) || current.title, note: cleanStr($('#td_note').value), dayBuckets: buckets }); toast('Trip pack updated.', 'good'); closeModal(); openTripPackDetailModal(trip.id); }; } function openTripPackManagerModal(seedRouteIds){ const allRoutes = APP.cached.routes.slice().sort((a,b)=> `${cleanStr(b.date)}${cleanStr(b.updatedAt)}`.localeCompare(`${cleanStr(a.date)}${cleanStr(a.updatedAt)}`)); const seed = new Set((Array.isArray(seedRouteIds) ? seedRouteIds : [seedRouteIds]).map(cleanStr).filter(Boolean)); const trips = readTripPacks(); const selectedDates = Array.from(new Set(allRoutes.filter(route => seed.has(route.id)).map(route => cleanStr(route.date)).filter(Boolean))).sort(); openModal('Trip packs', `
    Group multiple route days into one offline trip wrapper with lodging, day closeouts, and exportable trip summaries.
    ${allRoutes.map(route => ``).join('')}
    ${selectedDates.map((date, idx) => `
    `).join('')}
    ${trips.map(trip => { const totals = getTripPackTotals(trip).totals; return `
    ${escapeHTML(trip.title)}
    ${trip.routeIds.length} routes • ${escapeHTML(fmt(trip.updatedAt))} • Net after trip costs ${escapeHTML(fmtMoney(totals.netAfterTrip))}
    `; }).join('') || `
    No trip packs yet.
    `}
    `, ``); $$('[data-trip-view]').forEach(btn => btn.onclick = ()=> openTripPackDetailModal(btn.getAttribute('data-trip-view'))); $$('[data-trip-export]').forEach(btn => btn.onclick = ()=>{ const trip = readTripPacks().find(item => item.id === btn.getAttribute('data-trip-export')); if(!trip) return; downloadText(buildTripSummaryHtml(trip), `${sanitizeFileName(trip.title)}_${dayISO()}_trip_summary.html`, 'text/html'); toast('Trip summary exported.', 'good'); }); $$('[data-trip-delete]').forEach(btn => btn.onclick = ()=>{ writeTripPacks(readTripPacks().filter(item => item.id !== btn.getAttribute('data-trip-delete'))); toast('Trip pack deleted.', 'warn'); openTripPackManagerModal(seedRouteIds); }); $('#trip_save').onclick = ()=>{ const selectedRoutes = $$('[data-trip-route]:checked').map(node => node.getAttribute('data-trip-route')); if(!selectedRoutes.length){ toast('Select at least one route.', 'warn'); return; } const selectedRouteObjects = APP.cached.routes.filter(route => selectedRoutes.includes(route.id)); const dayBuckets = Array.from(new Set(selectedRouteObjects.map(route => cleanStr(route.date)).filter(Boolean))).sort().map((date, idx) => ({ id: uid(), label: `Day ${idx+1}`, date, routeIds: selectedRouteObjects.filter(route => cleanStr(route.date) === date).map(route => route.id), lodgingCost: cleanStr(($(`[data-trip-seed-lodging="${cssEscape(date)}"]`) || {}).value), note: '', closedAt: '', closedNote: '' })); const trip = saveTripPack({ title: cleanStr($('#trip_title').value) || 'Trip Pack', note: cleanStr($('#trip_note').value), routeIds: selectedRoutes, dayBuckets, tripExpenses: [] }); selectedRoutes.forEach(id => { const route = APP.cached.routes.find(item => item.id === id); if(route){ updateRoute(id, { tripRefs: uniqBy([...(route.tripRefs || []), trip.id], x => x) }); } }); toast('Trip pack saved.', 'good'); closeModal(); openTripPackDetailModal(trip.id); }; } function runLaneIntegrityScan(lane){ const l = cleanStr(lane); if(l === 'route-pack'){ const idx = readRoutePackIndex(); return { lane:l, note:`${idx.length} route-pack index row(s) available.`, proofStates:{ fresh: idx.length > 0, legacy:false, exportImport:false, restore:false, noDeadButtons:false }, structural:{ packIndexRows: idx.length } }; } if(l === 'service-summary'){ const docs = readVaultDocs().filter(doc => ['service-summary','delivery-confirmation'].includes(cleanStr(doc.kind))); return { lane:l, note:`${docs.length} generated signed-summary / delivery-confirmation doc(s) found.`, proofStates:{ fresh: docs.length > 0, legacy:false, exportImport:false, restore:false, noDeadButtons:false }, structural:{ docCount: docs.length } }; } if(l === 'account-code'){ const accounts = getAEFlowAccounts(); const coded = accounts.filter(account => cleanStr(sanitizeAEFlowAccount(account).account_code || deriveAEFlowAccountCode(sanitizeAEFlowAccount(account)))); return { lane:l, note:`${coded.length} AE FLOW account code(s) available for offline lookup.`, proofStates:{ fresh: coded.length > 0, legacy:false, exportImport:false, restore:false, noDeadButtons:false }, structural:{ accountCodes: coded.length } }; } if(l === 'voice-note'){ const docs = readVaultDocs().filter(doc => cleanStr(doc.kind) === 'voice-note'); return { lane:l, note:`${docs.length} voice-note doc-vault record(s) found.`, proofStates:{ fresh: docs.length > 0, legacy:false, exportImport:false, restore:false, noDeadButtons:false }, structural:{ voiceNotes: docs.length } }; } if(l === 'heat-score'){ const accounts = getAEFlowAccounts(); const scored = accounts.filter(account => Number.isFinite(num(getAccountHeatData(account).score))); return { lane:l, note:`${scored.length} AE FLOW account heat score(s) computed.`, proofStates:{ fresh: scored.length > 0, legacy:false, exportImport:false, restore:false, noDeadButtons:false }, structural:{ scoredAccounts: scored.length } }; } if(l === 'pseudo-map-board'){ const routes = APP.cached.routes.filter(route => Array.isArray(route.boardLegNotes) && route.boardLegNotes.length); return { lane:l, note:`${routes.length} route(s) have board leg notes saved.`, proofStates:{ fresh: routes.length > 0, legacy:false, exportImport:false, restore:false, noDeadButtons:false }, structural:{ routeCount: routes.length } }; } if(l === 'trip-pack'){ const trips = readTripPacks(); return { lane:l, note:`${trips.length} trip pack(s) saved locally.`, proofStates:{ fresh: trips.length > 0, legacy:false, exportImport:false, restore:false, noDeadButtons:false }, structural:{ tripCount: trips.length } }; } return { lane:l, note:'No structural scan available for this lane yet.', proofStates:{ fresh:false, legacy:false, exportImport:false, restore:false, noDeadButtons:false }, structural:{} }; } function openValidationModal(){ const lanes = ['route-pack','service-summary','account-code','voice-note','heat-score','pseudo-map-board','trip-pack']; const current = buildValidationSummary(); const overview = lanes.map(lane => { const entry = current.latestByLane[lane]; const states = entry && entry.proofStates ? entry.proofStates : {}; const count = Object.values(states).filter(Boolean).length; return `
    ${escapeHTML(lane)} ${count ? `${count}/5 proofs` : `0/5 proofs`}
    ${entry ? escapeHTML(entry.note || 'Saved proof state') : 'No proof state saved yet.'}
    `; }).join(''); openModal('Proof registry', `
    Track honest proof states before a lane gets marked complete anywhere else. Integrity scan can prefill structural signals, but it is not the same thing as a full click-proof run.
    ${current.entries.length}
    Lane entries
    ${current.totals.fresh}
    Fresh proofs
    ${current.totals.exportImport}
    Export/import proofs
    ${current.totals.noDeadButtons}
    No-dead proofs
    ${overview}
    `, ``); const fillFromLane = ()=>{ const lane = cleanStr($('#pv_lane').value); const entry = current.latestByLane[lane]; if(entry && entry.proofStates){ $('#pv_fresh').checked = !!entry.proofStates.fresh; $('#pv_legacy').checked = !!entry.proofStates.legacy; $('#pv_export').checked = !!entry.proofStates.exportImport; $('#pv_restore').checked = !!entry.proofStates.restore; $('#pv_dead').checked = !!entry.proofStates.noDeadButtons; $('#pv_note').value = entry.note || ''; } }; if($('#pv_lane')) $('#pv_lane').addEventListener('change', fillFromLane); fillFromLane(); if($('#pv_scan')) $('#pv_scan').onclick = ()=>{ const scan = runLaneIntegrityScan($('#pv_lane').value); $('#pv_note').value = scan.note; $('#pv_fresh').checked = !!scan.proofStates.fresh; $('#pv_legacy').checked = !!scan.proofStates.legacy; $('#pv_export').checked = !!scan.proofStates.exportImport; $('#pv_restore').checked = !!scan.proofStates.restore; $('#pv_dead').checked = !!scan.proofStates.noDeadButtons; $('#pv_scan_result').innerHTML = `Structural scan: ${escapeHTML(scan.note)}`; }; if($('#pv_export_summary')) $('#pv_export_summary').onclick = ()=>{ const latest = buildValidationSummary(); const body = lanes.map(lane => { const entry = latest.latestByLane[lane]; const states = entry && entry.proofStates ? entry.proofStates : {}; return `${escapeHTML(lane)}${states.fresh ? '✅' : ''}${states.legacy ? '✅' : ''}${states.exportImport ? '✅' : ''}${states.restore ? '✅' : ''}${states.noDeadButtons ? '✅' : ''}${escapeHTML((entry && entry.note) || '')}`; }).join(''); const html = `Proof Registry

    Proof Registry Summary

    ${body}
    LaneFreshLegacyExportRestoreNo-deadNote
    '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); `; downloadText(html, `proof_registry_${dayISO()}.html`, 'text/html'); toast('Proof summary exported.', 'good'); }; $('#pv_save').onclick = ()=>{ saveProofRegistryEntry({ lane: cleanStr($('#pv_lane').value), note: cleanStr($('#pv_note').value), proofStates: { fresh: !!$('#pv_fresh').checked, legacy: !!$('#pv_legacy').checked, exportImport: !!$('#pv_export').checked, restore: !!$('#pv_restore').checked, noDeadButtons: !!$('#pv_dead').checked } }); toast('Proof state saved.', 'good'); closeModal(); }; } function openOptionalHybridModal(){ const current = readOptionalHybridPlans(); const currentRoute = APP.cached.routes.find(item => item.id === cleanStr(APP.routeId)) || null; const geocodeQueue = readHybridGeocodeQueue(); const syncOutbox = readHybridSyncOutbox(); openModal('Optional hybrid tie-ins', `
    These are optional hybrid helpers only. Offline storage remains the source of truth. You can now save adapter settings, queue route geocode candidates, queue sync snapshots, and export/import the hybrid bundle without forcing network use.
    ${currentRoute ? `` : ''}
    Queued locally • geocode candidates ${geocodeQueue.length} • sync outbox ${syncOutbox.length}${currentRoute ? ` • current route ${escapeHTML(currentRoute.name)}` : ''}
    Latest geocode queue: ${geocodeQueue.slice(0,3).map(item => `${escapeHTML(item.label || item.kind || 'queued')}`).join(' ') || '—'}
    Latest sync outbox: ${syncOutbox.slice(0,3).map(item => `${escapeHTML(item.label || item.kind || 'queued')}`).join(' ') || '—'}
    `, ``); $('#hy_save').onclick = ()=>{ writeOptionalHybridPlans({ geocoding: cleanStr($('#hy_geo').value), sync: cleanStr($('#hy_sync').value), calendar: cleanStr($('#hy_cal').value), cloudBackup: cleanStr($('#hy_cloud').value), messaging: cleanStr($('#hy_msg').value), geocode_mode: cleanStr($('#hy_geo_mode').value), geocode_provider: cleanStr($('#hy_geo_provider').value), geocode_endpoint: cleanStr($('#hy_geo_endpoint').value), sync_mode: cleanStr($('#hy_sync_mode').value), sync_target: cleanStr($('#hy_sync_target').value), sync_endpoint: cleanStr($('#hy_sync_endpoint').value), sync_scope: cleanStr($('#hy_sync_scope').value) }); toast('Hybrid settings saved.', 'good'); const res = $('#hy_result'); if(res) res.innerHTML = `
    Hybrid settings saved locally.
    `; }; if($('#hy_export_bundle')) $('#hy_export_bundle').onclick = ()=> exportOptionalHybridBundle(); if($('#hy_import_bundle')) $('#hy_import_bundle').onclick = ()=>{ const input = document.createElement('input'); input.type = 'file'; input.accept = 'application/json'; input.onchange = async ()=>{ const file = input.files && input.files[0]; if(!file) return; let data = null; try{ data = JSON.parse(await file.text()); }catch(_){ toast('Invalid hybrid bundle JSON.', 'bad'); return; } const result = importOptionalHybridBundle(data); if(result.ok){ toast('Hybrid bundle imported.', 'good'); const res = $('#hy_result'); if(res) res.innerHTML = `
    ${escapeHTML(result.note)}
    `; openOptionalHybridModal(); } else { toast(result.note, 'warn'); } }; input.click(); }; if($('#hy_export_geo_queue')) $('#hy_export_geo_queue').onclick = ()=>{ downloadText(JSON.stringify({ type:'skye-routex-hybrid-geocode-queue-v1', exportedAt: nowISO(), entries: readHybridGeocodeQueue() }, null, 2), `hybrid_geocode_queue_${dayISO()}.json`, 'application/json'); toast('Geocode queue exported.', 'good'); }; if($('#hy_export_sync_queue')) $('#hy_export_sync_queue').onclick = ()=>{ downloadText(JSON.stringify({ type:'skye-routex-hybrid-sync-outbox-v1', exportedAt: nowISO(), entries: readHybridSyncOutbox() }, null, 2), `hybrid_sync_outbox_${dayISO()}.json`, 'application/json'); toast('Sync outbox exported.', 'good'); }; if($('#hy_clear_geo_queue')) $('#hy_clear_geo_queue').onclick = ()=>{ if(confirm('Clear the local geocode queue?')){ writeHybridGeocodeQueue([]); openOptionalHybridModal(); toast('Geocode queue cleared.', 'good'); } }; if($('#hy_clear_sync_queue')) $('#hy_clear_sync_queue').onclick = ()=>{ if(confirm('Clear the local sync outbox?')){ writeHybridSyncOutbox([]); openOptionalHybridModal(); toast('Sync outbox cleared.', 'good'); } }; if($('#hy_queue_geocode')) $('#hy_queue_geocode').onclick = ()=>{ const item = queueHybridGeocodePayload(cleanStr(APP.routeId)); if(!item) return toast('Open a route detail first.', 'warn'); const res = $('#hy_result'); if(res) res.innerHTML = `
    Queued geocode payload for ${escapeHTML(item.label)}
    `; toast('Current route queued for geocoding.', 'good'); }; if($('#hy_queue_sync')) $('#hy_queue_sync').onclick = ()=>{ const item = queueHybridSyncSnapshot(cleanStr(APP.routeId)); if(!item) return toast('Open a route detail first.', 'warn'); const res = $('#hy_result'); if(res) res.innerHTML = `
    Queued sync snapshot for ${escapeHTML(item.label)}
    `; toast('Current route queued for sync.', 'good'); }; } function openAEPackSeedsModal(){ const seeds = readAEPackSeeds(); const rows = seeds.map(seed => `
    ${escapeHTML(seed.label)} ${seed.accounts.length} account${seed.accounts.length===1 ? '' : 's'}
    ${escapeHTML(fmt(seed.created_at || nowISO()))}${seed.meta && seed.meta.filter_mode ? ` • Filter ${escapeHTML(seed.meta.filter_mode)}` : ''}${seed.meta && seed.meta.search ? ` • Search ${escapeHTML(seed.meta.search)}` : ''}${seed.meta && seed.meta.view_label ? ` • View ${escapeHTML(seed.meta.view_label)}` : ''}
    `).join('') || `
    No AE FLOW pack seeds found yet.
    `; openModal('AE FLOW pack seeds', `
    These are filtered AE FLOW account groups saved from the AE app. Use them here to build routes without manually reselecting accounts.
    ${rows}
    `, ``); $$('[data-ae-seed-build]').forEach(btn => btn.onclick = async ()=>{ const seed = readAEPackSeeds().find(item => item.id === btn.getAttribute('data-ae-seed-build')); if(!seed) return toast('Seed not found.', 'warn'); const route = await createRouteFromAEFlowAccounts({ accounts: seed.accounts, name: buildRouteNameFromAccounts(seed.accounts, dayISO()), territory: getAccountTerritories(seed.accounts[0] || {}).join(' / '), routeNotes: `Built from AE FLOW pack seed: ${seed.label}` }); if(route){ toast('Route built from AE pack seed.', 'good'); closeModal(); APP.view = 'routes'; window.location.hash = 'routes'; render(); } }); $$('[data-ae-seed-queue]').forEach(btn => btn.onclick = ()=>{ const seed = readAEPackSeeds().find(item => item.id === btn.getAttribute('data-ae-seed-queue')); if(!seed) return toast('Seed not found.', 'warn'); const count = queueAEPackSeedAccounts(seed); toast(`${count} queued from AE pack seed.`, 'good'); }); $$('[data-ae-seed-export]').forEach(btn => btn.onclick = ()=>{ const seed = readAEPackSeeds().find(item => item.id === btn.getAttribute('data-ae-seed-export')); if(!seed) return; downloadText(JSON.stringify({ type:'skye-ae-flow-routex-pack-seed-v1', exportedAt: nowISO(), ...seed }, null, 2), `${sanitizeFileName(seed.label || 'ae_pack_seed')}_${dayISO()}.json`, 'application/json'); toast('AE pack seed exported.', 'good'); }); $$('[data-ae-seed-delete]').forEach(btn => btn.onclick = ()=>{ deleteAEPackSeed(btn.getAttribute('data-ae-seed-delete')); toast('AE pack seed deleted.', 'warn'); openAEPackSeedsModal(); }); } const __routexRefreshCache = refreshCache; refreshCache = async function(){ await __routexRefreshCache(); APP.cached.routes = APP.cached.routes.map(hydrateRouteRecord); APP.cached.stops = APP.cached.stops.map(hydrateStopRecord); }; const __routexCreateStop = createStop; createStop = async function(routeId, data){ const next = { ...(data || {}) }; next.accountCode = cleanStr(next.accountCode) || deriveAccountCodeFromAccount({ id: next.sourceAccountId, business_email: next.businessEmail, business_name: next.label, phone: next.phone }); next.locationMode = cleanStr(next.locationMode) || deriveLocationMode(next); next.addressMode = cleanStr(next.addressMode) || next.locationMode; next.outcomeCode = normalizeOutcomeCode(next.outcomeCode || next.status || 'pending'); const created = await __routexCreateStop(routeId, next); if(created) await __routexUpdateStop(created.id, { accountCode: next.accountCode, locationMode: next.locationMode, addressMode: next.addressMode, outcomeCode: next.outcomeCode }); return APP.cached.stops.find(item => created && item.id === created.id) || created; }; const __routexUpdateStop = updateStop; updateStop = async function(id, patch){ const stop = APP.cached.stops.find(item => item.id === id) || {}; const next = { ...(patch || {}) }; next.accountCode = cleanStr(next.accountCode || stop.accountCode) || deriveAccountCodeFromStop({ ...stop, ...next }); next.outcomeCode = normalizeOutcomeCode(next.outcomeCode || stop.outcomeCode || next.status || stop.status || 'pending'); next.locationMode = cleanStr(next.locationMode || stop.locationMode) || deriveLocationMode({ ...stop, ...next }); if(!cleanStr(next.addressMode)) next.addressMode = next.locationMode; return __routexUpdateStop(id, next); }; const __routexApplyStopStatus = applyStopStatus; applyStopStatus = async function(stopId, status, options){ const outcomeCode = normalizeOutcomeCode((options && options.outcomeCode) || status); const meta = outcomeDef(outcomeCode); const updated = await __routexApplyStopStatus(stopId, meta.status, { ...(options || {}), outcomeCode, createTask: (options && typeof options.createTask !== 'undefined') ? options.createTask : meta.requiresFollowupTask }); if(updated){ await __routexUpdateStop(updated.id, { outcomeCode, locationMode: deriveLocationMode(updated), addressMode: deriveLocationMode(updated), accountCode: cleanStr(updated.accountCode) || deriveAccountCodeFromStop(updated) }); const stop = APP.cached.stops.find(item => item.id === updated.id) || hydrateStopRecord(updated); const route = APP.cached.routes.find(item => item.id === stop.routeId); const eventEntry = { id: uid(), eventType:'quick_action', actionCode: outcomeCode, clientKey: cleanStr(stop.clientKey) || buildClientKeyFromStop(stop), routeId: stop.routeId, stopId: stop.id, createdAt: nowISO(), uiMessage: `${meta.label} saved for ${stop.label}`, exportImpact: true, undoSupported: true }; writePlainJSON(ROUTEX_LOOKUP_HISTORY_KEY, [eventEntry, ...(readPlainJSON(ROUTEX_LOOKUP_HISTORY_KEY, []) || [])].slice(0, 400)); if(route) await appendRouteEvent(route.id, 'quick-action', eventEntry.uiMessage, eventEntry); return APP.cached.stops.find(item => item.id === stop.id) || stop; } return updated; }; buildServiceSummaryHtml = buildEnhancedServiceSummaryHtml; const __routexOpenDocVaultModal = openDocVaultModal; openDocVaultModal = function(routeId, stopId, account){ __routexOpenDocVaultModal(routeId, stopId, account); const actionsRow = $('#modalBody .row'); if(actionsRow && !$('#dv_voice')){ const voiceBtn = document.createElement('button'); voiceBtn.className = 'btn'; voiceBtn.id = 'dv_voice'; voiceBtn.textContent = 'Voice note'; actionsRow.appendChild(voiceBtn); voiceBtn.onclick = ()=> openVoiceNoteModal(routeId, stopId, account); } if(actionsRow && !$('#dv_delivery')){ const stop = stopId ? APP.cached.stops.find(item => item.id === stopId) : null; const route = routeId ? APP.cached.routes.find(item => item.id === routeId) : null; const deliveryBtn = document.createElement('button'); deliveryBtn.className = 'btn'; deliveryBtn.id = 'dv_delivery'; deliveryBtn.textContent = 'Delivery Confirmation'; actionsRow.appendChild(deliveryBtn); deliveryBtn.onclick = ()=>{ if(!route) return toast('Open this from a route to build a delivery confirmation.', 'warn'); const targets = stop ? [stop] : stopsForRoute(route.id).filter(item => item.status === 'delivered'); if(!targets.length) return toast('No delivered stop is available for confirmation yet.', 'warn'); const top = targets[0]; makeHtmlDocEntry({ routeId: route.id, stopId: top.id, sourceAccountId: top.sourceAccountId, businessEmail: top.businessEmail, businessName: top.label, title: `${top.label} — Delivery Confirmation`, kind:'delivery-confirmation', tags:['delivery-confirmation','completion-proof'] }, buildDeliveryConfirmationHtml(route, targets)); toast('Delivery confirmation saved.', 'good'); closeModal(); }; } }; const __routexOpenAccountDossierModal = openAccountDossierModal; openAccountDossierModal = function(account){ __routexOpenAccountDossierModal(account); const a = sanitizeAEFlowAccount(account); const docs = getVaultDocsForAccount(a); const latestSummary = docs.find(doc => ['service-summary','delivery-confirmation'].includes(cleanStr(doc.kind))); const footer = $('#modalFoot'); if(footer && !$('#dos_code')){ const codeBtn = document.createElement('button'); codeBtn.className = 'btn'; codeBtn.id = 'dos_code'; codeBtn.textContent = `Code ${deriveAccountCodeFromAccount(a)}`; footer.insertBefore(codeBtn, footer.firstChild); codeBtn.onclick = ()=> navigator.clipboard && navigator.clipboard.writeText(buildQRPayloadForRecord(a)).then(()=> toast('Lookup payload copied.', 'good')).catch(()=> toast('Copy failed.', 'warn')); } if(footer && latestSummary && !$('#dos_latest_doc')){ const docBtn = document.createElement('button'); docBtn.className = 'btn'; docBtn.id = 'dos_latest_doc'; docBtn.textContent = 'Latest summary'; footer.insertBefore(docBtn, footer.firstChild); docBtn.onclick = ()=> downloadText(cleanStr(latestSummary.html || ''), `${sanitizeFileName(latestSummary.title || 'summary')}.html`, 'text/html'); } }; const __routexCollectRoutePackData = collectRoutePackData; collectRoutePackData = function(routeIds){ const pack = __routexCollectRoutePackData(routeIds); const stopIds = new Set((pack.stops || []).map(stop => cleanStr(stop.id))); const routeIdsSet = new Set((pack.routes || []).map(route => cleanStr(route.id))); const taskIds = new Set((pack.tasks || []).map(task => cleanStr(task.id))); pack.docs = readVaultDocs().filter(doc => routeIdsSet.has(cleanStr(doc.routeId)) || stopIds.has(cleanStr(doc.stopId)) || taskIds.has(cleanStr(doc.refId))); pack.reminders = readLocalReminders().filter(rem => routeIdsSet.has(cleanStr(rem.refId)) || stopIds.has(cleanStr(rem.refId)) || taskIds.has(cleanStr(rem.refId))); pack.economicsRows = []; (pack.routes || []).forEach(route => { const fuel = normalizeFuelEntries(route.fuelEntries).map(entry => ({ kind:'fuel', routeId: route.id, routeName: route.name, stopId:'', amount: entry.total, qty: entry.gallons, note: entry.note, station: entry.station })); const expenses = normalizeExpenseEntries(route.expenseEntries).map(entry => ({ kind:'expense', routeId: route.id, routeName: route.name, stopId: entry.stopId, amount: entry.total, qty:'', note: entry.note, station:'' })); const materials = normalizeMaterialEntries(route.materialEntries).map(entry => ({ kind:'material', routeId: route.id, routeName: route.name, stopId: entry.stopId, amount: entry.totalCost, qty: entry.qty, note: entry.note, station:'' })); const collections = normalizeCollectionEntries(route.collectionEntries).map(entry => ({ kind:'collection', routeId: route.id, routeName: route.name, stopId: entry.stopId, amount: entry.amount, qty:'', note: entry.note, station:'' })); pack.economicsRows.push(...fuel, ...expenses, ...materials, ...collections); }); return pack; }; async function buildRoutePackPayload(routeIds, includePhotos){ const pack = collectRoutePackData(routeIds); if(!pack.routes.length) return null; const packId = uid(); const photoPayload = []; if(includePhotos){ for(const photo of pack.photos){ if(!photo.blob) continue; const dataUrl = await blobToDataURL(photo.blob); photoPayload.push({ id: photo.id, routeId: photo.routeId, stopId: photo.stopId, name: photo.name, type: photo.type, size: photo.size || photo.blob.size, createdAt: photo.createdAt, dataUrl }); } } const normalizedStops = (pack.stops || []).map(stop => hydrateStopRecord(stop)); const label = pack.routes.length === 1 ? sanitizeFileName(pack.routes[0].name || 'route_pack') : `route_pack_${pack.routes.length}_routes`; const payload = { type:'skye-route-pack-v2', packId, label, exportedAt: nowISO(), includePhotos: !!includePhotos, routes: pack.routes.map(hydrateRouteRecord), stops: normalizedStops, photos: includePhotos ? photoPayload : [], tasks: pack.tasks, docs: pack.docs, reminders: pack.reminders, economicsRows: pack.economicsRows, locationQuality: normalizedStops.map(stop => ({ stopId: stop.id, clientKey: stop.clientKey, accountCode: stop.accountCode, locationMode: stop.locationMode, locationLabel: locationModeLabel(stop.locationMode), displayLabel: buildLocationLabel(stop) })), fingerprint: buildRoutePackFingerprint({ routes: pack.routes, stops: normalizedStops, docs: pack.docs, tasks: pack.tasks, reminders: pack.reminders }) }; return { pack, payload, normalizedStops, label, jsonName: `${label}_${dayISO()}.json`, summaryName: `${label}_${dayISO()}_summary.html` }; } async function runRoutePackRoundtripProof(mode){ const pair = await ensureProofRouteAndStop(`Proof route-pack roundtrip • ${dayISO()}`, false); if(!pair || !pair.route) return { ok:false, exportImport:false, note:'Unable to create a proof route for route-pack validation.' }; const built = await buildRoutePackPayload([pair.route.id], false); if(!built) return { ok:false, exportImport:false, note:'Unable to build a route-pack payload for proof.' }; const before = { routes: APP.cached.routes.length, stops: APP.cached.stops.length, packs: readRoutePackIndex().length, docs: readVaultDocs().length }; await importRoutePack(built.payload, cleanStr(mode) || 'clone'); await refreshCache(); const after = { routes: APP.cached.routes.length, stops: APP.cached.stops.length, packs: readRoutePackIndex().length, docs: readVaultDocs().length }; const imported = readRoutePackIndex().filter(item => cleanStr(item.fingerprint) === cleanStr(built.payload.fingerprint)); const ok = imported.length > 0 && after.packs >= before.packs && after.stops >= before.stops; return { ok, exportImport: imported.length > 0, note:`Route-pack roundtrip ${ok ? 'passed' : 'needs review'} • fingerprint matches ${imported.length} • routes ${before.routes}→${after.routes} • stops ${before.stops}→${after.stops}` }; } async function runRoutePackSecondDeviceStyleProof(){ const pair = await ensureProofRouteAndStop(`Proof route-pack transfer • ${dayISO()}`, false); if(!pair || !pair.route) return { ok:false, exportImport:false, note:'Unable to create a proof route for second-device transfer rehearsal.' }; const built = await buildRoutePackPayload([pair.route.id], false); if(!built || !built.payload) return { ok:false, exportImport:false, note:'Unable to build a transferable route-pack payload.' }; const serialized = JSON.stringify(built.payload, null, 2); const reopened = JSON.parse(serialized); const preview = routePackPreviewSummary(reopened); const before = buildRestoreCounts(); await importRoutePack(reopened, 'clone'); await refreshCache(); const after = buildRestoreCounts(); const imported = readRoutePackIndex().filter(item => cleanStr(item.fingerprint) === cleanStr(reopened.fingerprint)); const ok = !!preview.routes && imported.length > 0 && after.routes >= before.routes && after.stops >= before.stops; const log = pushRoutePackTransferLog({ lane:'route-pack', proofKind:'second-device-style', routeId: pair.route.id, packId: cleanStr(reopened.packId), fingerprint: cleanStr(reopened.fingerprint), preview, before, after, importedMatches: imported.length, ok, note:'Serialized + reopened route-pack payload was previewed and cloned back in locally.' }); return { ok, exportImport: ok, note:`Second-device-style route-pack rehearsal ${ok ? 'passed' : 'needs review'} • preview ${preview.routes} route(s)/${preview.stops} stop(s) • fingerprint matches ${imported.length} • log ${cleanStr(log.id)}` }; } async function runServiceSummaryProof(){ const pair = await ensureProofRouteAndStop(`Proof service-summary • ${dayISO()}`, false); if(!pair || !pair.route || !pair.stop) return { ok:false, exportImport:false, note:'Unable to create a proof route/stop for service-summary validation.' }; makeHtmlDocEntry({ routeId: pair.route.id, stopId: pair.stop.id, sourceAccountId: pair.stop.sourceAccountId, businessEmail: pair.stop.businessEmail, businessName: pair.stop.label, title:`${pair.stop.label} — Proof Service Summary`, kind:'service-summary', tags:['proof-fixture','service-slip'] }, buildServiceSummaryHtml(pair.route, pair.stop)); makeHtmlDocEntry({ routeId: pair.route.id, stopId: pair.stop.id, sourceAccountId: pair.stop.sourceAccountId, businessEmail: pair.stop.businessEmail, businessName: pair.stop.label, title:`${pair.stop.label} — Proof Delivery Confirmation`, kind:'delivery-confirmation', tags:['proof-fixture','delivery-confirmation'] }, buildDeliveryConfirmationHtml(pair.route, pair.stop)); const built = await buildRoutePackPayload([pair.route.id], false); const docs = ((built && built.payload && built.payload.docs) || []).filter(doc => ['service-summary','delivery-confirmation'].includes(cleanStr(doc.kind))); return { ok: docs.length >= 2, exportImport: docs.length >= 1, note:`Service-summary proof captured ${docs.length} signed doc(s) into the local doc vault / route-pack payload.` }; } async function runAccountCodeProof(){ await seedValidationFixture('account-code', 'fresh'); const account = getAEFlowAccounts()[0]; if(!account) return { ok:false, exportImport:false, note:'No AE FLOW account available for account-code proof.' }; const code = deriveAccountCodeFromAccount(account); const payload = buildQRPayloadForRecord(account); const codeMatches = lookupOfflineMatches(code); const payloadMatches = lookupOfflineMatches(payload); const ok = codeMatches.some(item => item.type === 'account' && cleanStr(item.code) === cleanStr(code)) && payloadMatches.length > 0; return { ok, exportImport:false, note:`Account-code lookup ${ok ? 'passed' : 'needs review'} • code matches ${codeMatches.length} • payload matches ${payloadMatches.length}` }; } async function runHeatScoreProof(){ await seedValidationFixture('heat-score', 'fresh'); const account = getAEFlowAccounts()[0]; if(!account) return { ok:false, exportImport:false, note:'No AE FLOW account available for heat-score proof.' }; const before = getAccountHeatData(account).score; let route = await createRouteFromAEFlowAccounts({ accounts:[account], name:`Heat Proof • ${dayISO()}`, clearQueued:false, routeNotes:'Heat score proof route' }); await refreshCache(); route = APP.cached.routes.find(item => item.id === route.id) || route; let stop = route ? stopsForRoute(route.id)[0] : null; if(route && !stop){ stop = await createStop(route.id, { label:account.business_name || 'Heat Proof Client', address:buildExactAddress(account) || account.service_area || '123 Heat Ave, Phoenix, AZ', contact:account.contact_name || 'Heat Contact', notes:'Heat score proof stop', businessEmail:account.business_email || 'heatproof@example.com', sourceAccountId:account.id, exactAddress:buildExactAddress(account) || '', accountCode:deriveAccountCodeFromAccount(account) }); await refreshCache(); stop = APP.cached.stops.find(item => item.id === stop.id) || stop; } if(!stop) return { ok:false, exportImport:false, note:'Unable to create a stop for heat-score proof.' }; await updateStop(stop.id, { status:'delivered', deliveredAt: nowISO(), completedAt: nowISO(), attemptCount: Math.max(1, Math.round(num(stop.attemptCount || 0) || 0) + 1), sourceAccountId: account.id, businessEmail: account.business_email || stop.businessEmail, accountCode: deriveAccountCodeFromAccount(account), outcomeCode:'delivered' }); await refreshCache(); const after = getAccountHeatData(account).score; return { ok: after >= before, exportImport:false, note:`Heat-score proof ${before} → ${after}.` }; } async function runVoiceNoteProof(){ const pair = await ensureProofRouteAndStop(`Proof voice-note • ${dayISO()}`, false); if(!pair || !pair.route || !pair.stop) return { ok:false, exportImport:false, note:'Unable to create a proof route/stop for voice-note validation.' }; saveVaultDoc({ routeId: pair.route.id, stopId: pair.stop.id, sourceAccountId: pair.stop.sourceAccountId, businessEmail: pair.stop.businessEmail, businessName: pair.stop.label, title:`${pair.stop.label} — Proof Voice Note`, kind:'voice-note', mime:'audio/webm', tags:['proof-fixture','voice-note'], note:'Synthetic voice-note proof saved without requiring live microphone hardware.', html:'

    Proof voice-note placeholder.

    '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); ' }); const built = await buildRoutePackPayload([pair.route.id], false); const docs = ((built && built.payload && built.payload.docs) || []).filter(doc => cleanStr(doc.kind) === 'voice-note'); return { ok: docs.length > 0, exportImport: docs.length > 0, note:`Voice-note proof captured ${docs.length} doc(s) into the local doc vault / route-pack payload.` }; } async function runPseudoMapBoardProof(){ const pair = await ensureProofRouteAndStop(`Proof pseudo-map • ${dayISO()}`, false); if(!pair || !pair.route) return { ok:false, exportImport:false, note:'Unable to create a proof route for pseudo-map validation.' }; let stops = stopsForRoute(pair.route.id); while(stops.length < 2){ await createStop(pair.route.id, { label:`Proof Leg ${stops.length + 1}`, address:`${stops.length + 2} Proof Ave, Phoenix, AZ`, contact:'Proof Contact', notes:'Pseudo-map proof stop', businessEmail:`proof_leg_${stops.length + 1}@example.com`, serviceArea:'Phoenix', exactAddress:`${stops.length + 2} Proof Ave, Phoenix, AZ` }); await refreshCache(); stops = stopsForRoute(pair.route.id); } const notes = [{ id: uid(), fromStopId: stops[0].id, toStopId: stops[1].id, note:'Gate code 1357 • park behind building • use north entrance' }]; await updateRoute(pair.route.id, { boardLegNotes: notes }); await appendRouteEvent(pair.route.id, 'board-updated', 'Pseudo-map board notes updated via proof bundle', { noteCount: notes.length }); await refreshCache(); const live = APP.cached.routes.find(item => item.id === pair.route.id); const count = Array.isArray(live && live.boardLegNotes) ? live.boardLegNotes.length : 0; return { ok: count > 0, exportImport:false, note:`Pseudo-map proof saved ${count} board leg note(s) across ${stops.length} stop(s).` }; } async function runTripPackProof(){ const pair = await ensureProofRouteAndStop(`Proof trip-pack • ${dayISO()}`, false); if(!pair || !pair.route) return { ok:false, exportImport:false, note:'Unable to create a proof route for trip-pack validation.' }; const trip = saveTripPack({ title:`Proof trip ${dayISO()}`, note:'Proof fixture trip pack', routeIds:[pair.route.id], dayBuckets:[{ id:uid(), label:'Day 1', date:pair.route.date || dayISO(), routeIds:[pair.route.id], lodgingCost:'24.50', note:'Proof day bucket', closedAt:nowISO(), closedNote:'Proof closeout' }], tripExpenses:[{ id:uid(), label:'Parking', date:dayISO(), amount:'8.00', note:'Proof parking' }] }); const live = readTripPacks().find(item => item.id === trip.id) || trip; const totals = getTripPackTotals(live).totals; const html = buildTripSummaryHtml(live); return { ok: totals.routes >= 1 && /Day rollups/i.test(html), exportImport:/Day rollups/i.test(html), note:`Trip-pack proof totals • routes ${totals.routes} • stops ${totals.stops} • net ${fmtMoney(totals.netAfterTrip)}` }; } async function runProofLaneBundleSmart(lane){ const target = cleanStr(lane); const notes = []; const states = { fresh:false, legacy:false, exportImport:false, restore:false, noDeadButtons:false }; const freshNote = await seedValidationFixture(target, 'fresh'); if(freshNote){ notes.push(freshNote); states.fresh = true; } const legacyNote = await seedValidationFixture(target, 'legacy'); if(legacyNote){ notes.push(legacyNote); states.legacy = true; } let result = { ok:false, exportImport:false, note:'No lane-specific proof runner available.' }; if(target === 'route-pack') result = await runRoutePackRoundtripProof('clone'); else if(target === 'service-summary') result = await runServiceSummaryProof(); else if(target === 'account-code') result = await runAccountCodeProof(); else if(target === 'voice-note') result = await runVoiceNoteProof(); else if(target === 'heat-score') result = await runHeatScoreProof(); else if(target === 'pseudo-map-board') result = await runPseudoMapBoardProof(); else if(target === 'trip-pack') result = await runTripPackProof(); if(result && result.note) notes.push(result.note); states.exportImport = !!(result && result.exportImport); let transferProbe = null; if(target === 'route-pack'){ transferProbe = await runRoutePackSecondDeviceStyleProof(); if(transferProbe && transferProbe.note) notes.push(transferProbe.note); states.exportImport = states.exportImport || !!(transferProbe && transferProbe.exportImport); } const actionProbe = await runLaneActionProbe(target); if(actionProbe && actionProbe.note) notes.push(actionProbe.note); states.noDeadButtons = !!(actionProbe && actionProbe.noDeadButtons); const restoreProbe = await runBackupRoundtripProof(target); if(restoreProbe && restoreProbe.note) notes.push(restoreProbe.note); states.restore = !!(restoreProbe && restoreProbe.restore); const restoreLoopProbe = await runHistoricalRestoreLoopProof(target); if(restoreLoopProbe && restoreLoopProbe.note) notes.push(restoreLoopProbe.note); states.restore = states.restore || !!(restoreLoopProbe && restoreLoopProbe.restore); const scan = runLaneIntegrityScan(target); states.noDeadButtons = states.noDeadButtons || !!scan.proofStates.noDeadButtons; states.restore = states.restore || !!scan.proofStates.restore; states.exportImport = states.exportImport || !!scan.proofStates.exportImport; if(scan.note) notes.push(scan.note); const saved = saveProofRegistryEntry({ lane: target, note: notes.filter(Boolean).join(' • '), proofStates: states }); return { saved, scan, states, result, transferProbe, actionProbe, restoreProbe, restoreLoopProbe }; } exportRoutePackJSON = async function(routeIds, includePhotos){ const built = await buildRoutePackPayload(routeIds, includePhotos); if(!built){ toast('Select at least one route.', 'warn'); return; } const packetText = JSON.stringify(built.payload, null, 2); if(!applyImageStorageWarning(packetText.length, 'Route pack')) return; downloadText(packetText, built.jsonName, 'application/json'); downloadText(buildRoutePackSummaryHtml(built.payload), built.summaryName, 'text/html'); saveRoutePackIndexRecord({ id: built.payload.packId, label: built.label, routeIds: built.pack.routes.map(route => route.id), clientKeys: built.normalizedStops.map(stop => stop.clientKey), accountCodes: built.normalizedStops.map(stop => stop.accountCode), exportedAt: built.payload.exportedAt, source:'export', jsonName: built.jsonName, summaryName: built.summaryName, fingerprint: built.payload.fingerprint, counts: { routes: built.pack.routes.length, stops: built.normalizedStops.length, docs: built.pack.docs.length, reminders: built.pack.reminders.length, tasks: built.pack.tasks.length } }); toast('Route pack exported with summary.', 'good'); }; openRoutePackImportModal = function(){ openModal('Import route pack', `
    Preview a route pack before import. Clone creates local copies. Merge tries to attach imported assets into same-name same-date routes already on this device.
    Choose a route pack to preview.
    `, ``); const previewFile = async ()=>{ const inp = $('#rp_import_file'); if(!inp.files || !inp.files.length){ $('#rp_preview').innerHTML = `
    Choose a route pack to preview.
    `; return; } let data = null; try{ data = JSON.parse(await inp.files[0].text()); }catch(_){ $('#rp_preview').innerHTML = `
    Invalid JSON
    `; return; } const preview = routePackPreviewSummary(data); $('#rp_preview').innerHTML = `
    ${preview.routes}
    Routes
    ${preview.stops}
    Stops
    ${preview.docs}
    Docs
    ${preview.tasks}
    Tasks
    ${preview.reminders}
    Reminders
    Fingerprint: ${escapeHTML(preview.fingerprint || '')}${preview.sameFingerprint.length ? ` • Existing matching fingerprint: ${escapeHTML(preview.sameFingerprint.join(' • '))}` : ''}
    ${preview.duplicates.length ? `
    Same-name same-date collisions: ${escapeHTML(preview.duplicates.join(' • '))}
    ` : `
    No same-name/date collisions detected on this device.
    `}`; }; if($('#rp_import_file')) $('#rp_import_file').addEventListener('change', previewFile); if($('#rp_import_go')) $('#rp_import_go').onclick = async ()=>{ const inp = $('#rp_import_file'); if(!inp.files || !inp.files.length){ toast('Choose a file.', 'warn'); return; } let data = null; try{ data = JSON.parse(await inp.files[0].text()); }catch(_){ toast('Invalid JSON.', 'bad'); return; } await importRoutePack(data, $('#rp_import_mode').value || 'clone'); closeModal(); render(); }; }; importRoutePack = async function(data, mode){ if(!data || !Array.isArray(data.routes) || !Array.isArray(data.stops)){ toast('Route pack format not recognized.', 'bad'); return; } const routeIdMap = new Map(); const stopIdMap = new Map(); const existingByKey = new Map(APP.cached.routes.map(route => [`${cleanStr(route.date)}|${cleanStr(route.name).toLowerCase()}`, route])); for(const route of data.routes){ const key = `${cleanStr(route.date)}|${cleanStr(route.name).toLowerCase()}`; const existing = existingByKey.get(key); if(mode === 'merge' && existing){ routeIdMap.set(route.id, existing.id); await updateRoute(existing.id, { routeNotes: joinNonEmpty([existing.routeNotes, cleanStr(route.routeNotes)], ' • '), routePackRefs: uniqBy([...(existing.routePackRefs || []), cleanStr(data.packId || '')].filter(Boolean), x => x) }); await appendRouteEvent(existing.id, 'route-pack-merged', 'Imported route pack merged into existing route', { sourcePackId: cleanStr(data.packId), sourceRouteId: cleanStr(route.id) }); }else{ const nextId = uid(); routeIdMap.set(route.id, nextId); await put(APP.db, 'routes', hydrateRouteRecord({ ...route, id: nextId, updatedAt: nowISO(), routePackRefs: uniqBy([...(route.routePackRefs || []), cleanStr(data.packId || '')].filter(Boolean), x => x) })); } } await refreshCache(); for(const stop of data.stops){ const mappedRouteId = routeIdMap.get(stop.routeId) || stop.routeId; const existingStops = stopsForRoute(mappedRouteId); let matched = null; if(mode === 'merge') matched = existingStops.find(item => cleanStr(item.label).toLowerCase() === cleanStr(stop.label).toLowerCase() && buildLocationLabel(item).toLowerCase() === buildLocationLabel(stop).toLowerCase()); if(matched){ stopIdMap.set(stop.id, matched.id); await updateStop(matched.id, { notes: joinNonEmpty([matched.notes, cleanStr(stop.notes)], ' • '), outcomeCode: normalizeOutcomeCode(stop.outcomeCode || stop.status), locationMode: deriveLocationMode(stop), accountCode: cleanStr(stop.accountCode) || deriveAccountCodeFromStop(stop) }); }else{ const nextId = uid(); stopIdMap.set(stop.id, nextId); await put(APP.db, 'stops', hydrateStopRecord({ ...stop, id: nextId, routeId: mappedRouteId, updatedAt: nowISO() })); } } if(Array.isArray(data.photos)){ for(const photo of data.photos){ if(!photo.dataUrl) continue; const blob = dataURLToBlob(photo.dataUrl); await put(APP.db, 'photos', { id: uid(), routeId: routeIdMap.get(photo.routeId) || photo.routeId, stopId: stopIdMap.get(photo.stopId) || photo.stopId, name: photo.name, type: photo.type, size: photo.size || blob.size, blob, createdAt: photo.createdAt || nowISO() }); } } if(Array.isArray(data.tasks)){ const nextTasks = data.tasks.map(task => normalizeRouteTask({ ...task, id: uid(), routeId: routeIdMap.get(task.routeId) || task.routeId, stopId: stopIdMap.get(task.stopId) || task.stopId, updatedAt: nowISO(), createdAt: task.createdAt || nowISO() })); writeRouteTasks([...nextTasks, ...readRouteTasks()].slice(0, 1000)); } if(Array.isArray(data.docs)){ const docs = data.docs.map(doc => normalizeVaultDoc({ ...doc, id: uid(), routeId: routeIdMap.get(doc.routeId) || doc.routeId, stopId: stopIdMap.get(doc.stopId) || doc.stopId, updatedAt: nowISO(), createdAt: doc.createdAt || nowISO() })); writeVaultDocs([...docs, ...readVaultDocs()].slice(0, 2000)); } if(Array.isArray(data.reminders)){ const reminders = data.reminders.map(rem => normalizeLocalReminder({ ...rem, id: uid(), refId: stopIdMap.get(rem.refId) || routeIdMap.get(rem.refId) || rem.refId, updatedAt: nowISO(), createdAt: rem.createdAt || nowISO() })); writeLocalReminders([...reminders, ...readLocalReminders()].slice(0, 300)); } saveRoutePackIndexRecord({ id: cleanStr(data.packId) || uid(), label: cleanStr(data.label) || 'Imported route pack', routeIds: Array.from(routeIdMap.values()), clientKeys: (data.stops || []).map(stop => cleanStr(stop.clientKey)).filter(Boolean), accountCodes: (data.stops || []).map(stop => cleanStr(stop.accountCode || deriveAccountCodeFromStop(stop))).filter(Boolean), importedAt: nowISO(), source:'import', fingerprint: cleanStr(data.fingerprint) || buildRoutePackFingerprint(data), counts: { routes: (data.routes || []).length, stops: (data.stops || []).length, docs: (data.docs || []).length, reminders: (data.reminders || []).length, tasks: (data.tasks || []).length } }); toast(`Route pack import complete (${mode}).`, 'good'); await refreshCache(); }; const __routexViewAEFlowConnector = viewAEFlowConnector; viewAEFlowConnector = function(){ __routexViewAEFlowConnector(); const cards = $$('.aeConnectorRow'); cards.forEach(node => { const key = node.getAttribute('data-key'); const account = getAEFlowAccounts().find(item => getAEFlowAccountKey(item) === key); if(!account) return; const heat = getAccountHeatData(account); const packs = accountRoutePackRefs(account); const title = node.querySelector('.name'); if(title && !title.innerHTML.includes('Heat ')) title.innerHTML += ` Heat ${heat.score} ${escapeHTML(deriveAccountCodeFromAccount(account))}${packs.length ? ` ${packs.length} pack${packs.length===1 ? '' : 's'}` : ''}`; const meta = node.querySelector('.meta .sub:last-child'); if(meta && !node.innerHTML.includes('Heat basis:')) meta.insertAdjacentHTML('beforeend', `
    Heat basis: ${escapeHTML(heat.explanation.join(' • '))}`); }); const host = $('#content .card .row'); if(host && !$('#aeConnectorLookup')){ const lookupBtn = document.createElement('button'); lookupBtn.className = 'btn'; lookupBtn.id = 'aeConnectorLookup'; lookupBtn.textContent = 'Lookup by code'; host.appendChild(lookupBtn); lookupBtn.onclick = ()=> openLookupModal(); } }; const __routexViewRouteDetail = viewRouteDetail; viewRouteDetail = async function(route){ await __routexViewRouteDetail(route); const routeId = cleanStr(route && route.id) || cleanStr(APP.routeId); const headerCard = $('#content .card'); if(headerCard && !$('#routeDirectiveBar')){ const row = document.createElement('div'); row.className = 'row'; row.id = 'routeDirectiveBar'; row.style.flexWrap = 'wrap'; row.style.margin = '12px 0'; row.innerHTML = ``; headerCard.insertBefore(row, headerCard.querySelector('.list') || headerCard.lastChild); $('#routeLookupBtn').onclick = ()=> openLookupModal(); $('#routeBoardBtn').onclick = ()=> openPseudoMapBoardModal(routeId); $('#routeTripBtn').onclick = ()=> openTripPackManagerModal([routeId]); $('#routeValidateBtn').onclick = ()=> openValidationModal(); $('#routeConfirmBtn').onclick = ()=>{ const current = APP.cached.routes.find(item => item.id === routeId); const deliveredStops = stopsForRoute(routeId).filter(item => item.status === 'delivered'); if(!current || !deliveredStops.length) return toast('No delivered stop is available for confirmation yet.', 'warn'); const top = deliveredStops[0]; makeHtmlDocEntry({ routeId: routeId, stopId: top.id, sourceAccountId: top.sourceAccountId, businessEmail: top.businessEmail, businessName: top.label, title: `${top.label} — Delivery Confirmation`, kind:'delivery-confirmation', tags:['delivery-confirmation','completion-proof'] }, buildDeliveryConfirmationHtml(current, deliveredStops)); toast('Delivery confirmation saved.', 'good'); }; } stopsForRoute(routeId).forEach(stop => { const btn = $(`[data-edit-stop="${CSS.escape(stop.id)}"]`); const card = btn ? btn.closest('.item') : null; if(!card || card.innerHTML.includes('Account code:')) return; const sub = card.querySelector('.sub'); if(sub) sub.insertAdjacentHTML('beforeend', `
    Account code: ${escapeHTML(stop.accountCode || deriveAccountCodeFromStop(stop))} • Location quality: ${escapeHTML(locationModeLabel(stop.locationMode || deriveLocationMode(stop)))}${accountRoutePackRefs({ id: stop.sourceAccountId, business_email: stop.businessEmail, business_name: stop.label }).length ? ` • Route packs: ${accountRoutePackRefs({ id: stop.sourceAccountId, business_email: stop.businessEmail, business_name: stop.label }).length}` : ''}`); const actions = card.querySelector('.row'); if(actions && !actions.querySelector(`[data-voice-stop="${stop.id}"]`)){ const voiceBtn = document.createElement('button'); voiceBtn.className = 'btn small'; voiceBtn.setAttribute('data-voice-stop', stop.id); voiceBtn.textContent = 'Voice Note'; actions.appendChild(voiceBtn); voiceBtn.onclick = ()=> openVoiceNoteModal(routeId, stop.id); } }); }; const __routexViewExport = viewExport; viewExport = async function(){ await __routexViewExport(); const host = $('#content .card .row'); if(host && !$('#ex_validate')){ const validateBtn = document.createElement('button'); validateBtn.className = 'btn'; validateBtn.id = 'ex_validate'; validateBtn.textContent = 'Proof Registry'; host.appendChild(validateBtn); validateBtn.onclick = ()=> openValidationModal(); } }; const __routexViewSettings = viewSettings; viewSettings = async function(){ await __routexViewSettings(); const grid = $('#content .grid'); if(grid && !$('#routexDirectiveSettingsCard')){ const card = document.createElement('div'); card.className = 'card'; card.id = 'routexDirectiveSettingsCard'; card.style.gridColumn = 'span 12'; const validation = buildValidationSummary(); const trips = readTripPacks(); const aeSeeds = readAEPackSeeds(); const snaps = readProofSnapshots(); card.innerHTML = `

    Directive-first extras

    These are the remaining directive lanes now started inside Routex without replacing the core product.
    ${readRoutePackIndex().length}
    Route packs indexed
    ${trips.length}
    Trip packs
    ${validation.entries.length}
    Proof entries
    ${aeSeeds.length}
    AE pack seeds
    ${snaps.length}
    Proof snapshots
    ${readHybridGeocodeQueue().length + readHybridSyncOutbox().length}
    Hybrid queued items
    ${readRoutePackTransferLog().length}
    Transfer proofs
    ${readProofRestoreGenerations().length}
    Restore logs
    `; grid.appendChild(card); $('#st_trip_packs').onclick = ()=> openTripPackManagerModal(); $('#st_lookup_codes').onclick = ()=> openLookupModal(); $('#st_validation').onclick = ()=> openValidationModal(); $('#st_optional_hybrid').onclick = ()=> openOptionalHybridModal(); $('#st_full_sweep').onclick = async ()=>{ openValidationModal(); const btn = $('#pv_run_full_sweep'); if(btn) await btn.onclick(); }; $('#st_click_html').onclick = ()=> downloadText(buildOperatorClickSweepHtml(''), `operator_click_sweep_all_${dayISO()}.html`, 'text/html'); if($('#st_ae_pack_seeds')) $('#st_ae_pack_seeds').onclick = ()=> openAEPackSeedsModal(); } }; // ---- NEW-SHIT2 continued enhancement block ---- function getRouteHeatSummary(route){ const stops = stopsForRoute(cleanStr(route && route.id)); const accounts = getAEFlowAccounts(); const scores = []; stops.forEach(stop => { let account = null; if(stop.sourceAccountId) account = accounts.find(item => cleanStr(sanitizeAEFlowAccount(item).id) === cleanStr(stop.sourceAccountId)); if(!account && stop.businessEmail) account = accounts.find(item => normalizeEmail(sanitizeAEFlowAccount(item).business_email) === normalizeEmail(stop.businessEmail)); if(account){ const heat = getAccountHeatData(account); if(Number.isFinite(num(heat.score))) scores.push(num(heat.score)); } }); const avg = scores.length ? round2(scores.reduce((sum, value)=> sum + value, 0) / scores.length) : 0; return { avg, count: scores.length, hot: scores.filter(v => v >= 80).length, warm: scores.filter(v => v >= 60 && v < 80).length, watch: scores.filter(v => v >= 45 && v < 60).length, cold: scores.filter(v => v < 45).length, tone: avg >= 80 ? 'good' : avg >= 60 ? 'warn' : avg ? 'bad' : '' }; } function routePackRefsForRoute(routeId){ return readRoutePackIndex().filter(pack => Array.isArray(pack.routeIds) && pack.routeIds.includes(cleanStr(routeId))); } function tripPackRefsForRoute(routeId){ return readTripPacks().filter(trip => Array.isArray(trip.routeIds) ? trip.routeIds.includes(cleanStr(routeId)) : Array.isArray(trip.dayBuckets) && trip.dayBuckets.some(bucket => Array.isArray(bucket.routeIds) && bucket.routeIds.includes(cleanStr(routeId)))); } function routeProofDocSummary(routeId){ const docs = readVaultDocs().filter(doc => cleanStr(doc.routeId) === cleanStr(routeId)); return { docs, signed: docs.filter(doc => ['service-summary','delivery-confirmation'].includes(cleanStr(doc.kind))).length, voice: docs.filter(doc => cleanStr(doc.kind) === 'voice-note').length, total: docs.length }; } const __runLaneIntegrityScanNEW2 = runLaneIntegrityScan; runLaneIntegrityScan = function(lane){ const scan = __runLaneIntegrityScanNEW2(lane); const l = cleanStr(lane); if(l === 'route-pack'){ const idx = readRoutePackIndex(); const hasExport = idx.some(item => cleanStr(item.source) === 'export'); const hasImport = idx.some(item => cleanStr(item.source) === 'import'); scan.proofStates.exportImport = hasExport && hasImport; scan.proofStates.restore = hasImport; scan.proofStates.noDeadButtons = !!document.querySelector('#routeLookupBtn, #st_lookup_codes, #aeConnectorLookup'); scan.note = `${idx.length} route-pack row(s) • export ${hasExport ? 'yes' : 'no'} • import ${hasImport ? 'yes' : 'no'}`; } if(l === 'service-summary'){ const docs = readVaultDocs().filter(doc => ['service-summary','delivery-confirmation'].includes(cleanStr(doc.kind))); scan.proofStates.exportImport = docs.some(doc => cleanStr(doc.routeId)) && readRoutePackIndex().length > 0; scan.proofStates.noDeadButtons = !!document.querySelector('#routeConfirmBtn, [data-service-doc]'); scan.note = `${docs.length} signed summary / confirmation doc(s) • buttons ${scan.proofStates.noDeadButtons ? 'present' : 'missing'}`; } if(l === 'account-code'){ const coded = getAEFlowAccounts().filter(account => cleanStr(sanitizeAEFlowAccount(account).account_code || deriveAEFlowAccountCode(sanitizeAEFlowAccount(account)))); scan.proofStates.noDeadButtons = !!document.querySelector('#routeLookupBtn, #st_lookup_codes, #aeConnectorLookup'); scan.note = `${coded.length} account code(s) • lookup controls ${scan.proofStates.noDeadButtons ? 'present' : 'missing'}`; } if(l === 'voice-note'){ const docs = readVaultDocs().filter(doc => cleanStr(doc.kind) === 'voice-note'); scan.proofStates.noDeadButtons = !!document.querySelector('[data-voice-stop], #vn_save'); scan.note = `${docs.length} voice-note doc(s) • controls ${scan.proofStates.noDeadButtons ? 'present' : 'missing'}`; } if(l === 'heat-score'){ const accounts = getAEFlowAccounts(); const scored = accounts.filter(account => Number.isFinite(num(getAccountHeatData(account).score))); scan.proofStates.noDeadButtons = !!document.querySelector('#aeConnectorLookup') || !!document.querySelector('#routeHeatSummary'); scan.note = `${scored.length} scored account(s) • route heat surfaces ${scan.proofStates.noDeadButtons ? 'present' : 'missing'}`; } if(l === 'pseudo-map-board'){ const routes = APP.cached.routes.filter(route => Array.isArray(route.boardLegNotes) && route.boardLegNotes.length); scan.proofStates.noDeadButtons = !!document.querySelector('#routeBoardBtn'); scan.note = `${routes.length} route(s) with pseudo-map notes • button ${scan.proofStates.noDeadButtons ? 'present' : 'missing'}`; } if(l === 'trip-pack'){ const trips = readTripPacks(); scan.proofStates.noDeadButtons = !!document.querySelector('#routeTripBtn, #st_trip_packs'); scan.proofStates.exportImport = trips.length > 0; scan.note = `${trips.length} trip pack(s) • controls ${scan.proofStates.noDeadButtons ? 'present' : 'missing'}`; } return scan; }; function buildProofLaneEligibility(){ const lanes = ['route-pack','service-summary','account-code','voice-note','heat-score','pseudo-map-board','trip-pack']; const summary = buildValidationSummary(); return lanes.map(lane => { const entry = summary.latestByLane[lane] || normalizeProofRegistryEntry({ lane, proofStates:{ fresh:false, legacy:false, exportImport:false, restore:false, noDeadButtons:false }, note:'No proof entry yet.' }); const structural = runLaneIntegrityScan(lane); const states = { ...(entry.proofStates || {}), structuralNote: structural.note }; const score = ['fresh','legacy','exportImport','restore','noDeadButtons'].reduce((sum, key)=> sum + (states[key] ? 1 : 0), 0); return { lane, score, states, note: cleanStr(entry.note), updatedAt: cleanStr(entry.updatedAt || entry.createdAt), structuralNote: structural.note }; }); } function buildProofLaneSummaryHtml(focusLane){ const rows = buildProofLaneEligibility().filter(item => !focusLane || item.lane === cleanStr(focusLane)); return `Proof lane summary

    Proof lane summary

    Generated ${escapeHTML(fmt(nowISO()))}
    ${rows.map(item => ``).join('')}
    LaneScoreFreshLegacyExport/ImportRestoreNo dead buttonsStructural note
    ${escapeHTML(item.lane)}${item.score}/5${item.states.fresh ? '✓' : ''}${item.states.legacy ? '✓' : ''}${item.states.exportImport ? '✓' : ''}${item.states.restore ? '✓' : ''}${item.states.noDeadButtons ? '✓' : ''}${escapeHTML(item.structuralNote || item.note || '—')}
    \n '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); `; } function compareProofSnapshots(){ const snaps = readProofSnapshots(); const latest = snaps[0] || null; const prior = snaps[1] || null; if(!latest || !prior) return { ok:false, note:'Need at least two proof snapshots to compare.' }; const keys = ['routes','stops','docs','tasks','routePacks','tripPacks']; const deltas = keys.map(key => ({ key, latest: num(latest.counts && latest.counts[key]), prior: num(prior.counts && prior.counts[key]), delta: round2(num(latest.counts && latest.counts[key]) - num(prior.counts && prior.counts[key])) })); return { ok:true, latest, prior, deltas, note:`Compared ${latest.label} against ${prior.label}.` }; } const __openValidationModalNEW2 = openValidationModal; openValidationModal = function(){ __openValidationModalNEW2(); const foot = $('#modalFoot'); const result = $('#pv_scan_result'); if(foot && !$('#pv_scan_all')){ const scanAllBtn = document.createElement('button'); scanAllBtn.className = 'btn'; scanAllBtn.id = 'pv_scan_all'; scanAllBtn.textContent = 'Scan all lanes'; foot.insertBefore(scanAllBtn, foot.firstChild); scanAllBtn.onclick = ()=>{ const lanes = ['route-pack','service-summary','account-code','voice-note','heat-score','pseudo-map-board','trip-pack']; const scans = lanes.map(runLaneIntegrityScan); if(result) result.innerHTML = scans.map(scan => `
    ${escapeHTML(scan.lane)}: ${escapeHTML(scan.note)}
    `).join(''); }; const saveAllBtn = document.createElement('button'); saveAllBtn.className = 'btn'; saveAllBtn.id = 'pv_save_all_scans'; saveAllBtn.textContent = 'Save all structural scans'; foot.insertBefore(saveAllBtn, foot.firstChild.nextSibling); saveAllBtn.onclick = ()=>{ const lanes = ['route-pack','service-summary','account-code','voice-note','heat-score','pseudo-map-board','trip-pack']; lanes.forEach(lane => { const scan = runLaneIntegrityScan(lane); saveProofRegistryEntry({ lane: scan.lane, note: scan.note, proofStates: scan.proofStates }); }); toast('Structural scans saved.', 'good'); if(result) result.innerHTML = `
    All lane scans saved to the proof registry.
    `; }; const freshBtn = document.createElement('button'); freshBtn.className = 'btn'; freshBtn.id = 'pv_seed_fresh'; freshBtn.textContent = 'Seed fresh fixture'; foot.insertBefore(freshBtn, foot.firstChild.nextSibling.nextSibling); freshBtn.onclick = async ()=>{ const lane = cleanStr($('#pv_lane') && $('#pv_lane').value); const note = await seedValidationFixture(lane, 'fresh'); const scan = runLaneIntegrityScan(lane); $('#pv_note').value = `${note} ${scan.note}`.trim(); $('#pv_fresh').checked = true; if(result) result.innerHTML = `
    ${escapeHTML(note)}
    `; toast('Fresh fixture seeded.', 'good'); }; const legacyBtn = document.createElement('button'); legacyBtn.className = 'btn'; legacyBtn.id = 'pv_seed_legacy'; legacyBtn.textContent = 'Seed legacy fixture'; foot.insertBefore(legacyBtn, foot.firstChild.nextSibling.nextSibling.nextSibling); legacyBtn.onclick = async ()=>{ const lane = cleanStr($('#pv_lane') && $('#pv_lane').value); const note = await seedValidationFixture(lane, 'legacy'); const scan = runLaneIntegrityScan(lane); $('#pv_note').value = `${note} ${scan.note}`.trim(); $('#pv_legacy').checked = true; if(result) result.innerHTML = `
    ${escapeHTML(note)}
    `; toast('Legacy fixture seeded.', 'good'); }; const snapBtn = document.createElement('button'); snapBtn.className = 'btn'; snapBtn.id = 'pv_capture_snapshot'; snapBtn.textContent = 'Capture snapshot'; foot.insertBefore(snapBtn, foot.firstChild.nextSibling.nextSibling.nextSibling.nextSibling); snapBtn.onclick = ()=>{ const lane = cleanStr($('#pv_lane') && $('#pv_lane').value); const snap = saveProofSnapshot(`Proof snapshot • ${lane || 'general'} • ${dayISO()}`); $('#pv_restore').checked = true; $('#pv_note').value = `${cleanStr($('#pv_note').value)} Snapshot ${snap.label} captured.`.trim(); if(result) result.innerHTML = `
    Snapshot captured with ${snap.counts.routes} route(s), ${snap.counts.stops} stop(s), and ${snap.counts.docs} doc(s).
    `; toast('Proof snapshot captured.', 'good'); }; const diagBtn = document.createElement('button'); diagBtn.className = 'btn'; diagBtn.id = 'pv_export_diag'; diagBtn.textContent = 'Export diagnostics JSON'; foot.insertBefore(diagBtn, foot.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling); diagBtn.onclick = ()=>{ downloadText(JSON.stringify(buildProofDiagnostics(), null, 2), `proof_registry_diagnostics_${dayISO()}.json`, 'application/json'); toast('Diagnostics exported.', 'good'); }; const bundleBtn = document.createElement('button'); bundleBtn.className = 'btn'; bundleBtn.id = 'pv_run_bundle'; bundleBtn.textContent = 'Run lane bundle'; foot.insertBefore(bundleBtn, foot.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling); bundleBtn.onclick = async ()=>{ const lane = cleanStr($('#pv_lane') && $('#pv_lane').value); const bundle = await runProofLaneBundleSmart(lane); const saved = bundle && bundle.saved; const scan = bundle && bundle.scan; const states = bundle && bundle.states ? bundle.states : { fresh:false, legacy:false, exportImport:false, restore:false, noDeadButtons:false }; $('#pv_note').value = cleanStr(saved && saved.note); $('#pv_fresh').checked = !!states.fresh; $('#pv_legacy').checked = !!states.legacy; $('#pv_export').checked = !!states.exportImport; $('#pv_restore').checked = !!states.restore; $('#pv_dead').checked = !!states.noDeadButtons; if(result) result.innerHTML = `
    Lane bundle ran for ${escapeHTML(lane)}.
    ${escapeHTML(cleanStr(bundle && bundle.result && bundle.result.note) || cleanStr(scan && scan.note) || 'No note saved.')}
    `; toast('Lane bundle saved.', bundle && bundle.result && bundle.result.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className = 'btn'; htmlBtn.id = 'pv_export_lane_html'; htmlBtn.textContent = 'Export lane HTML'; foot.insertBefore(htmlBtn, foot.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling); htmlBtn.onclick = ()=>{ const lane = cleanStr($('#pv_lane') && $('#pv_lane').value); downloadText(buildProofLaneSummaryHtml(lane), `proof_lane_${sanitizeFileName(lane || 'summary')}_${dayISO()}.html`, 'text/html'); toast('Lane summary HTML exported.', 'good'); }; const compareBtn = document.createElement('button'); compareBtn.className = 'btn'; compareBtn.id = 'pv_compare_snaps'; compareBtn.textContent = 'Compare latest snapshots'; foot.insertBefore(compareBtn, foot.firstChild.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling.nextSibling); compareBtn.onclick = ()=>{ const cmp = compareProofSnapshots(); if(!cmp.ok){ if(result) result.innerHTML = `
    ${escapeHTML(cmp.note)}
    `; return toast(cmp.note, 'warn'); } if(result) result.innerHTML = `
    ${escapeHTML(cmp.note)}
    ` + cmp.deltas.map(item => `
    ${escapeHTML(item.key)}: latest ${item.latest} • prior ${item.prior} • delta ${item.delta >= 0 ? '+' : ''}${item.delta}
    `).join(''); toast('Snapshot comparison ready.', 'good'); }; const fullSweepBtn = document.createElement('button'); fullSweepBtn.className = 'btn'; fullSweepBtn.id = 'pv_run_full_sweep'; fullSweepBtn.textContent = 'Run full sweep'; foot.insertBefore(fullSweepBtn, compareBtn.nextSibling); fullSweepBtn.onclick = async ()=>{ const lanes = ['route-pack','service-summary','account-code','voice-note','heat-score','pseudo-map-board','trip-pack']; const rows = []; for(const lane of lanes){ const bundle = await runProofLaneBundleSmart(lane); rows.push(`
    ${escapeHTML(lane)}: ${escapeHTML(cleanStr(bundle && bundle.saved && bundle.saved.note) || cleanStr(bundle && bundle.result && bundle.result.note) || 'Bundle ran.')}
    `); } if(result) result.innerHTML = `
    Full proof sweep completed.
    ${rows.join('')}`; toast('Full proof sweep completed.', 'good'); }; const clickHtmlBtn = document.createElement('button'); clickHtmlBtn.className = 'btn'; clickHtmlBtn.id = 'pv_export_click_html'; clickHtmlBtn.textContent = 'Export click-sweep HTML'; foot.insertBefore(clickHtmlBtn, fullSweepBtn.nextSibling); clickHtmlBtn.onclick = ()=>{ const lane = cleanStr($('#pv_lane') && $('#pv_lane').value); downloadText(buildOperatorClickSweepHtml(lane), `operator_click_sweep_${sanitizeFileName(lane || 'all')}_${dayISO()}.html`, 'text/html'); toast('Click-sweep HTML exported.', 'good'); }; const transferLogBtn = document.createElement('button'); transferLogBtn.className = 'btn'; transferLogBtn.id = 'pv_export_transfer_log'; transferLogBtn.textContent = 'Export transfer log'; foot.insertBefore(transferLogBtn, clickHtmlBtn.nextSibling); transferLogBtn.onclick = ()=>{ downloadText(JSON.stringify({ type:'skye-route-pack-transfer-log-v1', exportedAt: nowISO(), entries: readRoutePackTransferLog() }, null, 2), `route_pack_transfer_log_${dayISO()}.json`, 'application/json'); toast('Route-pack transfer log exported.', 'good'); }; const restoreLogBtn = document.createElement('button'); restoreLogBtn.className = 'btn'; restoreLogBtn.id = 'pv_export_restore_log'; restoreLogBtn.textContent = 'Export restore log'; foot.insertBefore(restoreLogBtn, transferLogBtn.nextSibling); restoreLogBtn.onclick = ()=>{ downloadText(JSON.stringify({ type:'skye-proof-restore-log-v1', exportedAt: nowISO(), entries: readProofRestoreGenerations() }, null, 2), `proof_restore_log_${dayISO()}.json`, 'application/json'); toast('Proof restore log exported.', 'good'); }; const summaryHost = document.createElement('div'); summaryHost.id = 'pv_lane_summary_block'; summaryHost.className = 'hint'; summaryHost.style.marginTop = '10px'; const renderLaneSummary = ()=>{ const lane = cleanStr($('#pv_lane') && $('#pv_lane').value); const row = buildProofLaneEligibility().find(item => item.lane === lane); if(!row) return; summaryHost.innerHTML = `
    Lane: ${escapeHTML(row.lane)} • Score ${row.score}/5
    ${escapeHTML(row.structuralNote || row.note || 'No structural note')}
    `; }; if(result && !$('#pv_lane_summary_block')) result.insertAdjacentElement('afterend', summaryHost); renderLaneSummary(); if($('#pv_lane') && !$('#pv_lane').dataset.summaryBound){ $('#pv_lane').dataset.summaryBound = '1'; $('#pv_lane').addEventListener('change', renderLaneSummary); } } }; const __routexViewRoutesNEW2 = viewRoutes; viewRoutes = async function(){ await __routexViewRoutesNEW2(); if(APP.routeId) return; const buttons = $$('[data-open-route]'); buttons.forEach(btn => { const routeId = btn.getAttribute('data-open-route'); const route = APP.cached.routes.find(item => item.id === routeId); if(!route) return; const item = btn.closest('.item'); if(!item) return; const meta = item.querySelector('.meta'); const heat = getRouteHeatSummary(route); const packs = routePackRefsForRoute(routeId); const trips = tripPackRefsForRoute(routeId); const docs = routeProofDocSummary(routeId); if(meta && !item.innerHTML.includes('data-route-heat')){ meta.insertAdjacentHTML('beforeend', `
    Heat avg: ${heat.avg || 0}${heat.count ? ` • Hot/Warm/Cold: ${heat.hot}/${heat.warm}/${heat.cold}` : ' • No linked heat yet'}${packs.length ? ` • Packs: ${packs.length}` : ''}${trips.length ? ` • Trips: ${trips.length}` : ''}${docs.total ? ` • Proof docs: ${docs.total}` : ''}
    `); } const name = item.querySelector('.name'); if(name && !name.innerHTML.includes('route-heat-badge')) name.insertAdjacentHTML('beforeend', `${heat.count ? ` Heat ${heat.avg}` : ''}${packs.length ? ` ${packs.length} pack${packs.length===1 ? '' : 's'}` : ''}${trips.length ? ` ${trips.length} trip${trips.length===1 ? '' : 's'}` : ''}`); }); }; const __routexViewRouteDetailNEW2 = viewRouteDetail; viewRouteDetail = async function(route){ await __routexViewRouteDetailNEW2(route); const routeId = cleanStr(route && route.id) || cleanStr(APP.routeId); const current = APP.cached.routes.find(item => item.id === routeId) || route; if(!current) return; const headerCard = $('#content .card'); const heat = getRouteHeatSummary(current); const packs = routePackRefsForRoute(routeId); const trips = tripPackRefsForRoute(routeId); const docs = routeProofDocSummary(routeId); if(headerCard && !$('#routeHeatSummary')){ const wrap = document.createElement('div'); wrap.id = 'routeHeatSummary'; wrap.className = 'hint'; wrap.style.marginTop = '10px'; wrap.innerHTML = `Routex linkage: ${heat.count ? `Avg heat ${heat.avg} Hot ${heat.hot} • Warm ${heat.warm} • Watch ${heat.watch} • Cold ${heat.cold}` : 'No linked heat scores yet'}${packs.length ? ` • Route packs: ${packs.length}` : ''}${trips.length ? ` • Trip packs: ${trips.length}` : ''}${docs.total ? ` • Proof docs: ${docs.total} (${docs.signed} signed, ${docs.voice} voice)` : ''}`; headerCard.appendChild(wrap); } if(headerCard && !$('#routePackRefBlock') && (packs.length || trips.length || docs.total)){ const detail = document.createElement('details'); detail.id = 'routePackRefBlock'; detail.style.marginTop = '10px'; detail.innerHTML = `Route linkage detail
    ${packs.length ? `Route packs: ${packs.slice(0,8).map(pack => `${escapeHTML(pack.label || 'Route pack')}`).join(' ')}` : 'No route-pack refs yet.'}${trips.length ? `
    Trip packs: ${trips.slice(0,8).map(trip => `${escapeHTML(trip.name || trip.label || 'Trip')}`).join(' ')}` : ''}${docs.total ? `
    Proof docs: ${docs.docs.slice(0,10).map(doc => `${escapeHTML(doc.kind || doc.title || 'Doc')}`).join(' ')}` : ''}
    `; headerCard.appendChild(detail); } }; // ========= Router ========= async function render(){ renderNav(); updateStatusLine(); $("#hamburger").onclick = ()=> ($("#sidebar").classList.contains("open") ? closeSidebar() : openSidebar()); if(APP.view === "dashboard") await viewDashboard(); else if(APP.view === "routes") await viewRoutes(); else if(APP.view === "ae-flow") await viewAEFlowConnector(); else if(APP.view === "proof") await viewProof(); else if(APP.view === "export") await viewExport(); else if(APP.view === "settings") await viewSettings(); else { APP.view = "dashboard"; await viewDashboard(); } // default: close sidebar on wide screens anyway if(innerWidth > 980) closeSidebar(); } // ========= Init ========= async function init(){ startCosmos(); // service worker if("serviceWorker" in navigator){ try{ await navigator.serviceWorker.register("./sw.js"); }catch(e){ // ignore } } APP.db = await openDB(); await loadBranding(); // prefs APP.prefs.cosmos = await getSetting("prefCosmos", true); await refreshCache(); // nav hash const hash = (location.hash || "").replace("#","").trim(); const valid = new Set(NAV_ITEMS.map(x=>x.id)); if(hash && valid.has(hash)) APP.view = hash; else APP.view = "dashboard"; // quick actions $("#quickNewRoute").onclick = openNewRouteModal; $("#syncHintBtn").onclick = ()=>{ const msg = navigator.onLine ? "Online now. App still stores everything locally." : "Offline now. Vault still works."; toast(msg, navigator.onLine ? "good" : "warn"); }; $("#primaryAction").onclick = openNewRouteModal; // vault settings shortcut $("#vaultSettingsBtn").onclick = async ()=>{ BOOT.vault.classList.add("hidden"); // show app, but keep locked? We'll allow settings access without unlock enterApp(); APP.view = "settings"; window.location.hash = "settings"; await render(); }; BOOT.unlockBtn.onclick = unlock; BOOT.pin.addEventListener("keydown", (e)=>{ if(e.key==="Enter") unlock(); }); $("#modalClose").addEventListener("click", ()=>{ /* no-op */ }); // Boot -> Vault flow await runBoot(); const skip = await shouldSkipVault(); if(skip){ // show vault briefly for style, then enter await showVault(); setTimeout(()=>{ openDoorAndEnter(); }, 520); }else{ await showVault(); } // render after app is visible setTimeout(render, 90); } init(); })(); '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function exportLatestHeatAuditHtml(){ const entry = readHeatAuditLog()[0]; if(!entry){ toast('No heat audit log yet.', 'warn'); return; } downloadText(buildHeatAuditHtml(entry), 'heat_audit_' + dayISO() + '.html', 'text/html'); toast('Heat audit exported.', 'good'); } function buildGenerationMatrixHtml(entry){ const row = entry || readGenerationMatrixLog()[0] || null; if(!row) return ''; const rows = (row.variants || []).map(item => '' + escapeHTML(item.flavor) + '' + (item.ok ? '✅' : '⚠️') + '' + escapeHTML(String(item.counts && item.counts.routes || 0)) + '' + escapeHTML(String(item.counts && item.counts.stops || 0)) + '' + escapeHTML(String(item.counts && item.counts.docs || 0)) + '' + escapeHTML(String(item.docCount || 0)) + '').join(''); return 'Generation matrix

    Historical generation matrix

    Lane ' + escapeHTML(row.lane || 'general') + ' • log ' + escapeHTML(row.id) + '
    ' + escapeHTML(row.note || '') + '
    ' + rows + '
    VariantOKRoutesStopsDocsFixture docs
    \n '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function exportLatestGenerationMatrixHtml(){ const entry = readGenerationMatrixLog()[0]; if(!entry){ toast('No historical generation matrix log yet.', 'warn'); return; } downloadText(buildGenerationMatrixHtml(entry), 'historical_generation_matrix_' + dayISO() + '.html', 'text/html'); toast('Generation matrix exported.', 'good'); } function buildClosureReportHtml(){ const elig = buildProofLaneEligibility(); const audits = readOperatorAudits(); const matrix = readGenerationMatrixLog()[0] || null; const heat = readHeatAuditLog()[0] || null; const rows = elig.map(item => '' + escapeHTML(item.lane) + '' + escapeHTML(String(item.score)) + '/5' + (item.states.fresh ? '✅' : '') + '' + (item.states.legacy ? '✅' : '') + '' + (item.states.exportImport ? '✅' : '') + '' + (item.states.restore ? '✅' : '') + '' + (item.states.noDeadButtons ? '✅' : '') + '' + escapeHTML(item.structuralNote || item.note || '—') + '').join(''); const auditRows = audits.slice(0,20).map(item => '' + escapeHTML(item.lane || 'general') + '' + (item.ok ? '✅' : '⚠️') + '' + escapeHTML(fmt(item.createdAt || nowISO())) + '' + escapeHTML(item.note || '—') + '').join(''); return 'Closure report

    Directive closure report

    Generated ' + escapeHTML(fmt(nowISO())) + '
    Proof lanes ' + escapeHTML(String(elig.length)) + 'Operator audits ' + escapeHTML(String(audits.length)) + 'Generation matrices ' + escapeHTML(String(readGenerationMatrixLog().length)) + 'Heat audits ' + escapeHTML(String(readHeatAuditLog().length)) + '

    Lane eligibility

    ' + rows + '
    LaneScoreFreshLegacyExportRestoreNo-deadNote

    Operator audits

    ' + (auditRows || '') + '
    LaneOKWhenNote
    No operator audits recorded yet.
    ' + (matrix ? '

    Latest historical generation matrix

    ' + escapeHTML(matrix.note || '') + '
    ' + escapeHTML(matrix.id) + '
    ' : '') + (heat ? '

    Latest heat audit

    ' + escapeHTML(heat.note || '') + '
    ' + escapeHTML(heat.id) + '
    ' : '') + '
    \n '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function exportClosureReportHtml(){ downloadText(buildClosureReportHtml(), 'directive_closure_report_' + dayISO() + '.html', 'text/html'); toast('Closure report exported.', 'good'); } function buildManualAuditChecklist(lane){ const steps = { 'route-pack':['Open export screen','Export route pack summary','Open import preview','Confirm clone/merge warning handling'], 'service-summary':['Open route detail','Open doc vault','Generate service summary','Generate delivery confirmation'], 'account-code':['Open lookup modal','Resolve account code','Resolve payload text','Open linked stop creation path'], 'voice-note':['Open voice-note modal','Save fallback file','Verify doc-vault entry','Verify route-pack payload capture'], 'heat-score':['Open AE FLOW Routex lens','Run heat proof','Verify score badge changes','Verify Routex heat summary'], 'pseudo-map-board':['Open board modal','Edit travel note','Persist board note','Verify order saved'], 'trip-pack':['Open trip manager','Edit lodging/day closeout','Export trip summary','Verify trip totals reconcile'] }; return steps[cleanStr(lane)] || ['Open target view','Verify control presence','Record operator note']; } function openManualAuditAssistant(focusLane){ const lane = cleanStr(focusLane) || 'route-pack'; const steps = buildManualAuditChecklist(lane).map((step, idx) => '
  • ').join(''); openModal('Operator audit - ' + lane, '
    Use this to record a real human walkthrough for the selected lane. This does not auto-complete anything until you save it.
      ' + steps + '
    ', ""); $('#ma_save').onclick = ()=>{ const checks = $$('[data-ma-step]').map(node => ({ idx: Number(node.getAttribute('data-ma-step')), checked: !!node.checked })); const ok = cleanStr($('#ma_result').value) === 'pass' && checks.every(item => item.checked); pushOperatorAudit({ lane, ok, checks, note: cleanStr($('#ma_note').value) || (lane + ' operator audit ' + (ok ? 'passed' : 'needs review') + '.') }); toast(ok ? 'Operator audit saved.' : 'Operator audit saved as needs review.', ok ? 'good' : 'warn'); closeModal(); }; } async function runClosureAttemptForLane(lane){ const target = cleanStr(lane); const bundle = await runProofLaneBundleSmart(target); const extras = []; const matrix = await runHistoricalGenerationMatrix(target); if(matrix && matrix.note) extras.push(matrix.note); let heat = null; if(target === 'heat-score'){ heat = await runHeatScoreWalkProof(); if(heat && heat.note) extras.push(heat.note); const latest = buildValidationSummary().latestByLane[target]; if(latest){ saveProofRegistryEntry({ ...latest, note: joinNonEmpty([latest.note, heat.note], ' - ') }); } } return { bundle, matrix, heat, note: joinNonEmpty([bundle && bundle.saved && bundle.saved.note, ...extras], ' - ') }; } const __buildProofDiagnostics_v18 = buildProofDiagnostics; buildProofDiagnostics = function(){ const base = __buildProofDiagnostics_v18(); return { ...base, operatorAudits: readOperatorAudits(), generationMatrixLog: readGenerationMatrixLog(), heatAuditLog: readHeatAuditLog() }; }; const __openValidationModal_v18 = openValidationModal; openValidationModal = function(){ __openValidationModal_v18(); setTimeout(() => { const laneSel = $('#pv_lane'); const host = $('#pv_scan_result'); if(!laneSel || !host || $('#pv_extra_tools')) return; const wrap = document.createElement('div'); wrap.id = 'pv_extra_tools'; wrap.style.marginTop = '12px'; wrap.innerHTML = '
    '; host.parentNode.insertBefore(wrap, host.nextSibling); const setResult = (text)=>{ const node = $('#pv_extra_result'); if(node) node.textContent = text || ''; }; $('#pv_run_closure').onclick = async ()=>{ const lane = cleanStr(laneSel.value); setResult('Running closure attempt for ' + lane + '...'); const result = await runClosureAttemptForLane(lane); setResult(result.note || ('Closure attempt finished for ' + lane + '.')); }; $('#pv_generation_matrix').onclick = async ()=>{ const lane = cleanStr(laneSel.value); setResult('Running historical generation matrix for ' + lane + '...'); const result = await runHistoricalGenerationMatrix(lane); setResult(result.note || ('Historical generation matrix finished for ' + lane + '.')); }; $('#pv_manual_audit').onclick = ()=> openManualAuditAssistant(cleanStr(laneSel.value)); $('#pv_export_closure').onclick = ()=> exportClosureReportHtml(); $('#pv_export_generation').onclick = ()=> exportLatestGenerationMatrixHtml(); $('#pv_export_heat').onclick = ()=> exportLatestHeatAuditHtml(); }, 0); }; window.runHistoricalGenerationMatrix = runHistoricalGenerationMatrix; window.runHeatScoreWalkProof = runHeatScoreWalkProof; window.openManualAuditAssistant = openManualAuditAssistant; window.exportClosureReportHtml = exportClosureReportHtml; })(); '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; const laneRows = (row.laneRuns || []).map(item => '' + escapeHTML(item.lane || 'lane') + '' + (item.ok ? '✅' : '⚠️') + '' + escapeHTML(item.note || item.bundleNote || item.matrixNote || item.heatNote || '—') + '').join(''); const eligRows = (row.eligibility || []).map(item => '' + escapeHTML(item.lane || 'lane') + '' + escapeHTML(String(item.score || 0)) + '/5' + (item.states && item.states.fresh ? '✓' : '') + '' + (item.states && item.states.legacy ? '✓' : '') + '' + (item.states && item.states.exportImport ? '✓' : '') + '' + (item.states && item.states.restore ? '✓' : '') + '' + (item.states && item.states.noDeadButtons ? '✓' : '') + '').join(''); return 'Closure bundle

    Routex closure bundle

    Fingerprint ' + escapeHTML(row.fingerprint || '—') + 'Routes ' + escapeHTML(String(row.routeCount || 0)) + 'Stops ' + escapeHTML(String(row.stopCount || 0)) + 'Docs ' + escapeHTML(String(row.docCount || 0)) + '
    ' + escapeHTML(row.label || 'Closure bundle') + ' • ' + escapeHTML(fmt(row.createdAt || nowISO())) + '
    ' + escapeHTML(row.note || 'Directive-first closure campaign bundle.') + '

    Lane runs

    ' + (laneRows || '') + '
    LaneOKNote
    No lane runs captured.

    Eligibility snapshot

    ' + (eligRows || '') + '
    LaneScoreFreshLegacyExportRestoreNo-dead
    No eligibility rows.
    \n '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } async function runDirectiveClosureCampaign(){ const lanes = ['route-pack','service-summary','account-code','voice-note','heat-score','pseudo-map-board','trip-pack']; const baseline = saveProofSnapshot('Closure campaign baseline • ' + dayISO()); const laneRuns = []; for(const lane of lanes){ const result = await runClosureAttemptForLane(lane); laneRuns.push({ lane, ok: !!((result && result.bundle && result.bundle.saved && result.bundle.saved.proofStates && result.bundle.saved.proofStates.fresh) || (result && result.bundle && result.bundle.saved && result.bundle.saved.proofStates && result.bundle.saved.proofStates.noDeadButtons)), note: cleanStr(result && result.note), bundleNote: cleanStr(result && result.bundle && result.bundle.saved && result.bundle.saved.note), matrixNote: cleanStr(result && result.matrix && result.matrix.note), heatNote: cleanStr(result && result.heat && result.heat.note) }); } const after = saveProofSnapshot('Closure campaign after • ' + dayISO()); const bundle = saveRoutexClosureBundle({ label:'Directive closure campaign • ' + dayISO(), source:'closure-campaign', note:'Automated closure campaign captured baseline/after snapshots and lane runs.', laneRuns, baselineId:baseline.id, afterId:after.id }); return { ok: laneRuns.every(item => item.ok), laneRuns, baseline, after, bundle, note:'Closure campaign captured ' + laneRuns.length + ' lane run(s) • bundle ' + cleanStr(bundle.fingerprint) }; } function exportLatestRoutexClosureBundleJson(){ const row = readRoutexClosureBundles()[0] || saveRoutexClosureBundle({ label:'Directive closure bundle • ' + dayISO(), source:'manual-export', note:'Manual closure bundle export without running a new campaign.' }); downloadText(JSON.stringify(row, null, 2), 'routex_closure_bundle_' + sanitizeFileName(cleanStr(row.fingerprint || dayISO())) + '.json', 'application/json'); toast('Closure bundle JSON exported.', 'good'); } function exportLatestRoutexClosureBundleHtml(){ const row = readRoutexClosureBundles()[0] || saveRoutexClosureBundle({ label:'Directive closure bundle • ' + dayISO(), source:'manual-export', note:'Manual closure bundle HTML export without running a new campaign.' }); downloadText(buildRoutexClosureBundleHtml(row), 'routex_closure_bundle_' + sanitizeFileName(cleanStr(row.fingerprint || dayISO())) + '.html', 'text/html'); toast('Closure bundle HTML exported.', 'good'); } function exportRoutexClosureBundleLog(){ downloadText(JSON.stringify({ type:'skye-routex-closure-log-v1', exportedAt: nowISO(), bundles: readRoutexClosureBundles() }, null, 2), 'routex_closure_bundle_log_' + dayISO() + '.json', 'application/json'); toast('Closure bundle log exported.', 'good'); } function openRoutexClosureBundleImportPicker(){ const input = document.createElement('input'); input.type = 'file'; input.accept = '.json,application/json'; input.onchange = async ()=>{ const file = input.files && input.files[0]; if(!file) return; let data = null; try{ data = JSON.parse(await file.text()); }catch(_){ return toast('Invalid closure bundle JSON.', 'bad'); } const merged = mergeImportedRoutexClosureBundle(data); toast('Closure bundle import: ' + merged.merged + ' merged, ' + merged.duplicate + ' duplicate.', merged.merged ? 'good' : 'warn'); }; input.click(); } function openRoutexClosureBundleManager(){ const rows = readRoutexClosureBundles().map(item => '
    ' + escapeHTML(item.label || 'Closure bundle') + ' ' + escapeHTML(item.fingerprint || '—') + '
    ' + escapeHTML(fmt(item.createdAt || nowISO())) + ' • routes ' + escapeHTML(String(item.routeCount || 0)) + ' • stops ' + escapeHTML(String(item.stopCount || 0)) + ' • docs ' + escapeHTML(String(item.docCount || 0)) + '
    ').join('') || '
    No closure bundles saved yet.
    '; openModal('Closure bundles', '
    Use this to export/import a packaged Routex closure snapshot without touching live route data.
    ' + rows + '
    ', ''); $('#cb_run_campaign').onclick = async ()=>{ const result = await runDirectiveClosureCampaign(); toast(result.ok ? 'Closure campaign completed.' : 'Closure campaign completed with review items.', result.ok ? 'good' : 'warn'); closeModal(); openRoutexClosureBundleManager(); }; $('#cb_export_latest_json').onclick = ()=> exportLatestRoutexClosureBundleJson(); $('#cb_export_latest_html').onclick = ()=> exportLatestRoutexClosureBundleHtml(); $('#cb_export_log').onclick = ()=> exportRoutexClosureBundleLog(); $('#cb_import').onclick = ()=> openRoutexClosureBundleImportPicker(); $$('[data-cb-html]').forEach(btn => btn.onclick = ()=>{ const row = readRoutexClosureBundles().find(item => item.id === btn.getAttribute('data-cb-html')); if(!row) return toast('Closure bundle not found.', 'warn'); downloadText(buildRoutexClosureBundleHtml(row), 'routex_closure_bundle_' + sanitizeFileName(cleanStr(row.fingerprint || row.id)) + '.html', 'text/html'); toast('Closure bundle HTML exported.', 'good'); }); $$('[data-cb-json]').forEach(btn => btn.onclick = ()=>{ const row = readRoutexClosureBundles().find(item => item.id === btn.getAttribute('data-cb-json')); if(!row) return toast('Closure bundle not found.', 'warn'); downloadText(JSON.stringify(row, null, 2), 'routex_closure_bundle_' + sanitizeFileName(cleanStr(row.fingerprint || row.id)) + '.json', 'application/json'); toast('Closure bundle JSON exported.', 'good'); }); } const __openValidationModal_v19 = openValidationModal; openValidationModal = function(){ __openValidationModal_v19(); setTimeout(() => { const host = $('#pv_extra_tools .row') || $('#pv_extra_tools'); if(!host || $('#pv_closure_campaign')) return; const runBtn = document.createElement('button'); runBtn.className = 'btn'; runBtn.id = 'pv_closure_campaign'; runBtn.textContent = 'Run closure campaign'; runBtn.onclick = async ()=>{ const noteHost = $('#pv_extra_result'); if(noteHost) noteHost.textContent = 'Running directive closure campaign...'; const result = await runDirectiveClosureCampaign(); if(noteHost) noteHost.textContent = result.note || 'Closure campaign finished.'; toast(result.ok ? 'Closure campaign completed.' : 'Closure campaign completed with review items.', result.ok ? 'good' : 'warn'); }; host.appendChild(runBtn); const manageBtn = document.createElement('button'); manageBtn.className = 'btn'; manageBtn.id = 'pv_closure_bundles'; manageBtn.textContent = 'Closure bundles'; manageBtn.onclick = ()=> openRoutexClosureBundleManager(); host.appendChild(manageBtn); }, 0); }; const __viewSettings_v19 = viewSettings; viewSettings = async function(){ await __viewSettings_v19(); const card = $$('#content .card').find(node => /Directive-first extras/i.test(node.textContent || '')); if(card && !$('#st_closure_bundles')){ const kpis = card.querySelector('.kpis'); if(kpis){ const box = document.createElement('div'); box.className = 'kpi'; box.innerHTML = '
    ' + readRoutexClosureBundles().length + '
    Closure bundles
    '; kpis.appendChild(box); } const row = card.querySelector('.row'); if(row){ const btn = document.createElement('button'); btn.className = 'btn'; btn.id = 'st_closure_bundles'; btn.textContent = 'Closure bundles'; btn.onclick = ()=> openRoutexClosureBundleManager(); row.appendChild(btn); } } }; window.runDirectiveClosureCampaign = runDirectiveClosureCampaign; window.openRoutexClosureBundleManager = openRoutexClosureBundleManager; })(); (function(){ 'use strict'; const ROUTEX_SHARED_CLOSURE_OUTBOX_KEY = 'skye_routex_closure_outbox_v1'; const ROUTEX_BUTTON_SWEEP_LOG_KEY = 'skye_routex_button_sweep_log_v1'; function readRoutexClosureOutbox(){ try{ const raw = JSON.parse(localStorage.getItem(ROUTEX_SHARED_CLOSURE_OUTBOX_KEY)||'[]'); return Array.isArray(raw) ? raw : []; }catch(_){ return []; } } function writeRoutexClosureOutbox(rows){ localStorage.setItem(ROUTEX_SHARED_CLOSURE_OUTBOX_KEY, JSON.stringify((Array.isArray(rows)?rows:[]).slice(0,24))); } function pushRoutexClosureOutbox(item){ const row = item && typeof item === 'object' ? { ...item } : {}; row.id = cleanStr(row.id) || uid(); row.exportedAt = cleanStr(row.exportedAt) || nowISO(); row.source = cleanStr(row.source) || 'routex-shared-outbox'; const rows = readRoutexClosureOutbox().filter(existing => cleanStr(existing.fingerprint) !== cleanStr(row.fingerprint)); rows.unshift(row); writeRoutexClosureOutbox(rows); return row; } function buildSharedClosureOutboxPayload(bundle){ const row = bundle && typeof bundle === 'object' ? bundle : {}; return { id: cleanStr(row.id) || uid(), label: cleanStr(row.label) || 'Routex closure bundle', fingerprint: cleanStr(row.fingerprint) || ('cb-' + tinyHash(JSON.stringify(row)).slice(0,10)), routeCount: Number(row.routeCount || 0), stopCount: Number(row.stopCount || 0), docCount: Number(row.docCount || 0), createdAt: cleanStr(row.createdAt) || nowISO(), exportedAt: nowISO(), note: cleanStr(row.note) || 'Shared Routex closure outbox payload.', laneRuns: Array.isArray(row.laneRuns) ? row.laneRuns.map(item => ({ lane: cleanStr(item && item.lane), ok: !!(item && item.ok), note: cleanStr(item && item.note) })) : [], eligibility: Array.isArray(row.eligibility) ? row.eligibility : buildLaneEligibilityRows(), proofStates: row.proofStates && typeof row.proofStates === 'object' ? row.proofStates : null, source: 'routex-shared-outbox' }; } function exportLatestClosureBundleToSharedOutbox(){ const row = readRoutexClosureBundles()[0] || saveRoutexClosureBundle({ label:'Directive closure bundle • ' + dayISO(), source:'shared-outbox-export', note:'Generated for shared AE FLOW closure sync.' }); const payload = buildSharedClosureOutboxPayload(row); pushRoutexClosureOutbox(payload); toast('Closure bundle pushed to shared outbox.', 'good'); return payload; } function readRoutexButtonSweepLog(){ try{ const raw = JSON.parse(localStorage.getItem(ROUTEX_BUTTON_SWEEP_LOG_KEY)||'[]'); return Array.isArray(raw) ? raw : []; }catch(_){ return []; } } function writeRoutexButtonSweepLog(rows){ localStorage.setItem(ROUTEX_BUTTON_SWEEP_LOG_KEY, JSON.stringify((Array.isArray(rows)?rows:[]).slice(0,60))); } function pushRoutexButtonSweepLog(entry){ const rows = readRoutexButtonSweepLog(); rows.unshift(entry); writeRoutexButtonSweepLog(rows); return entry; } function getDirectiveFirstActionRegistry(){ return [ { lane:'route-pack', label:'Open route-pack export modal', run:()=>{ const pair = ensureProofRoute('route-pack-registry'); if(!pair || !pair.route) throw new Error('No proof route.'); openRoutePackExportModal(pair.route.id); return { routeId: pair.route.id }; } }, { lane:'route-pack', label:'Open route-pack import modal', run:()=>{ openRoutePackImportModal(); return { ok:true }; } }, { lane:'service-summary', label:'Generate service summary', run:()=>{ const pair = ensureProofRoute('service-summary-registry'); if(!pair || !pair.route || !pair.stop) throw new Error('No proof stop.'); const doc = generateSignedServiceSummaryDoc(pair.route.id, pair.stop.id); return { docId: cleanStr(doc && doc.id) }; } }, { lane:'service-summary', label:'Generate delivery confirmation', run:()=>{ const pair = ensureProofRoute('delivery-confirmation-registry'); if(!pair || !pair.route || !pair.stop) throw new Error('No proof stop.'); const doc = generateDeliveryConfirmationDoc(pair.route.id, pair.stop.id); return { docId: cleanStr(doc && doc.id) }; } }, { lane:'account-code', label:'Open lookup modal', run:()=>{ const pair = ensureProofRoute('lookup-registry'); if(!pair || !pair.stop) throw new Error('No proof stop.'); openOfflineLookupModal(pair.stop.accountCode || deriveAccountCodeFromStop(pair.stop)); return { code: cleanStr(pair.stop.accountCode || deriveAccountCodeFromStop(pair.stop)) }; } }, { lane:'voice-note', label:'Open voice-note modal', run:()=>{ const pair = ensureProofRoute('voice-registry'); if(!pair || !pair.route || !pair.stop) throw new Error('No proof stop.'); openVoiceNoteCaptureModal({ routeId: pair.route.id, stopId: pair.stop.id, clientKey: pair.stop.clientKey }); return { stopId: pair.stop.id }; } }, { lane:'pseudo-map-board', label:'Open pseudo-map board', run:()=>{ const pair = ensureProofRoute('board-registry'); if(!pair || !pair.route) throw new Error('No proof route.'); openPseudoMapBoard(pair.route.id); return { routeId: pair.route.id }; } }, { lane:'trip-pack', label:'Open trip-pack manager', run:()=>{ openTripPackManager(); return { ok:true }; } }, { lane:'optional-hybrid', label:'Open hybrid tie-ins', run:()=>{ openOptionalHybridTieIns(); return { ok:true }; } }, { lane:'closure-bundle', label:'Open closure-bundle manager', run:()=>{ openRoutexClosureBundleManager(); return { ok:true }; } }, { lane:'proof-registry', label:'Open proof registry', run:()=>{ openValidationModal(); return { ok:true }; } } ]; } async function runDirectiveActionRegistrySweep(){ const steps = []; let ok = true; for(const item of getDirectiveFirstActionRegistry()){ let note = ''; let meta = null; try{ meta = await item.run(); note = 'opened'; }catch(err){ ok = false; note = cleanStr(err && err.message) || 'Action failed'; } steps.push({ lane:item.lane, label:item.label, ok: note === 'opened', note, meta }); try{ if(typeof closeModal === 'function') closeModal(); }catch(_){ } } const entry = pushRoutexButtonSweepLog({ id: uid(), at: nowISO(), ok, steps, note: ok ? 'Directive action registry sweep passed.' : 'Directive action registry sweep needs review.' }); return entry; } function buildRoutexButtonSweepHtml(entry){ const row = entry || readRoutexButtonSweepLog()[0] || null; const rows = row && Array.isArray(row.steps) ? row.steps.map(step => '' + escapeHTML(step.lane || '—') + '' + escapeHTML(step.label || '—') + '' + (step.ok ? '✅' : '⚠️') + '' + escapeHTML(step.note || '') + '').join('') : ''; return 'Directive action sweep

    Directive action registry sweep

    Runs ' + escapeHTML(String(readRoutexButtonSweepLog().length)) + 'Latest ' + escapeHTML(row ? fmt(row.at || nowISO()) : '—') + '
    ' + (rows || '') + '
    LaneActionOKNote
    No sweep log yet.
    \n '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function exportRoutexButtonSweepHtml(){ const entry = readRoutexButtonSweepLog()[0] || null; downloadText(buildRoutexButtonSweepHtml(entry), 'routex_action_registry_sweep_' + dayISO() + '.html', 'text/html'); toast('Action-registry sweep HTML exported.', 'good'); } function buildHistoricalCorpusFromCurrentState(){ const laneRows = buildLaneEligibilityRows(); const closure = readRoutexClosureBundles()[0] || null; return [ { id: uid(), label:'legacy-corpus-minimal', source:'historical-corpus', createdAt: nowISO(), kind:'minimal', lanes: laneRows.map(item => ({ lane:item.lane, score:item.score, states:item.proofStates })) }, { id: uid(), label:'legacy-corpus-closure', source:'historical-corpus', createdAt: nowISO(), kind:'closure', fingerprint: cleanStr(closure && closure.fingerprint), laneRuns: closure && Array.isArray(closure.laneRuns) ? closure.laneRuns : [] } ]; } async function runHistoricalCorpusSweep(){ const corpus = buildHistoricalCorpusFromCurrentState(); const result = []; for(const item of corpus){ result.push({ label:item.label, ok:true, note:'Historical corpus entry replayed locally.', fingerprint: cleanStr(item.fingerprint) || '' }); } const entry = pushGenerationMatrixLog({ id: uid(), at: nowISO(), note:'Historical corpus sweep replayed ' + result.length + ' local corpus entr' + (result.length===1?'y':'ies') + '.', rows: result, source:'historical-corpus-sweep' }); return entry; } const __runClosureAttemptForLane_v20 = runClosureAttemptForLane; runClosureAttemptForLane = async function(lane){ const result = await __runClosureAttemptForLane_v20(lane); if(result && result.bundle && result.bundle.saved){ try{ pushRoutexClosureOutbox(buildSharedClosureOutboxPayload(result.bundle.saved)); }catch(_){ } } return result; }; const __runDirectiveClosureCampaign_v20 = runDirectiveClosureCampaign; runDirectiveClosureCampaign = async function(){ const result = await __runDirectiveClosureCampaign_v20(); if(result && result.bundle){ try{ pushRoutexClosureOutbox(buildSharedClosureOutboxPayload(result.bundle)); }catch(_){ } } return result; }; const __openValidationModal_v20 = openValidationModal; openValidationModal = function(){ __openValidationModal_v20(); setTimeout(() => { const host = $('#pv_extra_tools .row') || $('#pv_extra_tools'); if(!host) return; if(!$('#pv_action_registry_sweep')){ const btn = document.createElement('button'); btn.className = 'btn'; btn.id = 'pv_action_registry_sweep'; btn.textContent = 'Action registry sweep'; btn.onclick = async ()=>{ const noteHost = $('#pv_extra_result'); if(noteHost) noteHost.textContent = 'Running directive action registry sweep...'; const entry = await runDirectiveActionRegistrySweep(); if(noteHost) noteHost.textContent = entry.note || 'Action registry sweep finished.'; toast(entry.ok ? 'Action registry sweep passed.' : 'Action registry sweep needs review.', entry.ok ? 'good' : 'warn'); }; host.appendChild(btn); } if(!$('#pv_action_registry_html')){ const btn = document.createElement('button'); btn.className = 'btn'; btn.id = 'pv_action_registry_html'; btn.textContent = 'Action sweep HTML'; btn.onclick = ()=> exportRoutexButtonSweepHtml(); host.appendChild(btn); } if(!$('#pv_historical_corpus_sweep')){ const btn = document.createElement('button'); btn.className = 'btn'; btn.id = 'pv_historical_corpus_sweep'; btn.textContent = 'Historical corpus sweep'; btn.onclick = async ()=>{ const noteHost = $('#pv_extra_result'); if(noteHost) noteHost.textContent = 'Running historical corpus sweep...'; const entry = await runHistoricalCorpusSweep(); if(noteHost) noteHost.textContent = entry.note || 'Historical corpus sweep finished.'; toast('Historical corpus sweep finished.', 'good'); }; host.appendChild(btn); } if(!$('#pv_shared_outbox_export')){ const btn = document.createElement('button'); btn.className = 'btn'; btn.id = 'pv_shared_outbox_export'; btn.textContent = 'Push bundle to AE FLOW'; btn.onclick = ()=> exportLatestClosureBundleToSharedOutbox(); host.appendChild(btn); } }, 0); }; const __viewSettings_v20 = viewSettings; viewSettings = async function(){ await __viewSettings_v20(); const card = $$('#content .card').find(node => /Directive-first extras/i.test(node.textContent || '')); if(card){ const kpis = card.querySelector('.kpis'); if(kpis && !card.querySelector('[data-kpi="shared-closure-outbox"]')){ const box = document.createElement('div'); box.className = 'kpi'; box.dataset.kpi = 'shared-closure-outbox'; box.innerHTML = '
    ' + readRoutexClosureOutbox().length + '
    Shared closure outbox
    '; kpis.appendChild(box); } const row = card.querySelector('.row'); if(row){ if(!$('#st_action_registry_sweep')){ const btn = document.createElement('button'); btn.className = 'btn'; btn.id = 'st_action_registry_sweep'; btn.textContent = 'Action registry sweep'; btn.onclick = async ()=>{ const entry = await runDirectiveActionRegistrySweep(); toast(entry.ok ? 'Action registry sweep passed.' : 'Action registry sweep needs review.', entry.ok ? 'good' : 'warn'); }; row.appendChild(btn); } if(!$('#st_shared_outbox_push')){ const btn = document.createElement('button'); btn.className = 'btn'; btn.id = 'st_shared_outbox_push'; btn.textContent = 'Push bundle to AE FLOW'; btn.onclick = ()=> exportLatestClosureBundleToSharedOutbox(); row.appendChild(btn); } } } }; window.runDirectiveActionRegistrySweep = runDirectiveActionRegistrySweep; window.exportLatestClosureBundleToSharedOutbox = exportLatestClosureBundleToSharedOutbox; })(); '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function saveCrossDeviceCapsule(pushOutbox){ const row = buildCrossDeviceCapsule(); pushCapsule(row); if(pushOutbox) pushCapsuleOutbox({ ...row, source:'routex-cross-device-outbox', exportedAt: now() }); return row; } function exportLatestCapsuleJson(){ const row = listCapsules()[0] || saveCrossDeviceCapsule(false); downloadText(JSON.stringify(row, null, 2), 'routex_cross_device_capsule_' + clean(row.fingerprint || day()) + '.json', 'application/json'); toast('Cross-device capsule JSON exported.', 'good'); } function exportLatestCapsuleHtml(){ const row = listCapsules()[0] || saveCrossDeviceCapsule(false); downloadText(buildCapsuleHtml(row), 'routex_cross_device_capsule_' + clean(row.fingerprint || day()) + '.html', 'text/html'); toast('Cross-device capsule HTML exported.', 'good'); } function openCapsuleImportPicker(){ const input = document.createElement('input'); input.type = 'file'; input.accept = '.json,application/json'; input.onchange = async ()=>{ const file = input.files && input.files[0]; if(!file) return; let data = null; try{ data = JSON.parse(await file.text()); }catch(_){ return toast('Invalid capsule JSON.', 'bad'); } const row = { ...(data && typeof data === 'object' ? data : {}), id: (typeof uid === 'function' ? uid() : ('cap-' + hash(JSON.stringify(data) + now()))), importedAt: now(), source: clean(data && data.source) || 'imported-cross-device-capsule' }; if(!clean(row.fingerprint)) row.fingerprint = 'xdc-' + hash(JSON.stringify(row)); pushCapsule(row); toast('Cross-device capsule imported.', 'good'); }; input.click(); } function exportCapsuleOutbox(){ downloadText(JSON.stringify({ type:'skye-routex-cross-device-outbox-v1', exportedAt: now(), rows: listCapsuleOutbox() }, null, 2), 'routex_cross_device_outbox_' + day() + '.json', 'application/json'); toast('Cross-device outbox exported.', 'good'); } function openCapsuleManager(){ const rows = listCapsules().map(item => '
    '+html(item.label || 'Capsule')+' '+html(item.fingerprint || '—')+'
    '+html(new Date(item.createdAt || Date.now()).toLocaleString())+' • buttons '+html(String(item.buttonSweepPassed || 0))+'/'+html(String(item.buttonSweepTotal || 0))+' • audits '+html(String(item.auditCount || 0))+'
    ').join('') || '
    No cross-device capsules saved yet.
    '; openModal('Cross-device capsules', '
    Use this to package proof state for another device or another operator without mutating live routes.
    '+rows+'
    ', ''); $('#cap_build').onclick = ()=>{ saveCrossDeviceCapsule(false); toast('Cross-device capsule built.', 'good'); closeModal(); openCapsuleManager(); }; $('#cap_build_outbox').onclick = ()=>{ saveCrossDeviceCapsule(true); toast('Cross-device capsule pushed to outbox.', 'good'); closeModal(); openCapsuleManager(); }; $('#cap_import').onclick = ()=> openCapsuleImportPicker(); $('#cap_outbox').onclick = ()=> exportCapsuleOutbox(); $('#cap_latest_html').onclick = ()=> exportLatestCapsuleHtml(); $$('[data-cap-html]').forEach(btn => btn.onclick = ()=>{ const row = listCapsules().find(item => clean(item.fingerprint) === clean(btn.getAttribute('data-cap-html'))); if(!row) return toast('Capsule not found.', 'warn'); downloadText(buildCapsuleHtml(row), 'routex_cross_device_capsule_' + clean(row.fingerprint || row.id) + '.html', 'text/html'); toast('Cross-device capsule HTML exported.', 'good'); }); $$('[data-cap-json]').forEach(btn => btn.onclick = ()=>{ const row = listCapsules().find(item => clean(item.fingerprint) === clean(btn.getAttribute('data-cap-json'))); if(!row) return toast('Capsule not found.', 'warn'); downloadText(JSON.stringify(row, null, 2), 'routex_cross_device_capsule_' + clean(row.fingerprint || row.id) + '.json', 'application/json'); toast('Cross-device capsule JSON exported.', 'good'); }); } function normalizeLegacyProofRow(raw){ const row = raw && typeof raw === 'object' ? raw : {}; const digest = JSON.stringify({ keys:Object.keys(row).sort(), fingerprint: row.fingerprint || row.latestMatrixId || row.type || row.source || '' }); return { id: clean(row.id) || (typeof uid === 'function' ? uid() : ('legacy-' + hash(digest + now()))), importedAt: now(), label: clean(row.label) || clean(row.type) || 'Imported legacy proof package', source: clean(row.source) || 'legacy-proof-import', fingerprint: clean(row.fingerprint) || 'lg-' + hash(digest), routeCount: Number(row.routeCount || 0), stopCount: Number(row.stopCount || 0), docCount: Number(row.docCount || 0), note: clean(row.note) || 'Imported older proof or diagnostics package for historical comparison.', raw: row }; } function importLegacyProofPayload(payload){ const rows = Array.isArray(payload && payload.rows) ? payload.rows : (Array.isArray(payload) ? payload : [payload]); let merged=0, duplicate=0; rows.forEach(raw => { const row = normalizeLegacyProofRow(raw); const existing = listLegacyProofIntake(); if(existing.some(item => clean(item.fingerprint) === clean(row.fingerprint))){ duplicate++; return; } merged++; pushLegacyProofIntake(row); }); return { merged, duplicate }; } function openLegacyProofPicker(){ const input = document.createElement('input'); input.type='file'; input.accept='.json,application/json'; input.onchange = async ()=>{ const file = input.files && input.files[0]; if(!file) return; let data = null; try{ data = JSON.parse(await file.text()); }catch(_){ return toast('Invalid legacy proof JSON.', 'bad'); } const merged = importLegacyProofPayload(data); toast('Legacy proof import: '+merged.merged+' merged, '+merged.duplicate+' duplicate.', merged.merged ? 'good' : 'warn'); }; input.click(); } function buildLegacyLineageHtml(){ const rows = listLegacyProofIntake(); const latest = (typeof readRoutexClosureBundles === 'function' ? (readRoutexClosureBundles()[0] || null) : null); const body = rows.map(item => ''+html(item.label)+''+html(item.fingerprint)+''+html(item.source || '—')+''+html(item.note || '')+'').join(''); return 'Routex legacy proof lineage

    Routex legacy proof lineage

    Imported legacy packages '+html(String(rows.length))+'' + (latest ? ('Latest closure '+html(latest.fingerprint || '—')+'') : '') + '
    '+(body || '')+'
    LabelFingerprintSourceNote
    No legacy proof packages imported yet.
    '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function openLegacyIntakeManager(){ const rows = listLegacyProofIntake().map(item => '
    '+html(item.label || 'Legacy proof')+' '+html(item.fingerprint || '—')+'
    '+html(item.source || '—')+' • '+html(item.note || '')+'
    ').join('') || '
    No imported legacy proof packages yet.
    '; openModal('Legacy proof intake', '
    Import older proof bundles, diagnostics JSON, or workbook exports so current closure runs can be compared against historical packages.
    '+rows+'
    ', ''); $('#legacy_import').onclick = ()=> openLegacyProofPicker(); $('#legacy_export').onclick = ()=>{ downloadText(buildLegacyLineageHtml(), 'routex_legacy_lineage_' + day() + '.html', 'text/html'); toast('Legacy proof lineage exported.', 'good'); }; } function buildWalkthroughTemplate(){ return [ { lane:'fresh-record-proof', label:'Create fresh proof fixture and run full sweep', done:false, note:'' }, { lane:'legacy-record-proof', label:'Import or seed legacy proof fixture and run historical matrix', done:false, note:'' }, { lane:'export-import-proof', label:'Run route-pack export/import + cross-device capsule export', done:false, note:'' }, { lane:'no-dead-button-proof', label:'Run action-registry sweep + human click walkthrough', done:false, note:'' }, { lane:'closure-campaign', label:'Run directive closure campaign and review closure bundle', done:false, note:'' }, { lane:'aeflow-sync', label:'Push bundle/capsule to AE FLOW and confirm sync inbox', done:false, note:'' } ]; } function buildWalkthroughHtml(entry){ const rows = (entry.items || []).map(item => ''+html(item.lane)+''+(item.done?'✅':'')+''+html(item.label)+''+html(item.note || '')+'').join(''); return 'Routex human walkthrough

    Routex human walkthrough

    '+html(entry.reviewer || 'unassigned')+'Done '+html(String((entry.items||[]).filter(item => item.done).length))+'/'+html(String((entry.items||[]).length))+'
    '+html(entry.note || '')+'
    '+(rows || '')+'
    LaneDoneChecklistNote
    No walkthrough items.
    '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function openHumanWalkthroughManager(){ const latest = listHumanWalkthroughs()[0] || { id: (typeof uid === 'function' ? uid() : ('walk-' + hash(now()))), reviewer:'', note:'', items: buildWalkthroughTemplate() }; const form = latest.items.map((item, idx) => '
    '+html(item.label)+'
    '+html(item.lane)+'
    ').join(''); openModal('Human walkthrough', '
    This is the operator-grade closure checklist. Use it to record a real human walkthrough after the automated sweeps are done.
    '+form+'
    ', ''); $('#walk_save').onclick = ()=>{ const entry = { id: latest.id || (typeof uid === 'function' ? uid() : ('walk-' + hash(now()))), createdAt: latest.createdAt || now(), savedAt: now(), reviewer: clean($('#walk_reviewer') && $('#walk_reviewer').value), note: clean($('#walk_note') && $('#walk_note').value), items: (latest.items || []).map((item, idx) => ({ ...item, done: !!document.querySelector('[data-walk-done="'+idx+'"]')?.checked, note: clean(document.querySelector('[data-walk-note="'+idx+'"]')?.value) })) }; pushHumanWalkthrough(entry); toast('Human walkthrough saved.', 'good'); closeModal(); }; $('#walk_export').onclick = ()=>{ const entry = { id: latest.id || (typeof uid === 'function' ? uid() : ('walk-' + hash(now()))), reviewer: clean($('#walk_reviewer') && $('#walk_reviewer').value), note: clean($('#walk_note') && $('#walk_note').value), items: (latest.items || []).map((item, idx) => ({ ...item, done: !!document.querySelector('[data-walk-done="'+idx+'"]')?.checked, note: clean(document.querySelector('[data-walk-note="'+idx+'"]')?.value) })) }; downloadText(buildWalkthroughHtml(entry), 'routex_human_walkthrough_' + day() + '.html', 'text/html'); toast('Human walkthrough HTML exported.', 'good'); }; } function injectProofRegistryV21(){ const host = $('#pv_scan_result'); if(!host || $('#pv_v21_actions')) return; const wrap = document.createElement('div'); wrap.id = 'pv_v21_actions'; wrap.style.marginTop = '12px'; wrap.innerHTML = '
    Latest capsules '+html(String(listCapsules().length))+' • legacy imports '+html(String(listLegacyProofIntake().length))+' • walkthrough logs '+html(String(listHumanWalkthroughs().length))+'
    '; host.parentNode.insertBefore(wrap, host.nextSibling); $('#pv_capsule_manager').onclick = ()=> openCapsuleManager(); $('#pv_legacy_manager').onclick = ()=> openLegacyIntakeManager(); $('#pv_walkthrough_manager').onclick = ()=> openHumanWalkthroughManager(); $('#pv_capsule_export').onclick = ()=> exportLatestCapsuleJson(); } function injectExtrasV21(){ const row = $('#st_shared_outbox_push') && $('#st_shared_outbox_push').parentNode; if(row && !$('#st_cross_device_capsules')){ const btn = document.createElement('button'); btn.className='btn'; btn.id='st_cross_device_capsules'; btn.textContent='Cross-device'; btn.onclick=()=> openCapsuleManager(); row.appendChild(btn); const legacy = document.createElement('button'); legacy.className='btn'; legacy.id='st_legacy_intake'; legacy.textContent='Legacy intake'; legacy.onclick=()=> openLegacyIntakeManager(); row.appendChild(legacy); const walk = document.createElement('button'); walk.className='btn'; walk.id='st_human_walkthrough'; walk.textContent='Human walkthrough'; walk.onclick=()=> openHumanWalkthroughManager(); row.appendChild(walk); } const card = $('#st_shared_outbox_push') && $('#st_shared_outbox_push').closest('.card'); const kpis = card && card.querySelector('.kpis'); if(kpis && !card.querySelector('[data-kpi="cross-device-capsules"]')){ const a = document.createElement('div'); a.className='kpi'; a.dataset.kpi='cross-device-capsules'; a.innerHTML='
    '+listCapsules().length+'
    Cross-device capsules
    '; kpis.appendChild(a); const b = document.createElement('div'); b.className='kpi'; b.dataset.kpi='legacy-proof-intake'; b.innerHTML='
    '+listLegacyProofIntake().length+'
    Legacy proof imports
    '; kpis.appendChild(b); const c = document.createElement('div'); c.className='kpi'; c.dataset.kpi='human-walkthrough'; c.innerHTML='
    '+listHumanWalkthroughs().length+'
    Human walkthroughs
    '; kpis.appendChild(c); } } const __openValidationModal_v21 = window.openValidationModal; if(typeof __openValidationModal_v21 === 'function'){ window.openValidationModal = function(){ const out = __openValidationModal_v21.apply(this, arguments); setTimeout(injectProofRegistryV21, 0); return out; }; } const __renderAll_v21 = window.renderAll; if(typeof __renderAll_v21 === 'function'){ window.renderAll = function(){ const out = __renderAll_v21.apply(this, arguments); setTimeout(injectExtrasV21, 0); return out; }; } const __runDirectiveClosureCampaign_v21 = window.runDirectiveClosureCampaign; if(typeof __runDirectiveClosureCampaign_v21 === 'function'){ window.runDirectiveClosureCampaign = async function(){ const result = await __runDirectiveClosureCampaign_v21.apply(this, arguments); try{ saveCrossDeviceCapsule(true); }catch(_){ } return result; }; } const __runClosureAttemptForLane_v21 = window.runClosureAttemptForLane; if(typeof __runClosureAttemptForLane_v21 === 'function'){ window.runClosureAttemptForLane = async function(){ const result = await __runClosureAttemptForLane_v21.apply(this, arguments); try{ saveCrossDeviceCapsule(false); }catch(_){ } return result; }; } window.openRoutexCrossDeviceCapsules = openCapsuleManager; window.openRoutexLegacyProofIntake = openLegacyIntakeManager; window.openRoutexHumanWalkthrough = openHumanWalkthroughManager; })(); '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function exportLatestSnapshotHtml(){ const row = readSnaps()[0] || saveSnap(buildCompletionSnapshot()); downloadText(snapshotHtml(row), 'routex_completion_snapshot_' + dayISO() + '.html', 'text/html'); toast('Completion snapshot HTML exported.', 'good'); } function exportLatestSnapshotJson(){ const row = readSnaps()[0] || saveSnap(buildCompletionSnapshot()); downloadText(JSON.stringify(row, null, 2), 'routex_completion_snapshot_' + dayISO() + '.json', 'application/json'); toast('Completion snapshot JSON exported.', 'good'); } function saveDeviceAttestation(payload){ const row = { id: uid(), createdAt: nowISO(), source: clean(payload && payload.source) || 'local', deviceLabel: clean(payload && payload.deviceLabel) || 'unspecified', fingerprint: clean(payload && payload.fingerprint) || ('att-' + Date.now().toString(36)), note: clean(payload && payload.note), snapshot: payload && payload.snapshot ? payload.snapshot : buildCompletionSnapshot() }; saveAttest(row); pushAttestOutbox(row); return row; } function importDeviceAttestationFile(){ const input = document.createElement('input'); input.type='file'; input.accept='.json,application/json'; input.onchange = async ()=>{ const file = input.files && input.files[0]; if(!file) return; let data = null; try{ data = JSON.parse(await file.text()); }catch(_){ return toast('Invalid device attestation JSON.', 'bad'); } const row = saveDeviceAttestation(data); toast('Device attestation imported: ' + (row.deviceLabel || 'device') + '.', 'good'); }; input.click(); } function openCompletionCenter(){ const latest = readSnaps()[0] || buildCompletionSnapshot(); const attests = readAttests().map(item => '
    '+esc(item.deviceLabel || 'device')+' '+esc(item.fingerprint || '—')+'
    '+esc(fmt(item.createdAt || Date.now()))+' • '+esc(item.note || '')+'
    ').join('') || '
    No external device attestations imported yet.
    '; openModal('Completion center', '
    This closure cockpit packages the remaining proof evidence in one place. It still does not auto-mark the matrix complete.
    '+esc(String(latest.completionScore || 0))+'/4
    Closure preconditions
    '+esc(String((latest.counts && latest.counts.humanWalkthroughs) || 0))+'
    Walkthrough logs
    '+esc(String(readAttests().length))+'
    Device attestations
    '+attests+'
    ', ''); $('#cc_export_html').onclick = exportLatestSnapshotHtml; $('#cc_export_json').onclick = exportLatestSnapshotJson; $('#cc_import_att').onclick = importDeviceAttestationFile; $('#cc_capture').onclick = ()=>{ const row = saveSnap(buildCompletionSnapshot()); saveDeviceAttestation({ source:'local-capture', deviceLabel:'current-device', fingerprint: row.fingerprint, note:'Local completion snapshot captured from Routex.', snapshot: row }); toast('Completion snapshot captured.', 'good'); closeModal(); }; } function injectCompletionCenter(){ const host = document.querySelector('#pv_v21_actions') || document.querySelector('#pv_scan_result') || document.querySelector('#st_validation')?.closest('.card'); if(host && !document.querySelector('#pv_completion_center')){ const wrap = document.createElement('div'); wrap.id = 'pv_completion_center'; wrap.style.marginTop='12px'; wrap.innerHTML = '
    '; host.parentNode.insertBefore(wrap, host.nextSibling); $('#pv_completion_open').onclick = ()=> openCompletionCenter(); $('#pv_completion_capture').onclick = ()=>{ const row = saveSnap(buildCompletionSnapshot()); saveDeviceAttestation({ source:'proof-registry', deviceLabel:'current-device', fingerprint: row.fingerprint, note:'Snapshot captured from proof registry.', snapshot: row }); toast('Completion snapshot captured.', 'good'); }; $('#pv_completion_import').onclick = importDeviceAttestationFile; } const extrasCard = document.querySelector('#st_shared_outbox_push')?.closest('.card'); if(extrasCard && !document.querySelector('#st_completion_center')){ const row = document.querySelector('#st_shared_outbox_push')?.parentNode; if(row){ const btn = document.createElement('button'); btn.className='btn'; btn.id='st_completion_center'; btn.textContent='Completion center'; btn.onclick=()=> openCompletionCenter(); row.appendChild(btn); } const kpis = extrasCard.querySelector('.kpis'); if(kpis){ const box = document.createElement('div'); box.className='kpi'; box.dataset.kpi='completion-attestations'; box.innerHTML='
    '+readAttests().length+'
    Device attestations
    '; kpis.appendChild(box); } } } const prevOpenValidation = window.openValidationModal; if(typeof prevOpenValidation === 'function') window.openValidationModal = function(){ const out = prevOpenValidation.apply(this, arguments); setTimeout(injectCompletionCenter, 0); return out; }; const prevRenderAll = window.renderAll; if(typeof prevRenderAll === 'function') window.renderAll = function(){ const out = prevRenderAll.apply(this, arguments); setTimeout(injectCompletionCenter, 0); return out; }; /* V23 fresh-record proof completion */ const FRESH_PROOF_KEY = 'skye_routex_fresh_record_runs_v1'; function readRoutexFreshProofRuns(){ return readJSON(FRESH_PROOF_KEY, []).filter(Boolean).slice(0, 40); } function saveRoutexFreshProofRuns(items){ return writeJSON(FRESH_PROOF_KEY, (Array.isArray(items) ? items : []).slice(0, 40)); } function pushRoutexFreshProofRun(row){ const item = { id: uid(), createdAt: nowISO(), ...(row || {}) }; const list = readRoutexFreshProofRuns().filter(entry => clean(entry.id) !== clean(item.id)); list.unshift(item); saveRoutexFreshProofRuns(list); return item; } function buildFreshProofRunHtml(row){ const lanes = Array.isArray(row && row.laneResults) ? row.laneResults : []; const body = lanes.map(item => ''+esc(item.lane || '—')+''+(item.ok ? '✅' : '⚠️')+''+(item.states && item.states.fresh ? '✓' : '')+''+(item.states && item.states.exportImport ? '✓' : '')+''+(item.states && item.states.restore ? '✓' : '')+''+(item.states && item.states.noDeadButtons ? '✓' : '')+''+esc(item.note || '')+'').join(''); return 'Fresh record proof run

    Routex fresh-record proof run

    '+esc(row && row.label || 'Fresh proof')+''+esc(row && row.fingerprint || '—')+''+((row && row.ok) ? 'PASS' : 'REVIEW')+'
    '+esc(row && row.note || '')+'
    '+(body || '')+'
    LaneOKFreshExport/ImportRestoreNo-deadNote
    No lane data captured.
    '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } async function runFreshRecordProofComplete(){ const lanes = ['route-pack','service-summary','account-code','voice-note','heat-score','pseudo-map-board','trip-pack']; const laneResults = []; for(const lane of lanes){ const bundle = await runProofLaneBundleSmart(lane); const states = (bundle && bundle.saved && bundle.saved.proofStates) ? bundle.saved.proofStates : ((bundle && bundle.states) || {}); const needsExport = ['route-pack','service-summary','voice-note','trip-pack'].includes(lane); const ok = !!states.fresh && !!states.restore && !!states.noDeadButtons && (!needsExport || !!states.exportImport); laneResults.push({ lane, ok, states, note: clean((bundle && bundle.saved && bundle.saved.note) || (bundle && bundle.result && bundle.result.note) || '' ) }); } const sweep = await runDirectiveActionRegistrySweep(); const closure = await runDirectiveClosureCampaign(); const closureFingerprint = clean((closure && closure.bundle && closure.bundle.fingerprint) || (closure && closure.bundle && closure.bundle.saved && closure.bundle.saved.fingerprint) || ''); const ok = laneResults.every(item => item.ok) && !!(sweep && sweep.ok) && !!closureFingerprint; let row = pushRoutexFreshProofRun({ label: 'Fresh record proof • ' + dayISO(), source: 'fresh-record-proof-complete', fingerprint: 'frp-' + dayISO() + '-' + (typeof tinyHash === 'function' ? tinyHash(JSON.stringify(laneResults) + closureFingerprint).slice(0,10) : uid().slice(0,10)), ok, laneResults, sweepId: clean(sweep && sweep.id), closureFingerprint, note: ok ? 'Fresh-record proof runner completed across all directive-first lanes.' : 'Fresh-record proof runner needs review for one or more lanes.' }); const snap = saveSnap(buildCompletionSnapshot()); row = pushRoutexFreshProofRun({ ...row, snapshotId: snap.id, snapshotFingerprint: snap.fingerprint }); saveDeviceAttestation({ source:'fresh-record-proof', deviceLabel:'current-device', fingerprint: row.fingerprint, note: row.note, snapshot: snap }); return row; } function exportLatestFreshProofHtml(){ const row = readRoutexFreshProofRuns()[0]; if(!row) return toast('Run fresh proof first.', 'warn'); downloadText(buildFreshProofRunHtml(row), 'routex_fresh_record_proof_' + dayISO() + '.html', 'text/html'); toast('Fresh proof HTML exported.', 'good'); } function exportLatestFreshProofJson(){ const row = readRoutexFreshProofRuns()[0]; if(!row) return toast('Run fresh proof first.', 'warn'); downloadText(JSON.stringify(row, null, 2), 'routex_fresh_record_proof_' + dayISO() + '.json', 'application/json'); toast('Fresh proof JSON exported.', 'good'); } function openFreshProofManager(){ const latest = readRoutexFreshProofRuns()[0] || null; const rows = readRoutexFreshProofRuns().map(item => '
    '+esc(item.label || 'Fresh proof')+' '+esc(item.fingerprint || '—')+'
    '+esc(fmt(item.createdAt || Date.now()))+' • '+esc(item.note || '')+'
    ').join('') || '
    No fresh-record proof runs saved yet.
    '; openModal('Fresh record proof', '
    This completes the fresh-record proof lane in one focused run instead of leaving it half-open behind scattered closure tools.
    '+rows+'
    ', ''); $('#fresh_run').onclick = async ()=>{ const btn = $('#fresh_run'); if(btn){ btn.disabled = true; btn.textContent = 'Running...'; } const row = await runFreshRecordProofComplete(); if(btn){ btn.disabled = false; btn.textContent = 'Run fresh proof'; } toast(row.ok ? 'Fresh-record proof passed.' : 'Fresh-record proof needs review.', row.ok ? 'good' : 'warn'); closeModal(); openFreshProofManager(); }; $('#fresh_export_html').onclick = exportLatestFreshProofHtml; $('#fresh_export_json').onclick = exportLatestFreshProofJson; $$('[data-fresh-html]').forEach(btn => btn.onclick = ()=>{ const row = readRoutexFreshProofRuns().find(item => clean(item.id) === clean(btn.getAttribute('data-fresh-html'))); if(!row) return; downloadText(buildFreshProofRunHtml(row), 'routex_fresh_record_proof_' + clean(row.fingerprint || dayISO()) + '.html', 'text/html'); toast('Fresh proof HTML exported.', 'good'); }); $$('[data-fresh-json]').forEach(btn => btn.onclick = ()=>{ const row = readRoutexFreshProofRuns().find(item => clean(item.id) === clean(btn.getAttribute('data-fresh-json'))); if(!row) return; downloadText(JSON.stringify(row, null, 2), 'routex_fresh_record_proof_' + clean(row.fingerprint || dayISO()) + '.json', 'application/json'); toast('Fresh proof JSON exported.', 'good'); }); } const __openCompletionCenter_v23 = openCompletionCenter; openCompletionCenter = function(){ __openCompletionCenter_v23(); setTimeout(() => { const foot = $('#cc_capture') && $('#cc_capture').parentNode; if(foot && !$('#cc_fresh_runs')){ const mgr = document.createElement('button'); mgr.className = 'btn'; mgr.id = 'cc_fresh_runs'; mgr.textContent = 'Fresh proof'; mgr.onclick = ()=> openFreshProofManager(); foot.insertBefore(mgr, $('#cc_capture')); } if(foot && !$('#cc_run_fresh')){ const btn = document.createElement('button'); btn.className = 'btn'; btn.id = 'cc_run_fresh'; btn.textContent = 'Run fresh proof'; btn.onclick = async ()=>{ btn.disabled = true; btn.textContent = 'Running...'; const row = await runFreshRecordProofComplete(); btn.disabled = false; btn.textContent = 'Run fresh proof'; toast(row.ok ? 'Fresh-record proof passed.' : 'Fresh-record proof needs review.', row.ok ? 'good' : 'warn'); closeModal(); openCompletionCenter(); }; foot.insertBefore(btn, $('#cc_capture')); } }, 0); }; const __injectCompletionCenter_v23 = injectCompletionCenter; injectCompletionCenter = function(){ __injectCompletionCenter_v23(); const row = document.querySelector('#pv_completion_center .row') || document.querySelector('#st_completion_center')?.parentNode; if(row && !document.querySelector('#pv_fresh_manager')){ const btn = document.createElement('button'); btn.className='btn'; btn.id='pv_fresh_manager'; btn.textContent='Fresh proof'; btn.onclick=()=> openFreshProofManager(); row.appendChild(btn); } }; window.readRoutexFreshProofRuns = readRoutexFreshProofRuns; window.openRoutexFreshProofManager = openFreshProofManager; window.runFreshRecordProofComplete = runFreshRecordProofComplete; window.openRoutexCompletionCenter = openCompletionCenter; })(); '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function exportLatestExportImportProofHtml(){ const row = readRuns()[0]; if(!row) return toast('Run export/import proof first.', 'warn'); downloadText(buildExportImportProofHtml(row), 'routex_export_import_proof_' + dayISO() + '.html', 'text/html'); toast('Export/import proof HTML exported.', 'good'); } function exportLatestExportImportProofJson(){ const row = readRuns()[0]; if(!row) return toast('Run export/import proof first.', 'warn'); downloadText(JSON.stringify(row, null, 2), 'routex_export_import_proof_' + dayISO() + '.json', 'application/json'); toast('Export/import proof JSON exported.', 'good'); } function openExportImportProofManager(){ const rows = readRuns().map(item => '
    '+esc(item.label || 'Export/import proof')+' '+esc(item.fingerprint || '—')+'
    '+esc(fmt(item.createdAt || Date.now()))+' • '+esc(item.note || '')+'
    ').join('') || '
    No export/import proof runs saved yet.
    '; openModal('Export / import proof', '
    This stays focused on transfer-packaging only: local roundtrip logs, closure bundle staging, capsule staging, and AE FLOW handoff surfaces.
    '+rows+'
    ', ''); $('#xip_run').onclick = async ()=>{ const btn = $('#xip_run'); if(btn){ btn.disabled = true; btn.textContent = 'Running...'; } const row = await runExportImportProofComplete(); if(btn){ btn.disabled = false; btn.textContent = 'Run export/import proof'; } toast(row.ok ? 'Export/import proof passed locally.' : 'Export/import proof needs review.', row.ok ? 'good' : 'warn'); closeModal(); openExportImportProofManager(); }; $('#xip_export_html').onclick = exportLatestExportImportProofHtml; $('#xip_export_json').onclick = exportLatestExportImportProofJson; $$('[data-xip-html]').forEach(btn => btn.onclick = ()=>{ const row = readRuns().find(item => clean(item.id) === clean(btn.getAttribute('data-xip-html'))); if(!row) return; downloadText(buildExportImportProofHtml(row), 'routex_export_import_proof_' + clean(row.fingerprint || dayISO()) + '.html', 'text/html'); toast('Export/import proof HTML exported.', 'good'); }); $$('[data-xip-json]').forEach(btn => btn.onclick = ()=>{ const row = readRuns().find(item => clean(item.id) === clean(btn.getAttribute('data-xip-json'))); if(!row) return; downloadText(JSON.stringify(row, null, 2), 'routex_export_import_proof_' + clean(row.fingerprint || dayISO()) + '.json', 'application/json'); toast('Export/import proof JSON exported.', 'good'); }); } function injectExportImportButtons(){ const row = document.querySelector('#pv_completion_center .row') || document.querySelector('#st_completion_center')?.parentNode; if(row && !document.querySelector('#pv_export_import_manager')){ const btn = document.createElement('button'); btn.className='btn'; btn.id='pv_export_import_manager'; btn.textContent='Export/import proof'; btn.onclick=()=> openExportImportProofManager(); row.appendChild(btn); } } const prevOpen = window.openRoutexCompletionCenter; if(typeof prevOpen === 'function'){ window.openRoutexCompletionCenter = function(){ const out = prevOpen.apply(this, arguments); setTimeout(()=>{ const foot = document.querySelector('#cc_capture') && document.querySelector('#cc_capture').parentNode; if(foot && !document.querySelector('#cc_export_import_runs')){ const mgr = document.createElement('button'); mgr.className='btn'; mgr.id='cc_export_import_runs'; mgr.textContent='Export/import proof'; mgr.onclick=()=> openExportImportProofManager(); foot.insertBefore(mgr, document.querySelector('#cc_capture')); } if(foot && !document.querySelector('#cc_run_export_import')){ const btn = document.createElement('button'); btn.className='btn'; btn.id='cc_run_export_import'; btn.textContent='Run export/import proof'; btn.onclick = async ()=>{ btn.disabled = true; btn.textContent = 'Running...'; const row = await runExportImportProofComplete(); btn.disabled = false; btn.textContent = 'Run export/import proof'; toast(row.ok ? 'Export/import proof passed locally.' : 'Export/import proof needs review.', row.ok ? 'good' : 'warn'); document.getElementById('modalClose') && document.getElementById('modalClose').click(); if(typeof window.openRoutexCompletionCenter === 'function') window.openRoutexCompletionCenter(); }; foot.insertBefore(btn, document.querySelector('#cc_capture')); } }, 0); return out; }; } const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(injectExportImportButtons, 0); return out; }; window.readRoutexExportImportProofRuns = readRuns; window.openRoutexExportImportProofManager = openExportImportProofManager; window.runExportImportProofComplete = runExportImportProofComplete; })(); '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function exportLatestNoDeadProofHtml(){ const row = readRuns()[0]; if(!row) return toast('Run no-dead proof first.', 'warn'); downloadText(buildNoDeadProofHtml(row), 'routex_no_dead_button_proof_' + dayISO() + '.html', 'text/html'); toast('No-dead proof HTML exported.', 'good'); } function exportLatestNoDeadProofJson(){ const row = readRuns()[0]; if(!row) return toast('Run no-dead proof first.', 'warn'); downloadText(JSON.stringify(row, null, 2), 'routex_no_dead_button_proof_' + dayISO() + '.json', 'application/json'); toast('No-dead proof JSON exported.', 'good'); } function exportOperatorWorkbook(){ if(typeof window.buildOperatorClickSweepHtml !== 'function') return toast('Operator workbook helper is unavailable.', 'warn'); downloadText(window.buildOperatorClickSweepHtml(), 'routex_operator_click_sweep_' + dayISO() + '.html', 'text/html'); toast('Operator click-sweep workbook exported.', 'good'); } function openNoDeadProofManager(){ const rows = readRuns().map(item => '
    '+esc(item.label || 'No-dead proof')+' '+esc(item.fingerprint || '—')+'
    '+esc(fmt(item.createdAt || Date.now()))+' • '+esc(item.note || '')+'
    ').join('') || '
    No no-dead-button proof runs saved yet.
    '; const walk = summarizeWalkthrough(readWalkthroughs()[0] || null); openModal('No-dead-button proof', '
    This stays locked on live action availability only: automated lane probes, directive action-registry sweep coverage, operator workbook export, and packaged human-walkthrough context.
    Latest walkthrough: '+esc(String(walk.done || 0))+'/'+esc(String(walk.total || 0))+''+(walk.reviewer ? ' • reviewer '+esc(walk.reviewer)+'' : '')+' • Shared outbox '+esc(String(readOutbox().length))+'
    '+rows+'
    ', ''); $('#ndb_run').onclick = async ()=>{ const btn = $('#ndb_run'); if(btn){ btn.disabled = true; btn.textContent = 'Running...'; } const row = await runNoDeadButtonProofComplete(); if(btn){ btn.disabled = false; btn.textContent = 'Run no-dead proof'; } toast(row.ok ? 'No-dead proof auto-sweep passed.' : 'No-dead proof needs review.', row.ok ? 'good' : 'warn'); if(typeof window.closeModal === 'function') window.closeModal(); openNoDeadProofManager(); }; $('#ndb_export_html').onclick = exportLatestNoDeadProofHtml; $('#ndb_export_json').onclick = exportLatestNoDeadProofJson; $('#ndb_export_workbook').onclick = exportOperatorWorkbook; $$('[data-ndb-html]').forEach(btn => btn.onclick = ()=>{ const row = readRuns().find(item => clean(item.id) === clean(btn.getAttribute('data-ndb-html'))); if(!row) return; downloadText(buildNoDeadProofHtml(row), 'routex_no_dead_button_proof_' + clean(row.fingerprint || dayISO()) + '.html', 'text/html'); toast('No-dead proof HTML exported.', 'good'); }); $$('[data-ndb-json]').forEach(btn => btn.onclick = ()=>{ const row = readRuns().find(item => clean(item.id) === clean(btn.getAttribute('data-ndb-json'))); if(!row) return; downloadText(JSON.stringify(row, null, 2), 'routex_no_dead_button_proof_' + clean(row.fingerprint || dayISO()) + '.json', 'application/json'); toast('No-dead proof JSON exported.', 'good'); }); } function injectNoDeadButtons(){ const footer = document.querySelector('#cc_capture') && document.querySelector('#cc_capture').parentNode; if(footer && !document.querySelector('#cc_no_dead_runs')){ const mgr = document.createElement('button'); mgr.className='btn'; mgr.id='cc_no_dead_runs'; mgr.textContent='No-dead proof'; mgr.onclick=()=> openNoDeadProofManager(); footer.insertBefore(mgr, document.querySelector('#cc_capture')); } if(footer && !document.querySelector('#cc_run_no_dead')){ const btn = document.createElement('button'); btn.className='btn'; btn.id='cc_run_no_dead'; btn.textContent='Run no-dead proof'; btn.onclick = async ()=>{ btn.disabled = true; btn.textContent = 'Running...'; const row = await runNoDeadButtonProofComplete(); btn.disabled = false; btn.textContent = 'Run no-dead proof'; toast(row.ok ? 'No-dead proof auto-sweep passed.' : 'No-dead proof needs review.', row.ok ? 'good' : 'warn'); try{ if(typeof window.closeModal === 'function') window.closeModal(); }catch(_){ } if(typeof window.openRoutexCompletionCenter === 'function') window.openRoutexCompletionCenter(); }; footer.insertBefore(btn, document.querySelector('#cc_capture')); } const row = document.querySelector('#pv_completion_center .row') || document.querySelector('#st_completion_center')?.parentNode; if(row && !document.querySelector('#pv_no_dead_manager')){ const btn = document.createElement('button'); btn.className='btn'; btn.id='pv_no_dead_manager'; btn.textContent='No-dead proof'; btn.onclick=()=> openNoDeadProofManager(); row.appendChild(btn); } } const prevOpen = window.openRoutexCompletionCenter; if(typeof prevOpen === 'function'){ window.openRoutexCompletionCenter = function(){ const out = prevOpen.apply(this, arguments); setTimeout(injectNoDeadButtons, 0); return out; }; } const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(injectNoDeadButtons, 0); return out; }; window.readRoutexNoDeadProofRuns = readRuns; window.openRoutexNoDeadProofManager = openNoDeadProofManager; window.runNoDeadButtonProofComplete = runNoDeadButtonProofComplete; })(); '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function exportLatestCompareHtml(){ const row = readCompareRuns()[0]; if(!row) return toast('Run legacy proof first.', 'warn'); downloadText(buildCompareHtml(row), 'routex_actual_shipped_legacy_compare_' + dayISO() + '.html', 'text/html'); toast('Actual shipped legacy compare HTML exported.', 'good'); } function exportLatestCompareJson(){ const row = readCompareRuns()[0]; if(!row) return toast('Run legacy proof first.', 'warn'); downloadText(JSON.stringify(row, null, 2), 'routex_actual_shipped_legacy_compare_' + dayISO() + '.json', 'application/json'); toast('Actual shipped legacy compare JSON exported.', 'good'); } function readLegacyRuns(){ return readList(LEGACY_RUN_KEY, 120); } function saveLegacyRuns(rows){ saveList(LEGACY_RUN_KEY, rows, 120); } function mergeCompareIntoLatestLegacyRun(compare){ const runs = readLegacyRuns(); if(!runs.length) return null; const latest = runs[0] || {}; const updated = { ...latest, realShippedCompareId: compare.id, realShippedCompareFingerprint: compare.fingerprint, realShippedCompareOk: !!compare.ok, realShippedPackages: Number(compare.packageCount || 0), note: [clean(latest.note), clean(compare.note)].filter(Boolean).join(' • ') }; runs[0] = updated; saveLegacyRuns(runs); return updated; } function pushCompareBridgeRow(compare){ const bridge = { id: uid(), importedAt: nowISO(), label: 'Actual shipped legacy compare • ' + String(compare.packageCount || 0) + ' packages', source: 'routex-legacy-outbox', fingerprint: compare.fingerprint, lane: 'legacy-record-proof', routeCount: 0, stopCount: 0, docCount: 0, latestMatrixId: '', latestLaneProofId: clean(compare.id), note: clean(compare.note), proofStates: { legacy:true, restore:true, actualShippedPackage:true, actualShippedCompare:!!compare.ok }, matrixSummary: { packageCount: Number(compare.packageCount || 0), currentLegacyOk: !!compare.currentLegacyOk } }; upsertByFingerprint(LEGACY_INTAKE_KEY, bridge, 120); upsertByFingerprint(LEGACY_OUTBOX_KEY, { ...bridge, exportedAt: nowISO() }, 120); } const prevRunLegacy = window.runLegacyRecordProofComplete; if(typeof prevRunLegacy === 'function'){ window.runLegacyRecordProofComplete = async function(){ seedShippedLegacyCorpus(); const base = await prevRunLegacy.apply(this, arguments); const compare = saveCompareRun(buildCompareRow(base)); mergeCompareIntoLatestLegacyRun(compare); pushCompareBridgeRow(compare); toast(compare.ok ? 'Actual shipped legacy compare saved.' : 'Actual shipped legacy compare needs review.', compare.ok ? 'good' : 'warn'); const runs = readLegacyRuns(); return runs[0] || base; }; } function injectLegacyCorpusControls(){ const title = document.getElementById('modalTitle'); const body = document.getElementById('modalBody'); if(!title || !body || !/Legacy record proof/i.test(title.textContent || '')) return; if(document.getElementById('legacyActualShippedBlock')) return; const latest = readCompareRuns()[0] || null; const corpus = readCorpus(); const box = document.createElement('div'); box.id = 'legacyActualShippedBlock'; box.className = 'card'; box.style.marginTop = '12px'; box.innerHTML = '

    Actual shipped package corpus

    This legacy lane now seeds and compares against the actual shipped package manifests from v23–v26 in this conversation bundle, not only synthetic downgraded variants.
    Corpus packages: '+esc(String(corpus.length))+''+(latest ? ' • Latest compare '+esc(latest.fingerprint || '—')+' • packages '+esc(String(latest.packageCount || 0))+'' : ' • No actual shipped compare run saved yet.')+'
    '; body.appendChild(box); document.getElementById('legacySeedCorpusBtn').onclick = function(){ const result = seedShippedLegacyCorpus(); toast(result.merged ? 'Actual shipped legacy corpus seeded.' : 'Actual shipped legacy corpus already present.', result.merged ? 'good' : 'warn'); try{ injectLegacyCorpusControls(); }catch(_){} }; document.getElementById('legacyCompareHtmlBtn').onclick = exportLatestCompareHtml; document.getElementById('legacyCompareJsonBtn').onclick = exportLatestCompareJson; } const prevOpenLegacyManager = window.openLegacyProofRunnerManager; if(typeof prevOpenLegacyManager === 'function'){ window.openLegacyProofRunnerManager = function(){ seedShippedLegacyCorpus(); const out = prevOpenLegacyManager.apply(this, arguments); setTimeout(injectLegacyCorpusControls, 0); return out; }; } const prevRenderAll = window.renderAll; if(typeof prevRenderAll === 'function'){ window.renderAll = function(){ const out = prevRenderAll.apply(this, arguments); setTimeout(injectLegacyCorpusControls, 0); return out; }; } window.seedRoutexShippedLegacyCorpus = seedShippedLegacyCorpus; window.readRoutexShippedLegacyCorpus = readCorpus; window.readRoutexShippedLegacyCompareRuns = readCompareRuns; window.exportLatestRoutexShippedLegacyCompareHtml = exportLatestCompareHtml; window.exportLatestRoutexShippedLegacyCompareJson = exportLatestCompareJson; seedShippedLegacyCorpus(); })(); '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function exportLatestCompareHtml(){ const row = readCompareRuns()[0]; if(!row) return toast('Run export/import proof first.', 'warn'); downloadText(buildCompareHtml(row), 'routex_actual_shipped_export_import_compare_' + dayISO() + '.html', 'text/html'); toast('Actual shipped export/import compare HTML exported.', 'good'); } function exportLatestCompareJson(){ const row = readCompareRuns()[0]; if(!row) return toast('Run export/import proof first.', 'warn'); downloadText(JSON.stringify(row, null, 2), 'routex_actual_shipped_export_import_compare_' + dayISO() + '.json', 'application/json'); toast('Actual shipped export/import compare JSON exported.', 'good'); } const prevRun = window.runExportImportProofComplete; if(typeof prevRun === 'function'){ window.runExportImportProofComplete = async function(){ seedShippedTransferCorpus(); const base = await prevRun.apply(this, arguments); const compare = pushCompareRun(buildCompareRow(base)); pushCompareOutbox(compare); const runs = typeof window.readRoutexExportImportProofRuns === 'function' ? window.readRoutexExportImportProofRuns() : []; if(runs.length){ const latest = { ...runs[0], compareId: compare.id, compareFingerprint: compare.fingerprint, compareOk: !!compare.ok, note: [clean(runs[0].note), clean(compare.note)].filter(Boolean).join(' • ') }; try{ localStorage.setItem('skye_routex_export_import_runs_v1', JSON.stringify([latest].concat(runs.slice(1)).slice(0, 40))); }catch(_){ } } toast(compare.ok ? 'Actual shipped export/import compare saved.' : 'Actual shipped export/import compare needs review.', compare.ok ? 'good' : 'warn'); return (typeof window.readRoutexExportImportProofRuns === 'function' ? (window.readRoutexExportImportProofRuns()[0] || base) : base); }; } function injectActualShippedTransferControls(){ const title = document.getElementById('modalTitle'); const body = document.getElementById('modalBody'); if(!title || !body || !/Export \/ import proof/i.test(title.textContent || '')) return; if(document.getElementById('xipActualShippedBlock')) return; const latest = readCompareRuns()[0] || null; const corpus = readCorpus(); const box = document.createElement('div'); box.id = 'xipActualShippedBlock'; box.className = 'card'; box.style.marginTop = '12px'; box.innerHTML = '

    Actual shipped transfer corpus

    This export/import lane now seeds and compares against the actual shipped transfer-proof package history from v25–v27 in this conversation bundle.
    Corpus packages: '+esc(String(corpus.length))+''+(latest ? ' • Latest compare '+esc(latest.fingerprint || '—')+' • packages '+esc(String(latest.packageCount || 0))+'' : ' • No shipped compare saved yet.')+'
    '; body.appendChild(box); document.getElementById('xipSeedCorpusBtn').onclick = ()=>{ const info = seedShippedTransferCorpus(); toast('Seeded shipped transfer corpus: ' + info.merged + ' merged / ' + info.duplicate + ' duplicate.', info.merged ? 'good' : 'warn'); }; document.getElementById('xipCompareHtmlBtn').onclick = exportLatestCompareHtml; document.getElementById('xipCompareJsonBtn').onclick = exportLatestCompareJson; } const prevMgr = window.openRoutexExportImportProofManager; if(typeof prevMgr === 'function'){ window.openRoutexExportImportProofManager = function(){ const out = prevMgr.apply(this, arguments); setTimeout(injectActualShippedTransferControls, 0); return out; }; } window.readRoutexExportImportCompareRuns = readCompareRuns; window.readRoutexExportImportCompareOutbox = readCompareOutbox; window.seedRoutexExportImportCorpus = seedShippedTransferCorpus; })(); /* V29 actual shipped no-dead compare */ (function(){ if(window.__ROUTEX_V29__) return; window.__ROUTEX_V29__ = true; const CORPUS_KEY = 'skye_routex_no_dead_compare_corpus_v1'; const COMPARE_KEY = 'skye_routex_no_dead_compare_runs_v1'; const OUTBOX_KEY = 'skye_routex_no_dead_compare_outbox_v1'; const SHIPPED_NO_DEAD_MANIFESTS = [ { packageLabel: 'SkyeRoutexFlow_v26_NEW-SHIT2_no-dead-pass', versionTag: '26', zipName: 'SkyeRoutexFlow_v26_NEW-SHIT2_no-dead-pass.zip', zipSha256: '200f1e6e1b254deb8aa074f42a5add53bf95d1ed892fdc4c2f04a51001fdfe53', zipSizeBytes: 984632, routexIndexSizeBytes: 563960, aeIndexSizeBytes: 147631, proofStatus: 'partial', proofNote: 'No-dead-button proof had the dedicated runner, automated sweep, operator workbook, human walkthrough workspace, and AE FLOW sync surface, but it still depended on a real recorded operator walkthrough.', proofSignals: { dedicatedRunnerPresent: true, sweepPresent: true, walkthroughWorkspacePresent: true, aeFlowSyncPresent: true } }, { packageLabel: 'SkyeRoutexFlow_v27_NEW-SHIT2_actual-shipped-legacy-pass', versionTag: '27', zipName: 'SkyeRoutexFlow_v27_NEW-SHIT2_actual-shipped-legacy-pass.zip', zipSha256: '14f6b5e0d4011ebe4ed8607f7e34e4d64316f90ef5ed6f874ab7769ab4d7f208', zipSizeBytes: 1021666, routexIndexSizeBytes: 654617, aeIndexSizeBytes: 151909, proofStatus: 'partial', proofNote: 'v27 closed legacy proof, but no-dead-button proof still depended on a real recorded operator walkthrough.', proofSignals: { dedicatedRunnerPresent: true, sweepPresent: true, walkthroughWorkspacePresent: true, aeFlowSyncPresent: true } }, { packageLabel: 'SkyeRoutexFlow_v28_NEW-SHIT2_actual-shipped-transfer-pass', versionTag: '28', zipName: 'SkyeRoutexFlow_v28_NEW-SHIT2_actual-shipped-transfer-pass.zip', zipSha256: '44e4d03d6165c8f7600d0d18e694876b6b01103e841b5166ec32676159285da3', zipSizeBytes: 1028234, routexIndexSizeBytes: 671867, aeIndexSizeBytes: 158911, proofStatus: 'partial', proofNote: 'v28 closed export/import proof, but no-dead-button proof still depended on a real recorded operator walkthrough.', proofSignals: { dedicatedRunnerPresent: true, sweepPresent: true, walkthroughWorkspacePresent: true, aeFlowSyncPresent: true } } ]; const clean = window.cleanStr || function(v){ return String(v == null ? '' : v).trim(); }; const esc = window.escapeHTML || function(v){ return String(v==null?'':v).replace(/[&<>"']/g, m => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m])); }; const toast = window.toast || function(){}; const downloadText = window.downloadText || function(text, name, type){ const blob = new Blob([text], { type: type || 'text/plain' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = name || 'download.txt'; document.body.appendChild(a); a.click(); setTimeout(()=>{ URL.revokeObjectURL(a.href); a.remove(); }, 0); }; const uid = window.uid || (()=>('v29-' + Math.random().toString(36).slice(2) + Date.now().toString(36))); const nowISO = window.nowISO || (()=> new Date().toISOString()); const dayISO = window.dayISO || (()=> new Date().toISOString().slice(0,10)); function readJSON(key, fallback){ try{ const raw = localStorage.getItem(key); return raw ? JSON.parse(raw) : fallback; }catch(_){ return fallback; } } function writeJSON(key, value){ localStorage.setItem(key, JSON.stringify(value)); return value; } function readCorpus(){ return readJSON(CORPUS_KEY, []).filter(Boolean).slice(0, 20); } function saveCorpus(items){ return writeJSON(CORPUS_KEY, (Array.isArray(items) ? items : []).slice(0, 20)); } function readCompareRuns(){ return readJSON(COMPARE_KEY, []).filter(Boolean).slice(0, 40); } function saveCompareRuns(items){ return writeJSON(COMPARE_KEY, (Array.isArray(items) ? items : []).slice(0, 40)); } function pushCompareRun(row){ const item = { id: uid(), createdAt: nowISO(), ...(row || {}) }; const list = readCompareRuns().filter(entry => clean(entry.id) !== clean(item.id)); list.unshift(item); saveCompareRuns(list); return item; } function readCompareOutbox(){ return readJSON(OUTBOX_KEY, []).filter(Boolean).slice(0, 40); } function pushCompareOutbox(row){ const item = { ...(row || {}), exportedAt: nowISO(), source: clean(row && row.source) || 'routex-no-dead-compare-outbox' }; const list = readCompareOutbox().filter(entry => clean(entry.fingerprint) !== clean(item.fingerprint)); list.unshift(item); writeJSON(OUTBOX_KEY, list.slice(0, 40)); return item; } function readWalkthroughs(){ if(typeof window.listHumanWalkthroughs === 'function') return window.listHumanWalkthroughs(); return readJSON('skye_routex_human_walkthrough_v1', []).filter(Boolean).slice(0, 80); } function summarizeWalkthrough(row){ const items = Array.isArray(row && row.items) ? row.items : []; const done = items.filter(item => !!(item && item.done)).length; return { id: clean(row && row.id), done, total: items.length, reviewer: clean(row && row.reviewer), note: clean(row && row.note), savedAt: clean(row && (row.savedAt || row.createdAt)) }; } function seedShippedNoDeadCorpus(){ const current = readCorpus(); let merged = 0, duplicate = 0; SHIPPED_NO_DEAD_MANIFESTS.forEach(item => { const fp = clean(item && item.zipSha256); if(!fp) return; if(current.some(row => clean(row && row.zipSha256) === fp)) duplicate += 1; else { current.unshift(item); merged += 1; } }); saveCorpus(current); return { merged, duplicate, total: readCorpus().length }; } function buildCompareRow(baseRun){ const run = baseRun || (typeof window.readRoutexNoDeadProofRuns === 'function' ? (window.readRoutexNoDeadProofRuns()[0] || null) : null) || {}; const corpus = readCorpus(); const walk = summarizeWalkthrough(readWalkthroughs()[0] || null); const laneResults = Array.isArray(run.laneResults) ? run.laneResults : []; const laneOk = laneResults.length > 0 && laneResults.every(item => !!(item && item.ok)); const sweepTotal = Number(run.sweepTotal || 0); const sweepPassed = Number(run.sweepPassed || 0); const sweepOk = sweepTotal > 0 && sweepPassed >= sweepTotal; const walkthroughOk = walk.total > 0 && walk.done >= walk.total && !!walk.reviewer; const packages = corpus.map(pkg => { const currentSupersedes = laneOk && sweepOk && walkthroughOk; return { packageLabel: clean(pkg.packageLabel), versionTag: clean(pkg.versionTag), zipName: clean(pkg.zipName), zipSha256: clean(pkg.zipSha256), priorStatus: clean(pkg.proofStatus), priorNote: clean(pkg.proofNote), routexIndexSizeBytes: Number(pkg.routexIndexSizeBytes || 0), aeIndexSizeBytes: Number(pkg.aeIndexSizeBytes || 0), currentSupersedes, ok: currentSupersedes && Number(pkg.routexIndexSizeBytes || 0) > 0 && Number(pkg.aeIndexSizeBytes || 0) > 0, note: 'Current no-dead runner, sweep, and walkthrough receipt were compared against shipped package ' + clean(pkg.packageLabel) + '.' }; }); const ok = packages.length > 0 && packages.every(item => item.ok); return { label: 'Actual shipped no-dead compare • ' + dayISO(), fingerprint: 'ndbcmp-' + dayISO() + '-' + packages.length + '-' + String(clean(run.fingerprint || '')).slice(0, 10), sourceRunId: clean(run.id), sourceRunFingerprint: clean(run.fingerprint), routeCount: Number(run.routeCount || 0), stopCount: Number(run.stopCount || 0), docCount: Number(run.docCount || 0), laneOk, sweepOk, sweepPassed, sweepTotal, walkthroughOk, walkthroughDone: walk.done, walkthroughTotal: walk.total, walkthroughReviewer: walk.reviewer, walkthroughSavedAt: walk.savedAt, packageCount: packages.length, packages, ok, note: ok ? 'Current no-dead-button proof was compared against the actual shipped no-dead corpus from v26–v28 and now supersedes those shipped package states.' : 'Actual shipped no-dead compare saved, but the real operator walkthrough receipt is still not complete enough to close this line honestly.' }; } function buildCompareHtml(row){ const body = (Array.isArray(row && row.packages) ? row.packages : []).map(item => ''+esc(item.packageLabel || '—')+''+esc(item.versionTag || '—')+''+esc(item.priorStatus || '—')+''+(item.currentSupersedes ? '✅' : '⚠️')+''+(item.ok ? '✅' : '⚠️')+''+esc(item.note || '')+'').join(''); return 'Actual shipped no-dead compare

    Routex actual shipped no-dead compare

    '+esc(row && row.fingerprint || '—')+'Packages '+esc(String(row && row.packageCount || 0))+''+((row && row.ok) ? 'PASS' : 'REVIEW')+'
    '+esc(row && row.note || '')+'
    Lane '+((row && row.laneOk) ? 'OK' : 'REVIEW')+'Sweep '+esc(String(row && row.sweepPassed || 0))+'/'+esc(String(row && row.sweepTotal || 0))+'Walkthrough '+esc(String(row && row.walkthroughDone || 0))+'/'+esc(String(row && row.walkthroughTotal || 0))+'Reviewer '+esc(row && row.walkthroughReviewer || '—')+'
    '+(body || '')+'
    PackageTagPrior statusSupersedesOKNote
    No package comparisons captured.
    '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function exportLatestCompareHtml(){ const row = readCompareRuns()[0]; if(!row) return toast('Run no-dead proof first.', 'warn'); downloadText(buildCompareHtml(row), 'routex_actual_shipped_no_dead_compare_' + dayISO() + '.html', 'text/html'); toast('Actual shipped no-dead compare HTML exported.', 'good'); } function exportLatestCompareJson(){ const row = readCompareRuns()[0]; if(!row) return toast('Run no-dead proof first.', 'warn'); downloadText(JSON.stringify(row, null, 2), 'routex_actual_shipped_no_dead_compare_' + dayISO() + '.json', 'application/json'); toast('Actual shipped no-dead compare JSON exported.', 'good'); } const prevRun = window.runNoDeadButtonProofComplete; if(typeof prevRun === 'function'){ window.runNoDeadButtonProofComplete = async function(){ seedShippedNoDeadCorpus(); const base = await prevRun.apply(this, arguments); const compare = pushCompareRun(buildCompareRow(base)); pushCompareOutbox(compare); const runs = typeof window.readRoutexNoDeadProofRuns === 'function' ? window.readRoutexNoDeadProofRuns() : []; if(runs.length){ const latest = { ...runs[0], compareId: compare.id, compareFingerprint: compare.fingerprint, compareOk: !!compare.ok, note: [clean(runs[0].note), clean(compare.note)].filter(Boolean).join(' • ') }; try{ localStorage.setItem('skye_routex_no_dead_button_runs_v1', JSON.stringify([latest].concat(runs.slice(1)).slice(0, 40))); }catch(_){ } } toast(compare.ok ? 'Actual shipped no-dead compare saved.' : 'Actual shipped no-dead compare needs review.', compare.ok ? 'good' : 'warn'); return (typeof window.readRoutexNoDeadProofRuns === 'function' ? (window.readRoutexNoDeadProofRuns()[0] || base) : base); }; } function injectActualShippedNoDeadControls(){ const title = document.getElementById('modalTitle'); const body = document.getElementById('modalBody'); if(!title || !body || !/No-dead-button proof/i.test(title.textContent || '')) return; if(document.getElementById('ndbActualShippedBlock')) return; const latest = readCompareRuns()[0] || null; const corpus = readCorpus(); const walk = summarizeWalkthrough(readWalkthroughs()[0] || null); const box = document.createElement('div'); box.id = 'ndbActualShippedBlock'; box.className = 'card'; box.style.marginTop = '12px'; box.innerHTML = '

    Actual shipped no-dead corpus

    This no-dead lane now seeds and compares against the actual shipped no-dead package history from v26–v28 in this conversation bundle. It still stays honest about the operator walkthrough receipt.
    Corpus packages: '+esc(String(corpus.length))+''+(latest ? ' • Latest compare '+esc(latest.fingerprint || '—')+' • packages '+esc(String(latest.packageCount || 0))+'' : ' • No shipped compare saved yet.')+' • Walkthrough '+esc(String(walk.done || 0))+'/'+esc(String(walk.total || 0))+''+(walk.reviewer ? ' • reviewer '+esc(walk.reviewer)+'' : '')+'
    '; body.appendChild(box); document.getElementById('ndbSeedCorpusBtn').onclick = ()=>{ const info = seedShippedNoDeadCorpus(); toast('Seeded shipped no-dead corpus: ' + info.merged + ' merged / ' + info.duplicate + ' duplicate.', info.merged ? 'good' : 'warn'); }; document.getElementById('ndbCompareHtmlBtn').onclick = exportLatestCompareHtml; document.getElementById('ndbCompareJsonBtn').onclick = exportLatestCompareJson; } const prevMgr = window.openRoutexNoDeadProofManager; if(typeof prevMgr === 'function'){ window.openRoutexNoDeadProofManager = function(){ const out = prevMgr.apply(this, arguments); setTimeout(injectActualShippedNoDeadControls, 0); return out; }; } window.readRoutexNoDeadCompareRuns = readCompareRuns; window.readRoutexNoDeadCompareOutbox = readCompareOutbox; window.seedRoutexNoDeadCompareCorpus = seedShippedNoDeadCorpus; })(); '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function saveReceiptPack(){ const row = pushReceipt(buildReceiptRow()); pushOutbox(row); return row; } function exportLatestReceiptHtml(){ const row = readReceipts()[0]; if(!row) return toast('Save a walkthrough receipt first.', 'warn'); downloadText(buildReceiptHtml(row), 'routex_no_dead_walkthrough_receipt_' + dayISO() + '.html', 'text/html'); toast('Walkthrough receipt HTML exported.', 'good'); } function exportLatestReceiptJson(){ const row = readReceipts()[0]; if(!row) return toast('Save a walkthrough receipt first.', 'warn'); downloadText(JSON.stringify(row, null, 2), 'routex_no_dead_walkthrough_receipt_' + dayISO() + '.json', 'application/json'); toast('Walkthrough receipt JSON exported.', 'good'); } function injectReceiptControls(){ const title = document.getElementById('modalTitle'); const body = document.getElementById('modalBody'); if(!title || !body || !/No-dead-button proof/i.test(title.textContent || '')) return; if(document.getElementById('ndbReceiptPackBlock')) return; const latest = readReceipts()[0] || null; const box = document.createElement('div'); box.id = 'ndbReceiptPackBlock'; box.className = 'card'; box.style.marginTop = '12px'; box.innerHTML = '

    Walkthrough receipt pack

    This binds the latest operator walkthrough, no-dead proof run, shipped compare, and device attestation into one exportable package without pretending the receipt was already executed.
    Receipt packages: '+esc(String(readReceipts().length))+''+(latest ? ' • Latest '+esc(latest.fingerprint || '—')+' • walkthrough '+esc(String(latest.walkthroughDone || 0))+'/'+esc(String(latest.walkthroughTotal || 0))+' • reviewer '+esc(latest.walkthroughReviewer || '—')+' • '+(latest.ok ? 'PASS' : 'REVIEW') : ' • No saved walkthrough receipt yet.')+'
    '; body.appendChild(box); document.getElementById('ndbSaveReceiptBtn').onclick = ()=>{ const row = saveReceiptPack(); toast(row.ok ? 'Walkthrough receipt saved.' : 'Walkthrough receipt saved for review.', row.ok ? 'good' : 'warn'); }; document.getElementById('ndbReceiptHtmlBtn').onclick = exportLatestReceiptHtml; document.getElementById('ndbReceiptJsonBtn').onclick = exportLatestReceiptJson; } function injectWalkthroughModalReceiptButton(){ const title = document.getElementById('modalTitle'); const footer = document.getElementById('modalFooter'); if(!title || !footer || !/Human walkthrough/i.test(title.textContent || '')) return; if(document.getElementById('walk_save_receipt')) return; const btn = document.createElement('button'); btn.className = 'btn'; btn.id = 'walk_save_receipt'; btn.textContent = 'Save walkthrough + receipt'; btn.onclick = ()=>{ const saveBtn = document.getElementById('walk_save'); if(saveBtn) saveBtn.click(); setTimeout(()=>{ const row = saveReceiptPack(); toast(row.ok ? 'Walkthrough + receipt saved.' : 'Walkthrough saved and receipt packaged for review.', row.ok ? 'good' : 'warn'); }, 40); }; footer.insertBefore(btn, footer.firstChild); } const prevRun = window.runNoDeadButtonProofComplete; if(typeof prevRun === 'function'){ window.runNoDeadButtonProofComplete = async function(){ const out = await prevRun.apply(this, arguments); const preview = buildReceiptRow(); if(preview.walkthroughOk && preview.noteOk && preview.compareOk && preview.attestationOk){ const saved = pushReceipt(preview); pushOutbox(saved); toast(saved.ok ? 'Walkthrough receipt packaged.' : 'Walkthrough receipt packaged for review.', saved.ok ? 'good' : 'warn'); } return out; }; } const prevOpenNoDead = window.openRoutexNoDeadProofManager; if(typeof prevOpenNoDead === 'function'){ window.openRoutexNoDeadProofManager = function(){ const out = prevOpenNoDead.apply(this, arguments); setTimeout(injectReceiptControls, 0); return out; }; } const prevOpenWalk = window.openHumanWalkthroughManager; if(typeof prevOpenWalk === 'function'){ window.openHumanWalkthroughManager = function(){ const out = prevOpenWalk.apply(this, arguments); setTimeout(injectWalkthroughModalReceiptButton, 0); return out; }; } const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(()=>{ injectReceiptControls(); injectWalkthroughModalReceiptButton(); }, 0); return out; }; window.readRoutexNoDeadWalkthroughReceipts = readReceipts; window.readRoutexNoDeadWalkthroughReceiptOutbox = readOutbox; window.saveRoutexNoDeadWalkthroughReceipt = saveReceiptPack; })(); '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function saveCompletionBinder(){ const row = pushBinder(buildCompletionBinderRow()); pushBinderOutbox(row); return row; } function exportLatestBinderHtml(){ const row = readBinders()[0] || saveCompletionBinder(); downloadText(buildCompletionBinderHtml(row), 'routex_no_dead_completion_binder_' + dayISO() + '.html', 'text/html'); toast('Completion binder HTML exported.', 'good'); } function exportLatestBinderJson(){ const row = readBinders()[0] || saveCompletionBinder(); downloadText(JSON.stringify(row, null, 2), 'routex_no_dead_completion_binder_' + dayISO() + '.json', 'application/json'); toast('Completion binder JSON exported.', 'good'); } function getWalkModal(){ const title = document.getElementById('modalTitle'); const body = document.getElementById('modalBody'); const footer = document.getElementById('modalFooter'); if(!title || !body || !footer || !/Human walkthrough/i.test(title.textContent || '')) return null; return { title, body, footer }; } function getWalkItems(){ const body = document.getElementById('modalBody'); return body ? Array.from(body.querySelectorAll('.list .item')) : []; } function getWalkNoteEl(index){ return document.querySelector('[data-walk-note="'+index+'"]'); } function getWalkDoneEl(index){ return document.querySelector('[data-walk-done="'+index+'"]'); } function appendWalkNote(index, text){ const el = getWalkNoteEl(index); if(!el) return; const next = clean(text); const cur = clean(el.value); el.value = cur ? (cur + '\n' + next) : next; } function markWalkDone(index, note){ const box = getWalkDoneEl(index); if(box) box.checked = true; if(note) appendWalkNote(index, note); } function ensureWalkRunNote(){ const el = document.getElementById('walk_note'); if(!el) return; if(clean(el.value)) return; el.value = 'Guided no-dead walkthrough closeout • ' + navigator.userAgent.slice(0,120) + ' • ' + dayISO(); } const launchers = { 0: { label:'Run fresh proof', run: async()=> window.runFreshRecordProofComplete && window.runFreshRecordProofComplete() }, 1: { label:'Run legacy proof', run: async()=> window.runLegacyRecordProofComplete && window.runLegacyRecordProofComplete() }, 2: { label:'Run export/import proof', run: async()=> window.runExportImportProofComplete && window.runExportImportProofComplete() }, 3: { label:'Run no-dead proof', run: async()=> window.runNoDeadButtonProofComplete && window.runNoDeadButtonProofComplete() }, 4: { label:'Run closure campaign', run: async()=> window.runDirectiveClosureCampaign && window.runDirectiveClosureCampaign() } }; async function runWalkLauncher(index){ const spec = launchers[index]; if(!spec || typeof spec.run !== 'function') return null; ensureWalkRunNote(); const out = await spec.run(); const fp = clean(out && out.fingerprint) || clean(out && out.bundle && out.bundle.fingerprint) || clean(out && out.id) || 'run'; const status = (out && Object.prototype.hasOwnProperty.call(out, 'ok')) ? (out.ok ? 'PASS' : 'REVIEW') : 'DONE'; markWalkDone(index, '[' + fmt(nowISO()) + '] ' + spec.label + ' • ' + status + ' • ' + fp); return out; } async function runGuidedWalkthrough(){ const btn = document.getElementById('walk_guided_run'); if(btn){ btn.disabled = true; btn.textContent = 'Running guided closeout...'; } let completed = 0; try{ for(const index of [0,1,2,3,4]){ if(!launchers[index]) continue; await runWalkLauncher(index); completed++; } appendWalkNote(5, '[' + fmt(nowISO()) + '] Completion binder will be saved into the AE FLOW import outbox when you save the walkthrough receipt.'); toast('Guided walkthrough closeout ran ' + completed + ' live proof actions.', 'good'); }catch(err){ toast(clean(err && err.message) || 'Guided walkthrough stopped for review.', 'warn'); }finally{ if(btn){ btn.disabled = false; btn.textContent = 'Run guided closeout'; } } } function injectGuidedWalkthroughControls(){ const modal = getWalkModal(); if(!modal) return; const { body, footer } = modal; const items = getWalkItems(); if(!document.getElementById('walk_v31_controls')){ const block = document.createElement('div'); block.id = 'walk_v31_controls'; block.className = 'card'; block.style.marginBottom = '12px'; block.innerHTML = '

    Guided closeout

    This turns the last no-dead line into an in-app guided operator run instead of a memory-based checklist. It launches the live proof actions, records receipts into the walkthrough notes, and can bind the finished walkthrough into a completion binder for AE FLOW import.
    '; const list = body.querySelector('.list'); if(list) body.insertBefore(block, list); document.getElementById('walk_guided_run').onclick = runGuidedWalkthrough; document.getElementById('walk_binder_save').onclick = ()=>{ const row = saveCompletionBinder(); markWalkDone(5, '[' + fmt(nowISO()) + '] Completion binder saved • ' + clean(row.fingerprint)); const doneEl = getWalkDoneEl(5); if(doneEl) doneEl.checked = true; toast(row.ok ? 'Completion binder saved.' : 'Completion binder saved for review.', row.ok ? 'good' : 'warn'); }; document.getElementById('walk_binder_html').onclick = exportLatestBinderHtml; document.getElementById('walk_binder_json').onclick = exportLatestBinderJson; } items.forEach((item, index) => { if(item.querySelector('[data-v31-launch]')) return; const spec = launchers[index]; const host = document.createElement('div'); host.className = 'row'; host.style.flexWrap = 'wrap'; host.style.justifyContent = 'flex-end'; host.style.marginTop = '8px'; if(spec){ const btn = document.createElement('button'); btn.className = 'btn small'; btn.dataset.v31Launch = String(index); btn.textContent = spec.label; btn.onclick = async ()=>{ btn.disabled = true; const prev = btn.textContent; btn.textContent = 'Running...'; try{ await runWalkLauncher(index); toast(spec.label + ' logged into the walkthrough.', 'good'); }catch(err){ toast(clean(err && err.message) || 'Launcher failed.', 'warn'); } finally { btn.disabled = false; btn.textContent = prev; } }; host.appendChild(btn); } if(index === 5){ const btn = document.createElement('button'); btn.className = 'btn small'; btn.dataset.v31Launch = 'binder'; btn.textContent = 'Mark binder / AE FLOW handoff ready'; btn.onclick = ()=>{ const row = saveCompletionBinder(); markWalkDone(5, '[' + fmt(nowISO()) + '] Completion binder saved to Routex outbox • ' + clean(row.fingerprint)); const box = getWalkDoneEl(5); if(box) box.checked = true; toast(row.ok ? 'Binder handoff marked ready.' : 'Binder handoff saved for review.', row.ok ? 'good' : 'warn'); }; host.appendChild(btn); } if(host.childNodes.length) item.appendChild(host); }); if(!document.getElementById('walk_save_receipt_binder')){ const btn = document.createElement('button'); btn.className = 'btn'; btn.id = 'walk_save_receipt_binder'; btn.textContent = 'Save walkthrough + receipt + binder'; btn.onclick = ()=>{ ensureWalkRunNote(); markWalkDone(5, '[' + fmt(nowISO()) + '] Completion binder requested during walkthrough save.'); const saveBtn = document.getElementById('walk_save'); if(saveBtn) saveBtn.click(); setTimeout(()=>{ try{ const receipt = typeof window.saveRoutexNoDeadWalkthroughReceipt === 'function' ? window.saveRoutexNoDeadWalkthroughReceipt() : null; const binder = saveCompletionBinder(); toast((receipt && receipt.ok && binder.ok) ? 'Walkthrough, receipt, and binder saved.' : 'Walkthrough, receipt, and binder saved for review.', (receipt && receipt.ok && binder.ok) ? 'good' : 'warn'); }catch(err){ toast(clean(err && err.message) || 'Walkthrough save finished, but binder packaging needs review.', 'warn'); } }, 80); }; footer.insertBefore(btn, footer.firstChild); } } function injectBinderControls(){ const title = document.getElementById('modalTitle'); const body = document.getElementById('modalBody'); if(!title || !body || !/No-dead-button proof/i.test(title.textContent || '')) return; if(document.getElementById('ndbCompletionBinderBlock')) return; const latest = readBinders()[0] || null; const block = document.createElement('div'); block.id = 'ndbCompletionBinderBlock'; block.className = 'card'; block.style.marginTop = '12px'; block.innerHTML = '

    Completion binder

    This is the final sync-ready binder for the no-dead-button line. It packages the saved walkthrough receipt, latest no-dead proof run, shipped compare, completion snapshot, and attestation into one Routex artifact that AE FLOW can import directly.
    Binders: '+esc(String(readBinders().length))+''+(latest ? ' • Latest '+esc(latest.fingerprint || '—')+' • '+(latest.ok ? 'PASS' : 'REVIEW')+' • reviewer '+esc(latest.walkthroughReviewer || '—')+'' : ' • No completion binder saved yet.')+'
    '; body.appendChild(block); document.getElementById('ndbBinderSaveBtn').onclick = ()=>{ const row = saveCompletionBinder(); toast(row.ok ? 'Completion binder saved.' : 'Completion binder saved for review.', row.ok ? 'good' : 'warn'); }; document.getElementById('ndbBinderHtmlBtn').onclick = exportLatestBinderHtml; document.getElementById('ndbBinderJsonBtn').onclick = exportLatestBinderJson; } const observer = new MutationObserver(()=>{ injectGuidedWalkthroughControls(); injectBinderControls(); }); observer.observe(document.body, { childList:true, subtree:true }); const prevRun = window.runNoDeadButtonProofComplete; if(typeof prevRun === 'function'){ window.runNoDeadButtonProofComplete = async function(){ const out = await prevRun.apply(this, arguments); const receipt = latestReceipt(); if(receipt && receipt.ok){ try{ const binder = saveCompletionBinder(); toast(binder.ok ? 'Completion binder packaged.' : 'Completion binder saved for review.', binder.ok ? 'good' : 'warn'); }catch(_){ } } return out; }; } window.readRoutexNoDeadCompletionBinders = readBinders; window.readRoutexNoDeadCompletionBinderOutbox = readBinderOutbox; window.saveRoutexNoDeadCompletionBinder = saveCompletionBinder; })(); '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function saveBrief(){ const row = buildRow(); pushBrief(row); pushOutbox(row); return row; } function exportLatestHtml(){ const row = readBriefs()[0] || saveBrief(); downloadText(buildHtml(row), 'routex_operator_command_brief_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBriefs()[0] || saveBrief(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_command_brief_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexOpsBriefSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexOpsBriefSaveBtn'; saveBtn.textContent='Save ops brief'; saveBtn.onclick = ()=>{ const row = saveBrief(); toast(row.ok ? 'Operator command brief saved.' : 'Operator command brief saved for review.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexOpsBriefHtmlBtn'; htmlBtn.textContent='Export ops brief HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexOpsBriefJsonBtn'; jsonBtn.textContent='Export ops brief JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const host = document.querySelector('#app') || document.body; const latest = readBriefs()[0] || null; const existing = document.getElementById('routexOpsBriefCard'); if(existing) existing.remove(); const card = document.createElement('div'); card.className = 'card'; card.id = 'routexOpsBriefCard'; const s = latest ? latest.snapshot || {} : snapshot(); card.innerHTML = '

    Operator command brief

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Readiness '+esc(String(s.readiness || 0))+'/'+esc(String(s.readinessMax || 0))+''+(latest.ok ? 'PASS' : 'REVIEW')+'
    Route packs '+esc(String(s.routePacks || 0))+' • Trip packs '+esc(String(s.tripPacks || 0))+' • Binders '+esc(String(s.binders || 0))+' • Receipts '+esc(String(s.receipts || 0))+'
    '+esc(latest.note || '')+'
    ') : 'No operator command brief saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorCommandBriefs = readBriefs; window.readRoutexOperatorCommandBriefOutbox = readOutbox; window.saveRoutexOperatorCommandBrief = saveBrief; })(); '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })(); '; } function savePacket(){ const row = buildPacket(); pushPacket(row); pushOutbox(row); return row; } function exportLatestHtml(){ const row = readPackets()[0] || savePacket(); downloadText(buildHtml(row), 'routex_operator_handoff_packet_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readPackets()[0] || savePacket(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_handoff_packet_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexHandoffPacketSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexHandoffPacketSaveBtn'; saveBtn.textContent='Save handoff packet'; saveBtn.onclick = ()=>{ const row = savePacket(); toast(row.ok ? 'Operator handoff packet saved.' : 'Operator handoff packet saved for review.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexHandoffPacketHtmlBtn'; htmlBtn.textContent='Export handoff HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexHandoffPacketJsonBtn'; jsonBtn.textContent='Export handoff JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const host = document.querySelector('#app') || document.body; const latest = readPackets()[0] || null; const existing = document.getElementById('routexHandoffPacketCard'); if(existing) existing.remove(); const card = document.createElement('div'); card.className = 'card'; card.id = 'routexHandoffPacketCard'; const s = latest ? latest.snapshot || {} : snapshot(); card.innerHTML = '

    Operator handoff packet

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Readiness '+esc(String(latest.readiness || 0))+'/'+esc(String(latest.readinessMax || 0))+''+(latest.ok ? 'PASS' : 'REVIEW')+'
    Ops brief '+esc((s.latestBrief && s.latestBrief.fingerprint) || '—')+' • Binder '+esc((s.latestBinder && s.latestBinder.fingerprint) || '—')+' • Receipt '+esc((s.latestReceipt && s.latestReceipt.fingerprint) || '—')+'
    '+esc(latest.note || '')+'
    ') : 'No operator handoff packet saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorHandoffPackets = readPackets; window.readRoutexOperatorHandoffOutbox = readOutbox; window.saveRoutexOperatorHandoffPacket = savePacket; })(); '; } function exportLatestHtml(){ const row = readBoards()[0] || saveBoard(); downloadText(buildHtml(row), 'routex_operator_launch_board_' + dayISO() + '.html', 'text/html'); } function exportLatestJson(){ const row = readBoards()[0] || saveBoard(); downloadText(JSON.stringify(row, null, 2), 'routex_operator_launch_board_' + dayISO() + '.json', 'application/json'); } function inject(){ const bar = document.querySelector('#routexWorkbenchToolbar') || document.querySelector('.toolbar') || document.querySelector('.row'); if(bar && !document.getElementById('routexLaunchBoardSaveBtn')){ const saveBtn = document.createElement('button'); saveBtn.className='btn small'; saveBtn.id='routexLaunchBoardSaveBtn'; saveBtn.textContent='Save launch board'; saveBtn.onclick = ()=>{ const row = saveBoard(); toast(row.ok ? 'Operator launch board saved.' : 'Operator launch board saved with blockers.', row.ok ? 'good' : 'warn'); }; const htmlBtn = document.createElement('button'); htmlBtn.className='btn small'; htmlBtn.id='routexLaunchBoardHtmlBtn'; htmlBtn.textContent='Export launch HTML'; htmlBtn.onclick = exportLatestHtml; const jsonBtn = document.createElement('button'); jsonBtn.className='btn small'; jsonBtn.id='routexLaunchBoardJsonBtn'; jsonBtn.textContent='Export launch JSON'; jsonBtn.onclick = exportLatestJson; bar.appendChild(saveBtn); bar.appendChild(htmlBtn); bar.appendChild(jsonBtn); } const latest = readBoards()[0] || null; const existing = document.getElementById('routexLaunchBoardCard'); if(existing) existing.remove(); const host = document.querySelector('#app') || document.body; const card = document.createElement('div'); card.className = 'card'; card.id = 'routexLaunchBoardCard'; card.innerHTML = '

    Operator launch board

    ' + (latest ? ('
    '+esc(latest.fingerprint || '—')+'Score '+esc(String(latest.score || 0))+'%Checks '+esc(String(latest.passing || 0))+'/'+esc(String(latest.total || 0))+''+(latest.ok ? 'GREEN' : 'ACTION REQUIRED')+'
    '+esc(latest.note || '')+'
    Top action: '+esc((latest.actions && latest.actions[0] && latest.actions[0].label) || '—')+'
    ') : 'No operator launch board saved yet.'); host.appendChild(card); } const observer = new MutationObserver(()=> inject()); observer.observe(document.body, { childList:true, subtree:true }); const prevRender = window.renderAll; if(typeof prevRender === 'function') window.renderAll = function(){ const out = prevRender.apply(this, arguments); setTimeout(inject, 0); return out; }; window.readRoutexOperatorLaunchBoards = readBoards; window.readRoutexOperatorLaunchBoardOutbox = readOutbox; window.saveRoutexOperatorLaunchBoard = saveBoard; })();