aicodeman 1.16.0 → 1.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-checker-base.d.ts +12 -0
- package/dist/ai-checker-base.d.ts.map +1 -1
- package/dist/ai-checker-base.js +4 -2
- package/dist/ai-checker-base.js.map +1 -1
- package/dist/git-clone.d.ts +220 -0
- package/dist/git-clone.d.ts.map +1 -0
- package/dist/git-clone.js +717 -0
- package/dist/git-clone.js.map +1 -0
- package/dist/hooks-config.d.ts +15 -0
- package/dist/hooks-config.d.ts.map +1 -1
- package/dist/hooks-config.js +67 -21
- package/dist/hooks-config.js.map +1 -1
- package/dist/intent-store.d.ts +73 -0
- package/dist/intent-store.d.ts.map +1 -0
- package/dist/intent-store.js +208 -0
- package/dist/intent-store.js.map +1 -0
- package/dist/readmymind-collectors.d.ts +42 -0
- package/dist/readmymind-collectors.d.ts.map +1 -0
- package/dist/readmymind-collectors.js +170 -0
- package/dist/readmymind-collectors.js.map +1 -0
- package/dist/readmymind-context.d.ts +106 -0
- package/dist/readmymind-context.d.ts.map +1 -0
- package/dist/readmymind-context.js +241 -0
- package/dist/readmymind-context.js.map +1 -0
- package/dist/readmymind-predictor.d.ts +60 -0
- package/dist/readmymind-predictor.d.ts.map +1 -0
- package/dist/readmymind-predictor.js +208 -0
- package/dist/readmymind-predictor.js.map +1 -0
- package/dist/session.d.ts +5 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +8 -2
- package/dist/session.js.map +1 -1
- package/dist/transcript-watcher.d.ts +7 -0
- package/dist/transcript-watcher.d.ts.map +1 -1
- package/dist/transcript-watcher.js +23 -0
- package/dist/transcript-watcher.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/intent.d.ts +30 -0
- package/dist/types/intent.d.ts.map +1 -0
- package/dist/types/intent.js +9 -0
- package/dist/types/intent.js.map +1 -0
- package/dist/web/ports/config-port.d.ts +8 -0
- package/dist/web/ports/config-port.d.ts.map +1 -1
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/{app.c8e6e44e.js → app.4d006245.js} +1 -1
- package/dist/web/public/{app.c8e6e44e.js.br → app.4d006245.js.br} +0 -0
- package/dist/web/public/{app.c8e6e44e.js.gz → app.4d006245.js.gz} +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/constants.7d0f4047.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/home-sessions.js +335 -0
- package/dist/web/public/home-sessions.js.br +0 -0
- package/dist/web/public/home-sessions.js.gz +0 -0
- package/dist/web/public/i18n.ef50083f.js +1 -0
- package/dist/web/public/i18n.ef50083f.js.br +0 -0
- package/dist/web/public/i18n.ef50083f.js.gz +0 -0
- package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
- package/dist/web/public/index.html +101 -9
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
- package/dist/web/public/{keyboard-accessory.d6c3ad11.js → keyboard-accessory.1f35b482.js} +24 -5
- package/dist/web/public/keyboard-accessory.1f35b482.js.br +0 -0
- package/dist/web/public/keyboard-accessory.1f35b482.js.gz +0 -0
- package/dist/web/public/mobile-handlers.32fdd57f.js.gz +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/mobile.5d8c896c.css +1 -0
- package/dist/web/public/mobile.5d8c896c.css.br +0 -0
- package/dist/web/public/mobile.5d8c896c.css.gz +0 -0
- package/dist/web/public/notification-manager.5d229063.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/panels-ui.3df9cd95.js.gz +0 -0
- package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
- package/dist/web/public/readmymind-ui.js +135 -0
- package/dist/web/public/readmymind-ui.js.br +0 -0
- package/dist/web/public/readmymind-ui.js.gz +0 -0
- package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/{session-ui.1356a4f4.js → session-ui.f4fed187.js} +2 -2
- package/dist/web/public/session-ui.f4fed187.js.br +0 -0
- package/dist/web/public/session-ui.f4fed187.js.gz +0 -0
- package/dist/web/public/settings-ui.1c15649c.js +67 -0
- package/dist/web/public/settings-ui.1c15649c.js.br +0 -0
- package/dist/web/public/settings-ui.1c15649c.js.gz +0 -0
- package/dist/web/public/styles.0024c22e.css +1 -0
- package/dist/web/public/styles.0024c22e.css.br +0 -0
- package/dist/web/public/styles.0024c22e.css.gz +0 -0
- package/dist/web/public/subagent-windows.4cc8b005.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/{terminal-ui.430395be.js → terminal-ui.e7c5d233.js} +1 -1
- package/dist/web/public/terminal-ui.e7c5d233.js.br +0 -0
- package/dist/web/public/terminal-ui.e7c5d233.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-predictive-echo.6cec0fba.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.e5461859.js.gz +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/routes/case-routes.d.ts +3 -1
- package/dist/web/routes/case-routes.d.ts.map +1 -1
- package/dist/web/routes/case-routes.js +195 -4
- package/dist/web/routes/case-routes.js.map +1 -1
- package/dist/web/routes/index.d.ts +1 -0
- package/dist/web/routes/index.d.ts.map +1 -1
- package/dist/web/routes/index.js +1 -0
- package/dist/web/routes/index.js.map +1 -1
- package/dist/web/routes/readmymind-routes.d.ts +37 -0
- package/dist/web/routes/readmymind-routes.d.ts.map +1 -0
- package/dist/web/routes/readmymind-routes.js +127 -0
- package/dist/web/routes/readmymind-routes.js.map +1 -0
- package/dist/web/schemas.d.ts +38 -0
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +67 -1
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/server.d.ts +14 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +46 -3
- package/dist/web/server.js.map +1 -1
- package/package.json +1 -1
- package/skills/codeman/SKILL.md +34 -0
- package/skills/codeman/reference/endpoints.md +4 -0
- package/dist/web/public/i18n.75cf29d4.js +0 -1
- package/dist/web/public/i18n.75cf29d4.js.br +0 -0
- package/dist/web/public/i18n.75cf29d4.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.d6c3ad11.js.br +0 -0
- package/dist/web/public/keyboard-accessory.d6c3ad11.js.gz +0 -0
- package/dist/web/public/mobile.8a2651ba.css +0 -1
- package/dist/web/public/mobile.8a2651ba.css.br +0 -0
- package/dist/web/public/mobile.8a2651ba.css.gz +0 -0
- package/dist/web/public/session-ui.1356a4f4.js.br +0 -0
- package/dist/web/public/session-ui.1356a4f4.js.gz +0 -0
- package/dist/web/public/settings-ui.20f9c723.js +0 -67
- package/dist/web/public/settings-ui.20f9c723.js.br +0 -0
- package/dist/web/public/settings-ui.20f9c723.js.gz +0 -0
- package/dist/web/public/styles.e33fc754.css +0 -1
- package/dist/web/public/styles.e33fc754.css.br +0 -0
- package/dist/web/public/styles.e33fc754.css.gz +0 -0
- package/dist/web/public/terminal-ui.430395be.js.br +0 -0
- package/dist/web/public/terminal-ui.430395be.js.gz +0 -0
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
`).trim()}_preprocessAsciiArt(e){const t=/[─-╿▀-▟]/,s=/```[\s\S]*?```/g,n=[];return e.replace(s,a=>(n.push(a),`__CODEMAN_FENCE_${n.length-1}__`)).split(/(\n{2,})/).map(a=>/^\n{2,}$/.test(a)||!a.trim()||a.includes("__CODEMAN_FENCE_")?a:t.test(a)?"\n```\n"+a+"\n```\n":a).join("").replace(/__CODEMAN_FENCE_(\d+)__/g,(a,l)=>n[Number(l)])}_renderMarkdown(e){const t=e||"";if(typeof marked<"u"&&marked.parse)try{const n=this._preprocessAsciiArt(t);let i=this._sanitizeHtml(marked.parse(n,{breaks:!0,gfm:!0}));i=i.replace(/<table>/g,'<div class="rv-table-wrap"><table>').replace(/<\/table>/g,"</table></div>");const o=/[─-╿▀-▟]/,a=document.createElement("template");return a.innerHTML=i,a.content.querySelectorAll("pre > code").forEach(l=>{const r=l.parentElement,d=o.test(l.textContent||""),c=document.createElement("div");c.className=d?"rv-code-wrap rv-diagram-wrap":"rv-code-wrap";const u=document.createElement("div");u.className="rv-code-actions";const p=document.createElement("button");if(p.className="rv-copy-btn",p.type="button",p.setAttribute("aria-label","Copy code"),p.setAttribute("title","Copy code"),u.appendChild(p),d){r.classList.add("rv-diagram");const m=document.createElement("button");m.className="rv-wrap-toggle",m.type="button",m.setAttribute("aria-label","Toggle line wrapping"),m.setAttribute("title","Toggle line wrapping"),u.appendChild(m)}r.parentNode.insertBefore(c,r),c.appendChild(u),c.appendChild(r)}),a.innerHTML}catch{}return`<pre style="white-space:pre-wrap;word-break:break-word">${t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}</pre>`}_bindResponseViewerInteractions(e){!e||e.dataset.rvBound==="1"||(e.dataset.rvBound="1",e.addEventListener("click",async t=>{const s=t.target.closest(".rv-copy-btn");if(s){t.preventDefault(),t.stopPropagation();const l=s.closest(".rv-code-wrap")?.querySelector("pre code"),r=l?await this._copyText(l.textContent||""):!1;s.classList.remove("rv-copied","rv-copy-failed"),s.classList.add(r?"rv-copied":"rv-copy-failed"),clearTimeout(s._resetTimer),s._resetTimer=setTimeout(()=>{s.classList.remove("rv-copied","rv-copy-failed")},1500);return}const n=t.target.closest(".rv-wrap-toggle");if(!n)return;t.preventDefault(),t.stopPropagation();const i=n.closest(".rv-diagram-wrap"),o=i?.querySelector("pre.rv-diagram");if(!o||!i)return;const a=o.classList.toggle("rv-nowrap");i.classList.toggle("rv-wrap-nowrap",a)}))}async _copyText(e){if(!e)return!1;try{if(navigator.clipboard?.writeText)return await navigator.clipboard.writeText(e),!0}catch{}try{const t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.cssText="position:fixed;top:0;left:0;opacity:0;pointer-events:none",document.body.appendChild(t),t.select();const s=document.execCommand("copy");return document.body.removeChild(t),s}catch{return!1}}_buildResponseViewerMessage(e,t,s){const n=document.createElement("div"),i=t==="user";n.className="rv-message "+(i?"rv-msg-user":"rv-msg-assistant");const o=document.createElement("div");o.className="rv-role "+(i?"rv-role-user":"rv-role-assistant"),o.textContent=i?"You":s,n.appendChild(o);const a=document.createElement("div");return a.className="rv-text",a.innerHTML=this._renderMarkdown(e),n.appendChild(a),n}_getResponseViewerAgentLabel(){const e=this.sessions.get(this.activeSessionId)?.mode;return e==="codex"?"Codex":e==="gemini"?"Gemini":e==="antigravity"?"Antigravity":e==="opencode"?"OpenCode":"Claude"}async toggleResponseViewer(){const e=document.getElementById("responseViewer"),t=document.getElementById("responseViewerBackdrop");if(!e)return;if(e.classList.contains("visible")){e.classList.remove("visible"),t.classList.remove("visible");return}if(this.activeSessionId)try{let o=((await(await fetch(`/api/sessions/${this.activeSessionId}/last-response`)).json())?.data??{}).text||"";const a=this.sessions.get(this.activeSessionId)?.mode||"claude";if(!o&&(a==="claude"||a==="shell")){const u=(await(await fetch(`/api/sessions/${this.activeSessionId}/terminal`)).json())?.data??{};u.terminalBuffer&&(o=this._cleanTerminalBuffer(u.terminalBuffer))}const l=document.getElementById("responseViewerBody");o?(l.innerHTML="",l.appendChild(this._buildResponseViewerMessage(o,"assistant",this._getResponseViewerAgentLabel())),this._bindResponseViewerInteractions(l)):l.textContent=window.codemanT?.("No response yet \u2014 send a message in this session first.")||"No response yet \u2014 send a message in this session first.";const r=document.getElementById("responseViewerTitle"),d=document.getElementById("responseViewerMore");r&&(r.textContent="Last Response"),d&&(d.style.display="",d.textContent="More"),e.classList.add("visible"),t.classList.add("visible"),l.scrollTop=0}catch(n){console.error("Failed to load response:",n)}}async loadFullContext(){if(!this.activeSessionId)return;const e=document.getElementById("responseViewerMore");e&&(e.textContent="...");try{const n=((await(await fetch(`/api/sessions/${this.activeSessionId}/last-response?context=full`)).json())?.data??{}).messages||[],i=document.getElementById("responseViewerBody"),o=document.getElementById("responseViewerTitle");if(!i)return;if(n.length===0){i.textContent="No conversation history available";return}const a=this._getResponseViewerAgentLabel();i.innerHTML="";for(const l of n)i.appendChild(this._buildResponseViewerMessage(l.text,l.role,a));this._bindResponseViewerInteractions(i),o&&(o.textContent=`Conversation (${n.length} messages)`),e&&(e.style.display="none"),i.scrollTop=i.scrollHeight}catch(t){console.error("Failed to load context:",t)}finally{e&&(e.textContent="More")}}async _onSessionNeedsRefresh(){if(!(!this.activeSessionId||!this.terminal)&&!this._isLoadingBuffer)try{const t=(await(await fetch(`/api/sessions/${this.activeSessionId}/terminal?tail=${TERMINAL_TAIL_SIZE}`)).json())?.data??{};t.terminalBuffer&&(this.terminal.clear(),this.terminal.reset(),await this.chunkedTerminalWrite(t.terminalBuffer),this.terminal.scrollToBottom(),this._localEchoOverlay?.rerender(),this.activeSessionId&&this.sendResize(this.activeSessionId))}catch(e){console.error("needsRefresh reload failed:",e)}}async _onSessionClearTerminal(e){if(e.id===this.activeSessionId){if(this._isLoadingBuffer)return;try{const s=(await(await fetch(`/api/sessions/${e.id}/terminal`)).json())?.data??{};if(this.terminal.clear(),this.terminal.reset(),s.terminalBuffer){const n=s.terminalBuffer.replace(DEC_SYNC_STRIP_RE,"");await this.chunkedTerminalWrite(n)}this.sendResize(e.id),this._localEchoOverlay?.rerender()}catch(t){console.error("clearTerminal refresh failed:",t)}}}_onSessionCompletion(e){this.totalCost+=e.cost||0,this.updateCost(),e.id===this.activeSessionId&&(this.terminal.writeln(""),this.terminal.writeln(`\x1B[1;32m Done (Cost: $${(e.cost||0).toFixed(4)})\x1B[0m`))}_onSessionError(e){e.id===this.activeSessionId&&this.terminal.writeln(`\x1B[1;31m Error: ${e.error}\x1B[0m`),this._notifySession(e.id,"critical","session-error","Session Error",e.error||"Unknown error")}_onSessionExit(e){this._wsSessionId===e.id&&this._disconnectWs();const t=this.sessions.get(e.id);t&&(t.status="stopped",this.renderSessionTabs(),e.id===this.activeSessionId&&this._updateLocalEchoState()),e.code&&e.code!==0&&this._notifySession(e.id,"critical","session-crash","Session Crashed",`Exited with code ${e.code}`)}_onSessionIdle(e){const t=this.sessions.get(e.id);if(t&&(t.status="idle",this.renderSessionTabs(),this.sendPendingCtrlL(e.id),e.id===this.activeSessionId&&this._updateLocalEchoState()),!this.respawnStatus[e.id]?.enabled){const s=this.notificationManager?.preferences?.stuckThresholdMs||6e5;clearTimeout(this.idleTimers.get(e.id)),this.idleTimers.set(e.id,setTimeout(()=>{this._notifySession(e.id,"warning","session-stuck","Session Idle",`Idle for ${Math.round(s/6e4)}+ minutes`),this.idleTimers.delete(e.id)},s))}}_onSessionWorking(e){const t=this.sessions.get(e.id);t&&(t.status="busy",this.pendingHooks.has(e.id)||this.tabAlerts.delete(e.id),this.renderSessionTabs(),this.sendPendingCtrlL(e.id),e.id===this.activeSessionId&&this._updateLocalEchoState());const s=this.idleTimers.get(e.id);s&&(clearTimeout(s),this.idleTimers.delete(e.id))}_onSessionAutoClear(e){e.sessionId===this.activeSessionId&&(this.showToast(`Auto-cleared at ${e.tokens.toLocaleString()} tokens`,"info"),this.updateRespawnTokens(0)),this._notifySession(e.sessionId,"info","auto-clear","Auto-Cleared",`Context reset at ${(e.tokens||0).toLocaleString()} tokens`)}_onSessionLimitPauseScheduled(e){const t=this.sessions.get(e.sessionId);t&&(t.autoResumeAt=e.resumeAt);const s=new Date(e.resumeAt).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"});e.sessionId===this.activeSessionId&&this.showToast(`Usage limit reached \u2014 auto-resume at ${s}`,"warning"),this._notifySession(e.sessionId,"warning","limit-pause","Usage Limit Reached",`Auto-resume scheduled for ${s}`),this.updateAutoResumeStatus(e.sessionId)}_onSessionLimitResume(e){const t=this.sessions.get(e.sessionId);t&&(t.autoResumeAt=void 0),e.sessionId===this.activeSessionId&&this.showToast("Usage limit reset \u2014 work resumed automatically","success"),this._notifySession(e.sessionId,"info","limit-resume","Auto-Resumed","Usage limit reset \u2014 continuing work"),this.updateAutoResumeStatus(e.sessionId)}_onSessionLimitResumeCancelled(e){const t=this.sessions.get(e.sessionId);t&&(t.autoResumeAt=void 0),this.updateAutoResumeStatus(e.sessionId)}_onSessionRespawnBreakerTripped(e){const s=this.sessions.get(e.sessionId)?.name||"Session";this.showToast?.(`${s} stopped: repeated crashes detected. Restart to retry.`,"error")}_onSessionCliInfo(e){const t=this.sessions.get(e.sessionId);t&&(e.version&&(t.cliVersion=e.version),e.model&&(t.cliModel=e.model),e.accountType&&(t.cliAccountType=e.accountType),e.latestVersion&&(t.cliLatestVersion=e.latestVersion)),e.sessionId===this.activeSessionId&&this.updateCliInfoDisplay()}_onSessionStatusTelemetry(e){this.updatePlanUsageChip(e);try{localStorage.setItem("codeman:planUsage",JSON.stringify({t:Date.now(),data:e}))}catch{}}restorePlanUsageChip(){try{const e=localStorage.getItem("codeman:planUsage");if(!e)return;const t=JSON.parse(e);t?.data&&Date.now()-(t.t||0)<12*3600*1e3&&this.updatePlanUsageChip(t.data)}catch{}}updatePlanUsageChip(e){const t=document.getElementById("planUsageChip");if(!t||!e)return;const s=r=>r&&typeof r.usedPercentage=="number"?Math.round(r.usedPercentage):null,n=s(e.fiveHour),i=s(e.sevenDay);if(n===null&&i===null)return;const o=r=>r>=85?"pu-red":r>=60?"pu-yellow":"pu-green",a=(r,d)=>{if(d===null)return"";const c=Math.round(Number(d));return Number.isFinite(c)?`<span class="pu-win"><span class="pu-label">${r}</span><span class="pu-val ${o(c)}">${c}%</span></span>`:""};t.innerHTML=[a("5h",n),a("7d",i)].filter(Boolean).join('<span class="pu-sep">\xB7</span>');const l=r=>r&&r.resetAt?new Date(r.resetAt).toLocaleString():"\u2014";t.title=`Claude plan usage
|
|
15
15
|
5-hour limit: ${n??"\u2014"}% used (resets ${l(e.fiveHour)})
|
|
16
|
-
Weekly limit: ${i??"\u2014"}% used (resets ${l(e.sevenDay)})`}_onScheduledCreated(e){this.currentRun=e,this.showTimer()}_onScheduledUpdated(e){this.currentRun=e,this.updateTimer()}_onScheduledCompleted(e){this.currentRun=e,this.hideTimer(),this.showToast("Scheduled run completed!","success")}_onScheduledStopped(){this.currentRun=null,this.hideTimer()}setConnectionStatus(e){this._connectionStatus=e,e==="connected"?(this._connDownSince=null,this._nextSseRetryAt=null,this._offlineOverlayDismissed=!1):this._connDownSince===null&&(this._connDownSince=Date.now()),this._updateConnectionIndicator(),this._updateConnectionLossUi(),e==="connected"&&this._redeliverSweep()}_connectWs(e){this._disconnectWs(),this._wsState="connecting",this._updateConnectionIndicator();const t=location.protocol==="https:"?"wss:":"ws:",s=this._clientId?`${this._clientId}:${this._wsTabNonce}`:"",n=s?`?cid=${encodeURIComponent(s)}`:"",i=`${t}//${location.host}/ws/sessions/${e}/terminal${n}`,o=new WebSocket(i);this._ws=o,this._wsSessionId=e,o.onopen=()=>{this._ws===o&&(this._wsReady=!0,this._wsState="connected",this._wsReconnectAttempts=0,this._updateConnectionIndicator(),this.sendResize(e)?.catch?.(()=>{}),this._startMobileResizeRetry(e),this._onWsReady(e))},o.onmessage=a=>{if(this._ws===o){this._wsLastRecvAt=Date.now();try{const l=JSON.parse(a.data);l.t==="o"?this._onSessionTerminal({id:e,data:l.d}):l.t==="c"?this._onSessionClearTerminal({id:e}):l.t==="r"?this._onSessionNeedsRefresh({id:e}):l.t==="ia"&&this._onWsInputAck(l.seq)}catch{}}},o.onclose=a=>{if(this._ws!==o)return;this._ws=null,this._wsSessionId=null,this._wsReady=!1,this._stopMobileResizeRetry();const l=window.CodemanWsReconnect.plan(a.code,this._wsReconnectAttempts||0);_crashDiag.log(`WS CLOSE code=${a.code} reason=${a.reason||""} action=${l.action} attempts=${this._wsReconnectAttempts||0}`);const r=this.activeSessionId===e;if(l.action==="give-up")this._wsState=r?"fallback":"disconnected",this._updateConnectionIndicator();else if(l.action==="reconnect")if(r){this._wsState="reconnecting",this._updateConnectionIndicator();const d=l.delayMs+Math.floor(Math.random()*250);this._wsReconnectAttempts=(this._wsReconnectAttempts||0)+1,this._wsReconnectTimer=setTimeout(()=>{this._wsReconnectTimer=null,this.activeSessionId===e&&this._connectWs(e)},d)}else this._wsState="disconnected",this._updateConnectionIndicator();else this._wsState=r?"fallback":"disconnected",this._updateConnectionIndicator(),r&&(this._wsReconnectAttempts=(this._wsReconnectAttempts||0)+1,this._wsReconnectTimer=setTimeout(()=>{this._wsReconnectTimer=null,this.activeSessionId===e&&this._connectWs(e)},l.delayMs))},o.onerror=()=>{}}_disconnectWs(){this._clearTimer("_wsReconnectTimer"),this._wsState="disconnected",this._stopMobileResizeRetry(),this._ws&&(this._ws.onclose=null,this._ws.close(),this._ws=null,this._wsSessionId=null,this._wsReady=!1)}_startMobileResizeRetry(e){this._stopMobileResizeRetry(),(typeof MobileDetection<"u"&&MobileDetection.getDeviceType?MobileDetection.getDeviceType():"desktop")!=="desktop"&&(this._mobileResizeRetryTimer=setInterval(()=>{document.visibilityState==="visible"&&(!this._wsReady||this._wsSessionId!==e||typeof KeyboardHandler<"u"&&KeyboardHandler.keyboardVisible||this.sendResize(e)?.catch?.(()=>{}))},MOBILE_RESIZE_RETRY_MS))}_stopMobileResizeRetry(){this._mobileResizeRetryTimer&&(clearInterval(this._mobileResizeRetryTimer),this._mobileResizeRetryTimer=null)}_sendInputAsync(e,t,s){!e||!t||this._reliableSend(e,t,s?.useMux===!0)}_sendInputEphemeral(e,t){if(!(!e||!t)){if(this._ws&&this._ws.readyState===WebSocket.OPEN&&this._wsSessionId===e)try{this._ws.send(JSON.stringify({t:"i",d:t}));return}catch{}try{fetch(`/api/sessions/${e}/input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input:t}),keepalive:!0}).catch(()=>{})}catch{}}}_reliableSend(e,t,s){const i={seq:this._nextSeq(e),data:t,useMux:!!s,ts:Date.now(),tries:0,sentAt:0};let o=this._pendingDeliveries.get(e);o||(o=[],this._pendingDeliveries.set(e,o)),o.push(i),this._persistReliableState(),this._updateConnectionIndicator(),this._drainSession(e)}_nextSeq(e){const t=(this._seqCounters.get(e)||0)+1;return this._seqCounters.set(e,t),t}_drainSession(e){const t=this._pendingDeliveries.get(e);if(!(!t||t.length===0)){if(this._ws&&this._ws.readyState===WebSocket.OPEN&&this._wsSessionId===e){for(const s of t)if(s.sentAt===0)try{this._ws.send(JSON.stringify({t:"i",d:s.data,seq:s.seq,cid:this._clientId})),s.sentAt=Date.now(),s.tries++}catch{break}return}this._postDraining.has(e)||(this._postDraining.add(e),(async()=>{try{for(;;){const s=this._pendingDeliveries.get(e);if(!s||s.length===0||this._ws&&this._ws.readyState===WebSocket.OPEN&&this._wsSessionId===e)break;const n=s[0];n.tries++,n.sentAt=Date.now();let i=null;try{const o={input:n.data,seq:n.seq,clientId:this._clientId};n.useMux&&(o.useMux=!0),i=await fetch(`/api/sessions/${e}/input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o),keepalive:n.data.length<65536})}catch{i=null}if(i&&i.ok)this._ackDelivery(e,n.seq);else if(i&&(i.status===404||i.status===410))this._ackDelivery(e,n.seq);else break}}finally{this._postDraining.delete(e)}})())}}_ackDelivery(e,t){const s=this._pendingDeliveries.get(e);if(s){const n=s.findIndex(i=>i.seq===t);n!==-1&&(s.splice(n,1),s.length===0&&this._pendingDeliveries.delete(e),this._pendingDeliveries.size===0?this._persistReliableNow():this._persistReliableState(),this._updateConnectionIndicator())}this.clearPendingHooks?.(e)}_onWsInputAck(e){this._wsSessionId&&Number.isInteger(e)&&this._ackDelivery(this._wsSessionId,e)}_onWsReady(e){const t=this._pendingDeliveries.get(e);if(t)for(const s of t)s.sentAt=0;this._drainSession(e)}_redeliverSweep(){if(this._pendingDeliveries.size!==0)for(const e of[...this._pendingDeliveries.keys()]){const t=this._pendingDeliveries.get(e);if(!t||t.length===0)continue;if(this._ws&&this._ws.readyState===WebSocket.OPEN&&this._wsSessionId===e){const n=t[0],i=n&&n.sentAt&&Date.now()-n.sentAt>this._reliableAckTimeoutMs,o=Date.now()-this._wsLastRecvAt>this._reliableAckTimeoutMs;if(i&&o){try{this._ws.close()}catch{}continue}if(i)for(const a of t)a.sentAt&&Date.now()-a.sentAt>this._reliableAckTimeoutMs&&(a.sentAt=0)}this._drainSession(e)}}_pendingBytes(){let e=0,t=0;for(const s of this._pendingDeliveries.values())for(const n of s)e+=n.data.length,t++;return{bytes:e,count:t}}_loadReliableState(){try{this._clientId=localStorage.getItem("codeman:clientId")||""}catch{this._clientId=""}if(!this._clientId){this._clientId="c-"+Math.random().toString(36).slice(2)+"-"+Date.now().toString(36);try{localStorage.setItem("codeman:clientId",this._clientId)}catch{}}try{const e=localStorage.getItem("codeman:pendingInput");if(!e)return;const t=JSON.parse(e);if(t&&t.seqs)for(const[s,n]of Object.entries(t.seqs))Number.isFinite(n)&&this._seqCounters.set(s,n);if(t&&t.pending)for(const[s,n]of Object.entries(t.pending))Array.isArray(n)&&n.length&&this._pendingDeliveries.set(s,n.filter(i=>i&&typeof i.data=="string"&&Number.isInteger(i.seq)).map(i=>({seq:i.seq,data:i.data,useMux:!!i.useMux,ts:i.ts||Date.now(),tries:0,sentAt:0})))}catch{}}_persistReliableState(){this._persistReliableTimer||(this._persistReliableTimer=setTimeout(()=>{this._persistReliableTimer=null,this._persistReliableNow()},250))}_persistReliableNow(){this._persistReliableTimer&&(clearTimeout(this._persistReliableTimer),this._persistReliableTimer=null);try{const e={};for(const[i,o]of this._seqCounters)e[i]=o;const t={};let s=0;for(const[i,o]of this._pendingDeliveries)if(o.length){t[i]=o.map(a=>({seq:a.seq,data:a.data,useMux:a.useMux,ts:a.ts,tries:a.tries}));for(const a of o)s+=a.data.length}const n=s>this._reliableMaxBytes?{seqs:e}:{seqs:e,pending:t};localStorage.setItem("codeman:pendingInput",JSON.stringify(n))}catch{}}_computeConnectionDescriptor(){const{bytes:e,count:t}=this._pendingBytes(),s=t>0,i=e>4,o=l=>l<1024?`${l}B`:`${(l/1024).toFixed(1)}KB`,a=i?` \xB7 ${o(e)} queued`:"";if(!this.isOnline||this._connectionStatus==="offline")return{display:"flex",dotClass:"connection-dot offline",text:i?`Offline (${o(e)} queued)`:"Offline",title:"No network connection"};if(this.activeSessionId){let l,r,d;switch(this._wsState){case"connected":l="connected",r="WS",d="Terminal connected over WebSocket";break;case"fallback":l="fallback",r="HTTP",d="WebSocket unavailable \u2014 input sent over HTTP";break;case"reconnecting":l="reconnecting",r="WS\u2026",d="Reconnecting WebSocket";break;default:l="reconnecting",r="WS\u2026",d="Connecting WebSocket";break}return{display:"flex",dotClass:`connection-dot ${l}`,text:`${r}${a}`,title:d}}return this._connectionStatus==="reconnecting"||this._connectionStatus==="disconnected"?{display:"flex",dotClass:"connection-dot reconnecting",text:i?`Reconnecting (${o(e)} queued)`:"Reconnecting...",title:"Reconnecting to server"}:s?{display:"flex",dotClass:"connection-dot draining",text:i?`Sending ${o(e)}...`:"Sending...",title:"Delivering queued input"}:{display:"none",dotClass:"",text:"",title:""}}_updateConnectionIndicator(){const e=this.$("connectionIndicator"),t=this.$("connectionDot"),s=this.$("connectionText");if(!e||!t||!s)return;const n=this._computeConnectionDescriptor(),i=this._lastIndicatorDescriptor;i&&i.display===n.display&&i.dotClass===n.dotClass&&i.text===n.text&&i.title===n.title||(this._lastIndicatorDescriptor=n,e.style.display=n.display,n.display!=="none"&&(t.className=n.dotClass,s.textContent=n.text,e.title=n.title))}setupOnlineDetection(){window.addEventListener("online",()=>{this.isOnline=!0,this.reconnectAttempts=0,this._connDownSince=Date.now(),this.connectSSE(),this._redeliverSweep()}),window.addEventListener("offline",()=>{this.isOnline=!1,this.setConnectionStatus("offline")})}_updateConnectionLossUi(){const e=window.CodemanConnectionLoss,t=this.$("offlineBanner"),s=this.$("offlineOverlay");if(!e||!t||!s)return;const n=e.compute({isOnline:this.isOnline,status:this._connectionStatus,everLoaded:this._initGeneration>0,downSince:this._connDownSince,now:Date.now(),nextRetryAt:this._nextSseRetryAt,overlayDismissed:this._offlineOverlayDismissed,retryPending:this._offlineRetryPending});this._connDownSince===null?this._stopOfflineTicker():this._startOfflineTicker();const i=this._offlineRetryPending?"Reconnecting\u2026":n.retryInSec!=null&&n.retryInSec>0?`Retrying in ${n.retryInSec}s`:"Retrying\u2026",o=`${n.mode}|${n.kind}|${i}`;if(o!==this._lastOfflineUiKey){if(this._lastOfflineUiKey=o,t.hidden=n.mode!=="banner",s.hidden=n.mode!=="overlay",document.body.classList.toggle("connection-lost",n.mode!=="hidden"),n.mode==="banner"){const a=this.$("offlineBannerText"),l=this.$("offlineBannerDetail");a&&(a.textContent=n.title),l&&(l.textContent=i)}else if(n.mode==="overlay"){const a=this.$("offlineOverlayTitle"),l=this.$("offlineOverlayBody"),r=this.$("offlineOverlayHost"),d=this.$("offlineOverlayStatus");a&&(a.textContent=n.title),l&&(l.textContent=n.detail),r&&(r.textContent=location.host),d&&(d.textContent=i)}}}_startOfflineTicker(){this._offlineUiTicker||(this._offlineUiTicker=setInterval(()=>this._updateConnectionLossUi(),1e3))}_stopOfflineTicker(){this._offlineUiTicker&&(clearInterval(this._offlineUiTicker),this._offlineUiTicker=null)}retryConnection(){this._offlineRetryPending=!0,this._nextSseRetryAt=null,this.reconnectAttempts=0,this._clearTimer("sseReconnectTimeout"),this.isOnline=navigator.onLine,this._lastOfflineUiKey="",this._updateConnectionLossUi(),this.connectSSE(),this.activeSessionId&&this._wsState!=="connected"&&(this._wsReconnectAttempts=0,this._connectWs(this.activeSessionId)),this._clearTimer("_offlineRetryTimer"),this._offlineRetryTimer=setTimeout(()=>{this._offlineRetryPending=!1,this._lastOfflineUiKey="",this._updateConnectionLossUi()},1500)}dismissOfflineOverlay(){this._offlineOverlayDismissed=!0,this._lastOfflineUiKey="",this._updateConnectionLossUi()}_updateCjkInputState(){const e=document.getElementById("cjkInput");if(!e)return;const t=this.loadAppSettingsFromStorage(),s=this.getDefaultSettings?.()||{},i=(this._serverCjkOverride||(t.cjkInputEnabled??s.cjkInputEnabled??!1))&&!!this.activeSessionId;e.classList.toggle("cjk-input-visible",!!i),document.body.classList.toggle("cjk-input-visible",!!i),e.style.display=i?"block":"none",e.setAttribute("aria-hidden",i?"false":"true"),i||(window.cjkActive=!1),typeof KeyboardHandler<"u"&&KeyboardHandler.updateLayoutForKeyboard()}_resetAllAppState(){this.sessions.clear(),this.ralphStates.clear(),this.terminalBuffers.clear(),this.terminalBufferCache.clear(),this._xtermSnapshots?.clear(),this.projectInsights.clear(),this.teams.clear(),this.teamTasks.clear();for(const e of this.idleTimers.values())clearTimeout(e);if(this.idleTimers.clear(),this._clearTimer("flickerFilterTimeout"),this.flickerFilterBuffer="",this.flickerFilterActive=!1,this._clearTimer("syncWaitTimeout"),this.pendingWrites=[],this.writeFrameScheduled=!1,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._bufferLoadOwner=null,this._chunkedWriteGen=(this._chunkedWriteGen||0)+1,this._localEchoOverlay?.rerender(),this._predictiveEcho?.clearPredictions(),this.pendingHooks.clear(),this.approvals?.clear(),this._parentNameCache&&this._parentNameCache.clear(),this.subagentActivity.clear(),this.subagentToolResults.clear(),this.workflowRuns&&this.workflowRuns.clear(),this.workflowRunDetails&&this.workflowRunDetails.clear(),this.activeWorkflowRunId=null,this.activeWorkflowPhaseIndex=null,MobileDetection.cleanup(),KeyboardHandler.cleanup(),MobileDetection.init(),KeyboardHandler.init(),this.tabAlerts.clear(),this.attachmentHistoryCounts.clear(),this._shownCompletions&&this._shownCompletions.clear(),this.notificationManager?.titleFlashInterval&&(clearInterval(this.notificationManager.titleFlashInterval),this.notificationManager.titleFlashInterval=null),this.notificationManager?.groupingMap){for(const{timeout:e}of this.notificationManager.groupingMap.values())clearTimeout(e);this.notificationManager.groupingMap.clear()}this.terminalResizeObserver&&(this.terminalResizeObserver.disconnect(),this.terminalResizeObserver=null),this.planLoadingTimer&&(clearInterval(this.planLoadingTimer),this.planLoadingTimer=null),this.timerCountdownInterval&&(clearInterval(this.timerCountdownInterval),this.timerCountdownInterval=null),this.runSummaryAutoRefreshTimer&&(clearInterval(this.runSummaryAutoRefreshTimer),this.runSummaryAutoRefreshTimer=null)}handleInit(e){this._clearTimer("_initFallbackTimer");const t=++this._initGeneration;if(this._serverCjkOverride=e.inputCjkForm||!1,this._updateCjkInputState(),e.planUsage&&this.updatePlanUsageChip(e.planUsage),e.version){const i=this.$("versionDisplay"),o=this.$("headerVersion");i&&(i.textContent=`v${e.version}`,i.title=`Codeman v${e.version}`),o&&(o.textContent=`v${e.version}`,o.title=`Codeman v${e.version}`)}VoiceInput.cleanup(),this._resetAllAppState(),e.sessions.forEach(i=>{this.sessions.set(i.id,i),(i.ralphLoop||i.ralphTodos)&&!this.ralphClosedSessions.has(i.id)&&this.ralphStates.set(i.id,{loop:i.ralphLoop||null,todos:i.ralphTodos||[]})});try{localStorage.removeItem("codeman-tab-meta")}catch{}if(Array.isArray(e.sessionOrder)&&e.sessionOrder.length)try{localStorage.setItem("codeman-session-order",JSON.stringify(e.sessionOrder))}catch{}this.syncSessionOrder(),e.respawnStatus?this.respawnStatus=e.respawnStatus:this.respawnStatus={},this.respawnTimers={},this.respawnCountdownTimers={},this.respawnActionLogs={},e.globalStats&&(this.globalStats=e.globalStats),this.totalCost=e.sessions.reduce((i,o)=>i+(o.totalCost||0),0),this.totalCost+=e.scheduledRuns.reduce((i,o)=>i+(o.totalCost||0),0);const s=e.scheduledRuns.find(i=>i.status==="running");if(s&&(this.currentRun=s,this.showTimer()),this.updateCost(),this.renderSessionTabs(),this.seedApprovals?.(),this.sessions.size>0?this.startSystemStatsPolling():this.stopSystemStatsPolling(),this.cleanupAllFloatingWindows(),e.subagents&&(this.subagents.clear(),this.subagentActivity.clear(),this.subagentToolResults.clear(),e.subagents.forEach(i=>{this.subagents.set(i.agentId,i)}),this.renderSubagentPanel(),this.subagentParentMap.clear(),this.loadSubagentParentMap().then(()=>{for(const[i,o]of this.subagentParentMap){const a=this.subagents.get(i);if(a&&this.sessions.has(o)){a.parentSessionId=o;const l=this.sessions.get(o);l&&(a.parentSessionName=this.getSessionName(l)),this.subagents.set(i,a)}}for(const[i]of this.subagents)this.subagentParentMap.has(i)||this.findParentSessionForSubagent(i);this.restoreSubagentWindowStates()})),e.workflowRuns&&this.seedWorkflowRuns(e.workflowRuns),t!==this._initGeneration)return;if(this.isSoloWindow){this._applySoloMode();return}const n=this.activeSessionId;if(this.activeSessionId=null,this.sessionOrder.length>0){let i=n;if(!i||!this.sessions.has(i))try{i=localStorage.getItem("codeman-active-session")}catch{}i&&this.sessions.has(i)?this.selectSession(i):this.selectSession(this.sessionOrder[0])}}async loadState(){try{const t=await(await fetch("/api/status")).json();this.handleInit(t?.data??{})}catch(e){console.error("Failed to load state:",e)}}_debouncedCall(e,t,s=100){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{this._debounceTimers[e]=null,t.call(this)},s)}renderSessionTabs(){this._inlineRenameActive||this._debouncedCall("sessionTabs",this._renderSessionTabsImmediate)}_updateActiveTabImmediate(e){const t=this.$("sessionTabs");if(!t)return;const s=t.querySelectorAll(".session-tab[data-id]");for(const n of s)n.dataset.id===e?n.classList.add("active"):n.classList.remove("active")}_setTerminalLoadState(e,t,s){this.terminalLoadStates.set(e,{generation:t,phase:s}),this._updateTerminalLoadTab(e)}_clearTerminalLoadState(e,t){const s=this.terminalLoadStates.get(e);s&&s.generation!==t||(this.terminalLoadStates.delete(e),this._updateTerminalLoadTab(e))}_updateTerminalLoadTab(e){const t=this.$("sessionTabs")?.querySelector(`.session-tab[data-id="${e}"]`);if(!t)return;const s=this.terminalLoadStates.get(e);if(t.classList.toggle("tab-loading",!!s),s){if(t.setAttribute("aria-busy","true"),t.dataset.loadPhase=s.phase,!t.querySelector(".tab-load-spinner")){const n=document.createElement("span");n.className="tab-load-spinner",n.setAttribute("aria-hidden","true");const i=t.querySelector(".tab-number");i?i.insertAdjacentElement("afterend",n):t.insertBefore(n,t.firstChild)}}else t.setAttribute("aria-busy","false"),delete t.dataset.loadPhase,t.querySelector(".tab-load-spinner")?.remove()}_renderSessionTabsImmediate(){if(this._inlineRenameActive)return;const e=this.$("sessionTabs"),t=e.querySelectorAll(".session-tab[data-id]"),s=new Set([...t].map(r=>r.dataset.id)),n=new Set(this.sessions.keys()),i=[...e.querySelectorAll(".session-tab[data-webview-id]")].map(r=>r.dataset.webviewId),o=(this.webviewOrder||[]).filter(r=>this.webviews?.has(r)),a=i.length===o.length&&i.every((r,d)=>r===o[d]);if(s.size===n.size&&[...s].every(r=>n.has(r))&&a)for(const[r,d]of this.sessions){const c=e.querySelector(`.session-tab[data-id="${r}"]`);if(!c)continue;const u=r===this.activeSessionId&&!this.activeWebviewId,p=d.status||"idle",m=this.getSessionName(d),v=d.taskStats||{running:0,total:0},T=v.running>0,C=this.terminalLoadStates.get(r);if(u&&!c.classList.contains("active")?c.classList.add("active"):!u&&c.classList.contains("active")&&c.classList.remove("active"),c.classList.toggle("tab-loading",!!C),C){if(c.setAttribute("aria-busy","true"),c.dataset.loadPhase=C.phase,!c.querySelector(".tab-load-spinner")){const f=document.createElement("span");f.className="tab-load-spinner",f.setAttribute("aria-hidden","true");const g=c.querySelector(".tab-number");g?g.insertAdjacentElement("afterend",f):c.insertBefore(f,c.firstChild)}}else c.setAttribute("aria-busy","false"),delete c.dataset.loadPhase,c.querySelector(".tab-load-spinner")?.remove();const A=this.tabAlerts.get(r),L=A==="action",b=A==="idle",O=c.classList.contains("tab-alert-action"),S=c.classList.contains("tab-alert-idle");if(L&&!O?(c.classList.add("tab-alert-action"),c.classList.remove("tab-alert-idle")):b&&!S?(c.classList.add("tab-alert-idle"),c.classList.remove("tab-alert-action")):!A&&(O||S)&&c.classList.remove("tab-alert-action","tab-alert-idle"),!c.querySelector(".tab-number")){const f=this.sessionOrder.indexOf(r);if(f>=0&&f<9){const g=document.createElement("span");g.className="tab-number",g.textContent=String(f+1),c.insertBefore(g,c.firstChild)}}const w=c.querySelector(".tab-status");w&&!w.classList.contains(p)&&(w.className=`tab-status ${p}`);const y=c.querySelector(".tab-name");if(y){const f=parseSessionPrefix(m),g=f&&f.suffix?f.suffix:m;y.textContent!==g&&(y.textContent=g,c.title=f&&f.suffix?d.workingDir?`${f.prefix} (${d.workingDir})`:f.prefix:d.workingDir||"")}const _=c.querySelector(".tab-badge");if(T)if(_)_.textContent!==String(v.running)&&(_.textContent=v.running);else{this._fullRenderSessionTabs();return}else if(_){this._fullRenderSessionTabs();return}const E=c.querySelector(".tab-subagent-badge"),k=this.minimizedSubagents.get(r),R=k?.size||0;if(R>0&&E){const f=E.querySelector(".subagent-label"),g=R===1?"AGENT":`AGENTS (${R})`;f&&f.textContent!==g&&(f.textContent=g);const I=E.querySelector(".subagent-dropdown");if(I){const x=this.renderSubagentTabBadge(r,k),N=document.createElement("div");N.innerHTML=x;const D=N.querySelector(".subagent-dropdown");D&&(I.innerHTML=D.innerHTML)}}else if(R>0&&!E){const f=this.renderSubagentTabBadge(r,k),g=c.querySelector(".tab-actions");g&&g.insertAdjacentHTML("beforebegin",f)}else R===0&&E&&E.remove()}else this._fullRenderSessionTabs();this.updateTabOverflowMode(),this._applyTabEntrances?.(),this._refreshMobileOverviewIfVisible?.()}updateTabOverflowMode(){const e=this.$("sessionTabs");if(!e)return;const t=MobileDetection.getDeviceType(),s=this.loadAppSettingsFromStorage(),n=this.getDefaultSettings(),i=t==="desktop"?s.tabTwoRows??n.tabTwoRows??!1:!1;if(i||t!=="desktop"){e.classList.remove("tabs-auto-wrap");return}e.classList.remove("tabs-auto-wrap");const o=window.CodemanTabOverflow?.shouldAutoWrapTabs?window.CodemanTabOverflow.shouldAutoWrapTabs({deviceType:t,manualTwoRows:i,tabCount:this.sessions.size,scrollWidth:e.scrollWidth,clientWidth:e.clientWidth}):e.scrollWidth>e.clientWidth+1;e.classList.toggle("tabs-auto-wrap",o)}_setupTabMiddleClickClose(){const e=this.$("sessionTabs");!e||this._tabAuxClickBound||(this._tabAuxClickBound=!0,e.addEventListener("auxclick",t=>{if(t.button!==1)return;const s=t.target.closest?.(".session-tab");s&&(t.preventDefault(),t.stopPropagation(),s.dataset.id?this.requestCloseSession(s.dataset.id):s.dataset.webviewId&&this.closeWebviewTab?.(s.dataset.webviewId))}))}_fullRenderSessionTabs(){if(this._inlineRenameActive)return;const e=this.$("sessionTabs");document.querySelectorAll("body > .subagent-dropdown").forEach(i=>i.remove()),this.cancelHideSubagentDropdown();const t=[];let s=this.sessionOrder;MobileDetection.getDeviceType()==="mobile"&&this.activeSessionId&&(s=[this.activeSessionId,...this.sessionOrder.filter(i=>i!==this.activeSessionId)]);let n=0;for(const i of s){const o=this.sessions.get(i);if(!o)continue;const a=i===this.activeSessionId&&!this.activeWebviewId,l=o.status||"idle",r=this.getSessionName(o),d=o.mode||"claude",c=o.color||"default",u=o.taskStats||{running:0,total:0},p=u.running>0,m=this.tabAlerts.get(i),v=m==="action"?" tab-alert-action":m==="idle"?" tab-alert-idle":"",T=this.terminalLoadStates.get(i),C=this.minimizedSubagents.get(i),L=(C?.size||0)>0?this.renderSubagentTabBadge(i,C):"",b=this.renderUltracodeTabBadge?this.renderUltracodeTabBadge(i):"",O=o.workingDir&&o.workingDir.split("/").pop()||"",w=(this._tallTabsEnabled??!1)&&o.name&&O&&O!==r,y=parseSessionPrefix(r),_=y&&y.suffix?y.suffix:r,E=y&&y.suffix?o.workingDir?`${y.prefix} (${o.workingDir})`:y.prefix:o.workingDir||"";t.push(`<div class="session-tab ${a?"active":""}${v}${T?" tab-loading":""}" data-id="${i}" data-color="${c}" ${T?`data-load-phase="${escapeHtml(T.phase)}"`:""} onclick="app.handleSessionTabClick(event, ${escapeHtml(JSON.stringify(i))})" oncontextmenu="event.preventDefault(); app.startInlineRename(${escapeHtml(JSON.stringify(i))})" tabindex="0" role="tab" aria-selected="${a?"true":"false"}" aria-busy="${T?"true":"false"}" aria-label="${escapeHtml(r)} session" ${E?`title="${escapeHtml(E)}"`:""}>
|
|
16
|
+
Weekly limit: ${i??"\u2014"}% used (resets ${l(e.sevenDay)})`}_onScheduledCreated(e){this.currentRun=e,this.showTimer()}_onScheduledUpdated(e){this.currentRun=e,this.updateTimer()}_onScheduledCompleted(e){this.currentRun=e,this.hideTimer(),this.showToast("Scheduled run completed!","success")}_onScheduledStopped(){this.currentRun=null,this.hideTimer()}setConnectionStatus(e){this._connectionStatus=e,e==="connected"?(this._connDownSince=null,this._nextSseRetryAt=null,this._offlineOverlayDismissed=!1):this._connDownSince===null&&(this._connDownSince=Date.now()),this._updateConnectionIndicator(),this._updateConnectionLossUi(),e==="connected"&&this._redeliverSweep()}_connectWs(e){this._disconnectWs(),this._wsState="connecting",this._updateConnectionIndicator();const t=location.protocol==="https:"?"wss:":"ws:",s=this._clientId?`${this._clientId}:${this._wsTabNonce}`:"",n=s?`?cid=${encodeURIComponent(s)}`:"",i=`${t}//${location.host}/ws/sessions/${e}/terminal${n}`,o=new WebSocket(i);this._ws=o,this._wsSessionId=e,o.onopen=()=>{this._ws===o&&(this._wsReady=!0,this._wsState="connected",this._wsReconnectAttempts=0,this._updateConnectionIndicator(),this.sendResize(e)?.catch?.(()=>{}),this._startMobileResizeRetry(e),this._onWsReady(e))},o.onmessage=a=>{if(this._ws===o){this._wsLastRecvAt=Date.now();try{const l=JSON.parse(a.data);l.t==="o"?this._onSessionTerminal({id:e,data:l.d}):l.t==="c"?this._onSessionClearTerminal({id:e}):l.t==="r"?this._onSessionNeedsRefresh({id:e}):l.t==="ia"&&this._onWsInputAck(l.seq)}catch{}}},o.onclose=a=>{if(this._ws!==o)return;this._ws=null,this._wsSessionId=null,this._wsReady=!1,this._stopMobileResizeRetry();const l=window.CodemanWsReconnect.plan(a.code,this._wsReconnectAttempts||0);_crashDiag.log(`WS CLOSE code=${a.code} reason=${a.reason||""} action=${l.action} attempts=${this._wsReconnectAttempts||0}`);const r=this.activeSessionId===e;if(l.action==="give-up")this._wsState=r?"fallback":"disconnected",this._updateConnectionIndicator();else if(l.action==="reconnect")if(r){this._wsState="reconnecting",this._updateConnectionIndicator();const d=l.delayMs+Math.floor(Math.random()*250);this._wsReconnectAttempts=(this._wsReconnectAttempts||0)+1,this._wsReconnectTimer=setTimeout(()=>{this._wsReconnectTimer=null,this.activeSessionId===e&&this._connectWs(e)},d)}else this._wsState="disconnected",this._updateConnectionIndicator();else this._wsState=r?"fallback":"disconnected",this._updateConnectionIndicator(),r&&(this._wsReconnectAttempts=(this._wsReconnectAttempts||0)+1,this._wsReconnectTimer=setTimeout(()=>{this._wsReconnectTimer=null,this.activeSessionId===e&&this._connectWs(e)},l.delayMs))},o.onerror=()=>{}}_disconnectWs(){this._clearTimer("_wsReconnectTimer"),this._wsState="disconnected",this._stopMobileResizeRetry(),this._ws&&(this._ws.onclose=null,this._ws.close(),this._ws=null,this._wsSessionId=null,this._wsReady=!1)}_startMobileResizeRetry(e){this._stopMobileResizeRetry(),(typeof MobileDetection<"u"&&MobileDetection.getDeviceType?MobileDetection.getDeviceType():"desktop")!=="desktop"&&(this._mobileResizeRetryTimer=setInterval(()=>{document.visibilityState==="visible"&&(!this._wsReady||this._wsSessionId!==e||typeof KeyboardHandler<"u"&&KeyboardHandler.keyboardVisible||this.sendResize(e)?.catch?.(()=>{}))},MOBILE_RESIZE_RETRY_MS))}_stopMobileResizeRetry(){this._mobileResizeRetryTimer&&(clearInterval(this._mobileResizeRetryTimer),this._mobileResizeRetryTimer=null)}_sendInputAsync(e,t,s){!e||!t||this._reliableSend(e,t,s?.useMux===!0)}_sendInputEphemeral(e,t){if(!(!e||!t)){if(this._ws&&this._ws.readyState===WebSocket.OPEN&&this._wsSessionId===e)try{this._ws.send(JSON.stringify({t:"i",d:t}));return}catch{}try{fetch(`/api/sessions/${e}/input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input:t}),keepalive:!0}).catch(()=>{})}catch{}}}_reliableSend(e,t,s){const i={seq:this._nextSeq(e),data:t,useMux:!!s,ts:Date.now(),tries:0,sentAt:0};let o=this._pendingDeliveries.get(e);o||(o=[],this._pendingDeliveries.set(e,o)),o.push(i),this._persistReliableState(),this._updateConnectionIndicator(),this._drainSession(e)}_nextSeq(e){const t=(this._seqCounters.get(e)||0)+1;return this._seqCounters.set(e,t),t}_drainSession(e){const t=this._pendingDeliveries.get(e);if(!(!t||t.length===0)){if(this._ws&&this._ws.readyState===WebSocket.OPEN&&this._wsSessionId===e){for(const s of t)if(s.sentAt===0)try{this._ws.send(JSON.stringify({t:"i",d:s.data,seq:s.seq,cid:this._clientId})),s.sentAt=Date.now(),s.tries++}catch{break}return}this._postDraining.has(e)||(this._postDraining.add(e),(async()=>{try{for(;;){const s=this._pendingDeliveries.get(e);if(!s||s.length===0||this._ws&&this._ws.readyState===WebSocket.OPEN&&this._wsSessionId===e)break;const n=s[0];n.tries++,n.sentAt=Date.now();let i=null;try{const o={input:n.data,seq:n.seq,clientId:this._clientId};n.useMux&&(o.useMux=!0),i=await fetch(`/api/sessions/${e}/input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o),keepalive:n.data.length<65536})}catch{i=null}if(i&&i.ok)this._ackDelivery(e,n.seq);else if(i&&(i.status===404||i.status===410))this._ackDelivery(e,n.seq);else break}}finally{this._postDraining.delete(e)}})())}}_ackDelivery(e,t){const s=this._pendingDeliveries.get(e);if(s){const n=s.findIndex(i=>i.seq===t);n!==-1&&(s.splice(n,1),s.length===0&&this._pendingDeliveries.delete(e),this._pendingDeliveries.size===0?this._persistReliableNow():this._persistReliableState(),this._updateConnectionIndicator())}this.clearPendingHooks?.(e)}_onWsInputAck(e){this._wsSessionId&&Number.isInteger(e)&&this._ackDelivery(this._wsSessionId,e)}_onWsReady(e){const t=this._pendingDeliveries.get(e);if(t)for(const s of t)s.sentAt=0;this._drainSession(e)}_redeliverSweep(){if(this._pendingDeliveries.size!==0)for(const e of[...this._pendingDeliveries.keys()]){const t=this._pendingDeliveries.get(e);if(!t||t.length===0)continue;if(this._ws&&this._ws.readyState===WebSocket.OPEN&&this._wsSessionId===e){const n=t[0],i=n&&n.sentAt&&Date.now()-n.sentAt>this._reliableAckTimeoutMs,o=Date.now()-this._wsLastRecvAt>this._reliableAckTimeoutMs;if(i&&o){try{this._ws.close()}catch{}continue}if(i)for(const a of t)a.sentAt&&Date.now()-a.sentAt>this._reliableAckTimeoutMs&&(a.sentAt=0)}this._drainSession(e)}}_pendingBytes(){let e=0,t=0;for(const s of this._pendingDeliveries.values())for(const n of s)e+=n.data.length,t++;return{bytes:e,count:t}}_loadReliableState(){try{this._clientId=localStorage.getItem("codeman:clientId")||""}catch{this._clientId=""}if(!this._clientId){this._clientId="c-"+Math.random().toString(36).slice(2)+"-"+Date.now().toString(36);try{localStorage.setItem("codeman:clientId",this._clientId)}catch{}}try{const e=localStorage.getItem("codeman:pendingInput");if(!e)return;const t=JSON.parse(e);if(t&&t.seqs)for(const[s,n]of Object.entries(t.seqs))Number.isFinite(n)&&this._seqCounters.set(s,n);if(t&&t.pending)for(const[s,n]of Object.entries(t.pending))Array.isArray(n)&&n.length&&this._pendingDeliveries.set(s,n.filter(i=>i&&typeof i.data=="string"&&Number.isInteger(i.seq)).map(i=>({seq:i.seq,data:i.data,useMux:!!i.useMux,ts:i.ts||Date.now(),tries:0,sentAt:0})))}catch{}}_persistReliableState(){this._persistReliableTimer||(this._persistReliableTimer=setTimeout(()=>{this._persistReliableTimer=null,this._persistReliableNow()},250))}_persistReliableNow(){this._persistReliableTimer&&(clearTimeout(this._persistReliableTimer),this._persistReliableTimer=null);try{const e={};for(const[i,o]of this._seqCounters)e[i]=o;const t={};let s=0;for(const[i,o]of this._pendingDeliveries)if(o.length){t[i]=o.map(a=>({seq:a.seq,data:a.data,useMux:a.useMux,ts:a.ts,tries:a.tries}));for(const a of o)s+=a.data.length}const n=s>this._reliableMaxBytes?{seqs:e}:{seqs:e,pending:t};localStorage.setItem("codeman:pendingInput",JSON.stringify(n))}catch{}}_computeConnectionDescriptor(){const{bytes:e,count:t}=this._pendingBytes(),s=t>0,i=e>4,o=l=>l<1024?`${l}B`:`${(l/1024).toFixed(1)}KB`,a=i?` \xB7 ${o(e)} queued`:"";if(!this.isOnline||this._connectionStatus==="offline")return{display:"flex",dotClass:"connection-dot offline",text:i?`Offline (${o(e)} queued)`:"Offline",title:"No network connection"};if(this.activeSessionId){let l,r,d;switch(this._wsState){case"connected":l="connected",r="WS",d="Terminal connected over WebSocket";break;case"fallback":l="fallback",r="HTTP",d="WebSocket unavailable \u2014 input sent over HTTP";break;case"reconnecting":l="reconnecting",r="WS\u2026",d="Reconnecting WebSocket";break;default:l="reconnecting",r="WS\u2026",d="Connecting WebSocket";break}return{display:"flex",dotClass:`connection-dot ${l}`,text:`${r}${a}`,title:d}}return this._connectionStatus==="reconnecting"||this._connectionStatus==="disconnected"?{display:"flex",dotClass:"connection-dot reconnecting",text:i?`Reconnecting (${o(e)} queued)`:"Reconnecting...",title:"Reconnecting to server"}:s?{display:"flex",dotClass:"connection-dot draining",text:i?`Sending ${o(e)}...`:"Sending...",title:"Delivering queued input"}:{display:"none",dotClass:"",text:"",title:""}}_updateConnectionIndicator(){const e=this.$("connectionIndicator"),t=this.$("connectionDot"),s=this.$("connectionText");if(!e||!t||!s)return;const n=this._computeConnectionDescriptor(),i=this._lastIndicatorDescriptor;i&&i.display===n.display&&i.dotClass===n.dotClass&&i.text===n.text&&i.title===n.title||(this._lastIndicatorDescriptor=n,e.style.display=n.display,n.display!=="none"&&(t.className=n.dotClass,s.textContent=n.text,e.title=n.title))}setupOnlineDetection(){window.addEventListener("online",()=>{this.isOnline=!0,this.reconnectAttempts=0,this._connDownSince=Date.now(),this.connectSSE(),this._redeliverSweep()}),window.addEventListener("offline",()=>{this.isOnline=!1,this.setConnectionStatus("offline")})}_updateConnectionLossUi(){const e=window.CodemanConnectionLoss,t=this.$("offlineBanner"),s=this.$("offlineOverlay");if(!e||!t||!s)return;const n=e.compute({isOnline:this.isOnline,status:this._connectionStatus,everLoaded:this._initGeneration>0,downSince:this._connDownSince,now:Date.now(),nextRetryAt:this._nextSseRetryAt,overlayDismissed:this._offlineOverlayDismissed,retryPending:this._offlineRetryPending});this._connDownSince===null?this._stopOfflineTicker():this._startOfflineTicker();const i=this._offlineRetryPending?"Reconnecting\u2026":n.retryInSec!=null&&n.retryInSec>0?`Retrying in ${n.retryInSec}s`:"Retrying\u2026",o=`${n.mode}|${n.kind}|${i}`;if(o!==this._lastOfflineUiKey){if(this._lastOfflineUiKey=o,t.hidden=n.mode!=="banner",s.hidden=n.mode!=="overlay",document.body.classList.toggle("connection-lost",n.mode!=="hidden"),n.mode==="banner"){const a=this.$("offlineBannerText"),l=this.$("offlineBannerDetail");a&&(a.textContent=n.title),l&&(l.textContent=i)}else if(n.mode==="overlay"){const a=this.$("offlineOverlayTitle"),l=this.$("offlineOverlayBody"),r=this.$("offlineOverlayHost"),d=this.$("offlineOverlayStatus");a&&(a.textContent=n.title),l&&(l.textContent=n.detail),r&&(r.textContent=location.host),d&&(d.textContent=i)}}}_startOfflineTicker(){this._offlineUiTicker||(this._offlineUiTicker=setInterval(()=>this._updateConnectionLossUi(),1e3))}_stopOfflineTicker(){this._offlineUiTicker&&(clearInterval(this._offlineUiTicker),this._offlineUiTicker=null)}retryConnection(){this._offlineRetryPending=!0,this._nextSseRetryAt=null,this.reconnectAttempts=0,this._clearTimer("sseReconnectTimeout"),this.isOnline=navigator.onLine,this._lastOfflineUiKey="",this._updateConnectionLossUi(),this.connectSSE(),this.activeSessionId&&this._wsState!=="connected"&&(this._wsReconnectAttempts=0,this._connectWs(this.activeSessionId)),this._clearTimer("_offlineRetryTimer"),this._offlineRetryTimer=setTimeout(()=>{this._offlineRetryPending=!1,this._lastOfflineUiKey="",this._updateConnectionLossUi()},1500)}dismissOfflineOverlay(){this._offlineOverlayDismissed=!0,this._lastOfflineUiKey="",this._updateConnectionLossUi()}_updateCjkInputState(){const e=document.getElementById("cjkInput");if(!e)return;const t=this.loadAppSettingsFromStorage(),s=this.getDefaultSettings?.()||{},i=(this._serverCjkOverride||(t.cjkInputEnabled??s.cjkInputEnabled??!1))&&!!this.activeSessionId;e.classList.toggle("cjk-input-visible",!!i),document.body.classList.toggle("cjk-input-visible",!!i),e.style.display=i?"block":"none",e.setAttribute("aria-hidden",i?"false":"true"),i||(window.cjkActive=!1),typeof KeyboardHandler<"u"&&KeyboardHandler.updateLayoutForKeyboard()}_resetAllAppState(){this.sessions.clear(),this.ralphStates.clear(),this.terminalBuffers.clear(),this.terminalBufferCache.clear(),this._xtermSnapshots?.clear(),this.projectInsights.clear(),this.teams.clear(),this.teamTasks.clear();for(const e of this.idleTimers.values())clearTimeout(e);if(this.idleTimers.clear(),this._clearTimer("flickerFilterTimeout"),this.flickerFilterBuffer="",this.flickerFilterActive=!1,this._clearTimer("syncWaitTimeout"),this.pendingWrites=[],this.writeFrameScheduled=!1,this._isLoadingBuffer=!1,this._loadBufferQueue=null,this._bufferLoadOwner=null,this._chunkedWriteGen=(this._chunkedWriteGen||0)+1,this._localEchoOverlay?.rerender(),this._predictiveEcho?.clearPredictions(),this.pendingHooks.clear(),this.approvals?.clear(),this._parentNameCache&&this._parentNameCache.clear(),this.subagentActivity.clear(),this.subagentToolResults.clear(),this.workflowRuns&&this.workflowRuns.clear(),this.workflowRunDetails&&this.workflowRunDetails.clear(),this.activeWorkflowRunId=null,this.activeWorkflowPhaseIndex=null,MobileDetection.cleanup(),KeyboardHandler.cleanup(),MobileDetection.init(),KeyboardHandler.init(),this.tabAlerts.clear(),this.attachmentHistoryCounts.clear(),this._shownCompletions&&this._shownCompletions.clear(),this.notificationManager?.titleFlashInterval&&(clearInterval(this.notificationManager.titleFlashInterval),this.notificationManager.titleFlashInterval=null),this.notificationManager?.groupingMap){for(const{timeout:e}of this.notificationManager.groupingMap.values())clearTimeout(e);this.notificationManager.groupingMap.clear()}this.terminalResizeObserver&&(this.terminalResizeObserver.disconnect(),this.terminalResizeObserver=null),this.planLoadingTimer&&(clearInterval(this.planLoadingTimer),this.planLoadingTimer=null),this.timerCountdownInterval&&(clearInterval(this.timerCountdownInterval),this.timerCountdownInterval=null),this.runSummaryAutoRefreshTimer&&(clearInterval(this.runSummaryAutoRefreshTimer),this.runSummaryAutoRefreshTimer=null)}handleInit(e){this._clearTimer("_initFallbackTimer");const t=++this._initGeneration;if(this._serverCjkOverride=e.inputCjkForm||!1,this._updateCjkInputState(),e.planUsage&&this.updatePlanUsageChip(e.planUsage),e.version){const i=this.$("versionDisplay"),o=this.$("headerVersion");i&&(i.textContent=`v${e.version}`,i.title=`Codeman v${e.version}`),o&&(o.textContent=`v${e.version}`,o.title=`Codeman v${e.version}`)}VoiceInput.cleanup(),this._resetAllAppState(),e.sessions.forEach(i=>{this.sessions.set(i.id,i),(i.ralphLoop||i.ralphTodos)&&!this.ralphClosedSessions.has(i.id)&&this.ralphStates.set(i.id,{loop:i.ralphLoop||null,todos:i.ralphTodos||[]})});try{localStorage.removeItem("codeman-tab-meta")}catch{}if(Array.isArray(e.sessionOrder)&&e.sessionOrder.length)try{localStorage.setItem("codeman-session-order",JSON.stringify(e.sessionOrder))}catch{}this.syncSessionOrder(),e.respawnStatus?this.respawnStatus=e.respawnStatus:this.respawnStatus={},this.respawnTimers={},this.respawnCountdownTimers={},this.respawnActionLogs={},e.globalStats&&(this.globalStats=e.globalStats),this.totalCost=e.sessions.reduce((i,o)=>i+(o.totalCost||0),0),this.totalCost+=e.scheduledRuns.reduce((i,o)=>i+(o.totalCost||0),0);const s=e.scheduledRuns.find(i=>i.status==="running");if(s&&(this.currentRun=s,this.showTimer()),this.updateCost(),this.renderSessionTabs(),this.seedApprovals?.(),this.sessions.size>0?this.startSystemStatsPolling():this.stopSystemStatsPolling(),this.cleanupAllFloatingWindows(),e.subagents&&(this.subagents.clear(),this.subagentActivity.clear(),this.subagentToolResults.clear(),e.subagents.forEach(i=>{this.subagents.set(i.agentId,i)}),this.renderSubagentPanel(),this.subagentParentMap.clear(),this.loadSubagentParentMap().then(()=>{for(const[i,o]of this.subagentParentMap){const a=this.subagents.get(i);if(a&&this.sessions.has(o)){a.parentSessionId=o;const l=this.sessions.get(o);l&&(a.parentSessionName=this.getSessionName(l)),this.subagents.set(i,a)}}for(const[i]of this.subagents)this.subagentParentMap.has(i)||this.findParentSessionForSubagent(i);this.restoreSubagentWindowStates()})),e.workflowRuns&&this.seedWorkflowRuns(e.workflowRuns),t!==this._initGeneration)return;if(this.isSoloWindow){this._applySoloMode();return}const n=this.activeSessionId;if(this.activeSessionId=null,this.sessionOrder.length>0){let i=n;if(!i||!this.sessions.has(i))try{i=localStorage.getItem("codeman-active-session")}catch{}i&&this.sessions.has(i)?this.selectSession(i):this.selectSession(this.sessionOrder[0])}}async loadState(){try{const t=await(await fetch("/api/status")).json();this.handleInit(t?.data??{})}catch(e){console.error("Failed to load state:",e)}}_debouncedCall(e,t,s=100){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{this._debounceTimers[e]=null,t.call(this)},s)}renderSessionTabs(){this._inlineRenameActive||this._debouncedCall("sessionTabs",this._renderSessionTabsImmediate)}_updateActiveTabImmediate(e){const t=this.$("sessionTabs");if(!t)return;const s=t.querySelectorAll(".session-tab[data-id]");for(const n of s)n.dataset.id===e?n.classList.add("active"):n.classList.remove("active")}_setTerminalLoadState(e,t,s){this.terminalLoadStates.set(e,{generation:t,phase:s}),this._updateTerminalLoadTab(e)}_clearTerminalLoadState(e,t){const s=this.terminalLoadStates.get(e);s&&s.generation!==t||(this.terminalLoadStates.delete(e),this._updateTerminalLoadTab(e))}_updateTerminalLoadTab(e){const t=this.$("sessionTabs")?.querySelector(`.session-tab[data-id="${e}"]`);if(!t)return;const s=this.terminalLoadStates.get(e);if(t.classList.toggle("tab-loading",!!s),s){if(t.setAttribute("aria-busy","true"),t.dataset.loadPhase=s.phase,!t.querySelector(".tab-load-spinner")){const n=document.createElement("span");n.className="tab-load-spinner",n.setAttribute("aria-hidden","true");const i=t.querySelector(".tab-number");i?i.insertAdjacentElement("afterend",n):t.insertBefore(n,t.firstChild)}}else t.setAttribute("aria-busy","false"),delete t.dataset.loadPhase,t.querySelector(".tab-load-spinner")?.remove()}_renderSessionTabsImmediate(){if(this._inlineRenameActive)return;const e=this.$("sessionTabs"),t=e.querySelectorAll(".session-tab[data-id]"),s=new Set([...t].map(r=>r.dataset.id)),n=new Set(this.sessions.keys()),i=[...e.querySelectorAll(".session-tab[data-webview-id]")].map(r=>r.dataset.webviewId),o=(this.webviewOrder||[]).filter(r=>this.webviews?.has(r)),a=i.length===o.length&&i.every((r,d)=>r===o[d]);if(s.size===n.size&&[...s].every(r=>n.has(r))&&a)for(const[r,d]of this.sessions){const c=e.querySelector(`.session-tab[data-id="${r}"]`);if(!c)continue;const u=r===this.activeSessionId&&!this.activeWebviewId,p=d.status||"idle",m=this.getSessionName(d),v=d.taskStats||{running:0,total:0},T=v.running>0,C=this.terminalLoadStates.get(r);if(u&&!c.classList.contains("active")?c.classList.add("active"):!u&&c.classList.contains("active")&&c.classList.remove("active"),c.classList.toggle("tab-loading",!!C),C){if(c.setAttribute("aria-busy","true"),c.dataset.loadPhase=C.phase,!c.querySelector(".tab-load-spinner")){const f=document.createElement("span");f.className="tab-load-spinner",f.setAttribute("aria-hidden","true");const g=c.querySelector(".tab-number");g?g.insertAdjacentElement("afterend",f):c.insertBefore(f,c.firstChild)}}else c.setAttribute("aria-busy","false"),delete c.dataset.loadPhase,c.querySelector(".tab-load-spinner")?.remove();const A=this.tabAlerts.get(r),L=A==="action",b=A==="idle",O=c.classList.contains("tab-alert-action"),S=c.classList.contains("tab-alert-idle");if(L&&!O?(c.classList.add("tab-alert-action"),c.classList.remove("tab-alert-idle")):b&&!S?(c.classList.add("tab-alert-idle"),c.classList.remove("tab-alert-action")):!A&&(O||S)&&c.classList.remove("tab-alert-action","tab-alert-idle"),!c.querySelector(".tab-number")){const f=this.sessionOrder.indexOf(r);if(f>=0&&f<9){const g=document.createElement("span");g.className="tab-number",g.textContent=String(f+1),c.insertBefore(g,c.firstChild)}}const w=c.querySelector(".tab-status");w&&!w.classList.contains(p)&&(w.className=`tab-status ${p}`);const y=c.querySelector(".tab-name");if(y){const f=parseSessionPrefix(m),g=f&&f.suffix?f.suffix:m;y.textContent!==g&&(y.textContent=g,c.title=f&&f.suffix?d.workingDir?`${f.prefix} (${d.workingDir})`:f.prefix:d.workingDir||"")}const _=c.querySelector(".tab-badge");if(T)if(_)_.textContent!==String(v.running)&&(_.textContent=v.running);else{this._fullRenderSessionTabs();return}else if(_){this._fullRenderSessionTabs();return}const E=c.querySelector(".tab-subagent-badge"),k=this.minimizedSubagents.get(r),R=k?.size||0;if(R>0&&E){const f=E.querySelector(".subagent-label"),g=R===1?"AGENT":`AGENTS (${R})`;f&&f.textContent!==g&&(f.textContent=g);const I=E.querySelector(".subagent-dropdown");if(I){const x=this.renderSubagentTabBadge(r,k),N=document.createElement("div");N.innerHTML=x;const D=N.querySelector(".subagent-dropdown");D&&(I.innerHTML=D.innerHTML)}}else if(R>0&&!E){const f=this.renderSubagentTabBadge(r,k),g=c.querySelector(".tab-actions");g&&g.insertAdjacentHTML("beforebegin",f)}else R===0&&E&&E.remove()}else this._fullRenderSessionTabs();this.updateTabOverflowMode(),this._applyTabEntrances?.(),this._refreshMobileOverviewIfVisible?.(),this._refreshHomeSessionsIfVisible?.()}updateTabOverflowMode(){const e=this.$("sessionTabs");if(!e)return;const t=MobileDetection.getDeviceType(),s=this.loadAppSettingsFromStorage(),n=this.getDefaultSettings(),i=t==="desktop"?s.tabTwoRows??n.tabTwoRows??!1:!1;if(i||t!=="desktop"){e.classList.remove("tabs-auto-wrap");return}e.classList.remove("tabs-auto-wrap");const o=window.CodemanTabOverflow?.shouldAutoWrapTabs?window.CodemanTabOverflow.shouldAutoWrapTabs({deviceType:t,manualTwoRows:i,tabCount:this.sessions.size,scrollWidth:e.scrollWidth,clientWidth:e.clientWidth}):e.scrollWidth>e.clientWidth+1;e.classList.toggle("tabs-auto-wrap",o)}_setupTabMiddleClickClose(){const e=this.$("sessionTabs");!e||this._tabAuxClickBound||(this._tabAuxClickBound=!0,e.addEventListener("auxclick",t=>{if(t.button!==1)return;const s=t.target.closest?.(".session-tab");s&&(t.preventDefault(),t.stopPropagation(),s.dataset.id?this.requestCloseSession(s.dataset.id):s.dataset.webviewId&&this.closeWebviewTab?.(s.dataset.webviewId))}))}_fullRenderSessionTabs(){if(this._inlineRenameActive)return;const e=this.$("sessionTabs");document.querySelectorAll("body > .subagent-dropdown").forEach(i=>i.remove()),this.cancelHideSubagentDropdown();const t=[];let s=this.sessionOrder;MobileDetection.getDeviceType()==="mobile"&&this.activeSessionId&&(s=[this.activeSessionId,...this.sessionOrder.filter(i=>i!==this.activeSessionId)]);let n=0;for(const i of s){const o=this.sessions.get(i);if(!o)continue;const a=i===this.activeSessionId&&!this.activeWebviewId,l=o.status||"idle",r=this.getSessionName(o),d=o.mode||"claude",c=o.color||"default",u=o.taskStats||{running:0,total:0},p=u.running>0,m=this.tabAlerts.get(i),v=m==="action"?" tab-alert-action":m==="idle"?" tab-alert-idle":"",T=this.terminalLoadStates.get(i),C=this.minimizedSubagents.get(i),L=(C?.size||0)>0?this.renderSubagentTabBadge(i,C):"",b=this.renderUltracodeTabBadge?this.renderUltracodeTabBadge(i):"",O=o.workingDir&&o.workingDir.split("/").pop()||"",w=(this._tallTabsEnabled??!1)&&o.name&&O&&O!==r,y=parseSessionPrefix(r),_=y&&y.suffix?y.suffix:r,E=y&&y.suffix?o.workingDir?`${y.prefix} (${o.workingDir})`:y.prefix:o.workingDir||"";t.push(`<div class="session-tab ${a?"active":""}${v}${T?" tab-loading":""}" data-id="${i}" data-color="${c}" ${T?`data-load-phase="${escapeHtml(T.phase)}"`:""} onclick="app.handleSessionTabClick(event, ${escapeHtml(JSON.stringify(i))})" oncontextmenu="event.preventDefault(); app.startInlineRename(${escapeHtml(JSON.stringify(i))})" tabindex="0" role="tab" aria-selected="${a?"true":"false"}" aria-busy="${T?"true":"false"}" aria-label="${escapeHtml(r)} session" ${E?`title="${escapeHtml(E)}"`:""}>
|
|
17
17
|
${n<9?'<span class="tab-number">'+(n+1)+"</span>":""}
|
|
18
18
|
${T?'<span class="tab-load-spinner" aria-hidden="true"></span>':""}
|
|
19
19
|
<span class="tab-status ${l}" aria-hidden="true"></span>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Desktop home screen session list: the open tabs as a vertical
|
|
3
|
+
* column down the left of the welcome overlay.
|
|
4
|
+
*
|
|
5
|
+
* The welcome screen centers ~560px of content in a window that is usually
|
|
6
|
+
* 1400px+, so the two gutters are dead space. The left one now carries the same
|
|
7
|
+
* list a phone gets on its home screen (mobile-overview.js), turned vertical:
|
|
8
|
+
* one row per live tab, in TAB ORDER (not sorted by state) so it reads as the
|
|
9
|
+
* tab strip rotated, and so Alt+1..9 still matches what you see.
|
|
10
|
+
*
|
|
11
|
+
* DESKTOP ONLY, and only in a wide enough window: the column is absolutely
|
|
12
|
+
* positioned so the centered welcome content never moves, which means it can
|
|
13
|
+
* only exist where the gutter is genuinely wider than the column. Below
|
|
14
|
+
* `HOME_SESSIONS_MIN_WIDTH` nothing renders; on a phone the mobile overview owns
|
|
15
|
+
* the home screen entirely and this surface stays out of its way.
|
|
16
|
+
*
|
|
17
|
+
* The working state is deliberately identical to the phone's: a pulsing green
|
|
18
|
+
* dot ringed by the spinner a tab shows while it loads (`tab-load-spin`, reused
|
|
19
|
+
* from styles.css), plus a green halo. Same signal, same motion, both surfaces.
|
|
20
|
+
*
|
|
21
|
+
* Everything renders from state the page already holds (`this.sessions`,
|
|
22
|
+
* `this.cases`, `this.pendingHooks`, `this.webviews`) — no endpoint, no SSE
|
|
23
|
+
* event, no schema. State classification and case matching are reused from
|
|
24
|
+
* mobile-overview.js rather than re-derived, so the two home screens can never
|
|
25
|
+
* disagree about what "working" means.
|
|
26
|
+
*
|
|
27
|
+
* @mixin Extends CodemanApp.prototype via Object.assign
|
|
28
|
+
* @dependency app.js (this.sessions, this.cases, this.pendingHooks, selectSession)
|
|
29
|
+
* @dependency mobile-overview.js (_mobileOverviewState, _mobileOverviewCaseFor, shouldUseMobileOverview)
|
|
30
|
+
* @dependency webview-tabs.js (this.webviews, this.webviewOrder, openWebview)
|
|
31
|
+
* @dependency mobile-handlers.js (MobileDetection)
|
|
32
|
+
* @loadorder 12.56 of 16, after mobile-overview.js, before entrance-animations.js
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Narrowest window that gets the column. The welcome content is 560px wide and
|
|
37
|
+
* centered, so at 1180px each gutter is 310px — enough for the 256px column plus
|
|
38
|
+
* its 20px offset and still a visible gap. Anything narrower would overlap the
|
|
39
|
+
* search panel, which is why this is a width gate and not a device-type gate.
|
|
40
|
+
*/
|
|
41
|
+
const HOME_SESSIONS_MIN_WIDTH = 1180;
|
|
42
|
+
|
|
43
|
+
/** Pill copy per state. Same words as the phone overview, same reasons. */
|
|
44
|
+
const HOME_SESSIONS_PILL_LABEL = {
|
|
45
|
+
needs: 'needs you',
|
|
46
|
+
error: 'error',
|
|
47
|
+
waiting: 'waiting',
|
|
48
|
+
working: 'working',
|
|
49
|
+
idle: 'idle',
|
|
50
|
+
done: 'done',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** Short backend badge, mirroring `.tab-mode` in the tab strip. */
|
|
54
|
+
const HOME_SESSIONS_MODE_BADGE = {
|
|
55
|
+
shell: 'sh',
|
|
56
|
+
opencode: 'oc',
|
|
57
|
+
codex: 'cx',
|
|
58
|
+
gemini: 'gm',
|
|
59
|
+
antigravity: 'ag',
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
Object.assign(CodemanApp.prototype, {
|
|
63
|
+
// ═══════════════════════════════════════════════════════════════
|
|
64
|
+
// Gate + visibility
|
|
65
|
+
// ═══════════════════════════════════════════════════════════════
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Width-driven, like every other layout decision in the app. Explicitly yields
|
|
69
|
+
* to the phone overview: that surface already lists the same sessions, and two
|
|
70
|
+
* lists of the same thing on one screen is worse than none.
|
|
71
|
+
*/
|
|
72
|
+
shouldShowHomeSessions() {
|
|
73
|
+
if (this.isSoloWindow) return false;
|
|
74
|
+
if (this.shouldUseMobileOverview?.()) return false;
|
|
75
|
+
return window.innerWidth >= HOME_SESSIONS_MIN_WIDTH;
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
/** True while the column is the visible home surface. */
|
|
79
|
+
isHomeSessionsVisible() {
|
|
80
|
+
const el = document.getElementById('homeSessions');
|
|
81
|
+
return !!el && !el.hidden;
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
showHomeSessions() {
|
|
85
|
+
const el = document.getElementById('homeSessions');
|
|
86
|
+
if (!el) return;
|
|
87
|
+
this._wireHomeSessions(el);
|
|
88
|
+
if (!this.shouldShowHomeSessions()) {
|
|
89
|
+
el.hidden = true;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
el.hidden = false;
|
|
93
|
+
this.renderHomeSessions();
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
hideHomeSessions() {
|
|
97
|
+
const el = document.getElementById('homeSessions');
|
|
98
|
+
if (el) el.hidden = true;
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
/** Re-render only when showing (called from the tab renderer's tail). */
|
|
102
|
+
_refreshHomeSessionsIfVisible() {
|
|
103
|
+
if (!this.isHomeSessionsVisible()) return;
|
|
104
|
+
this._debouncedCall('homeSessions', () => this.renderHomeSessions(), 150);
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* One delegated click listener for every row, plus a width listener so
|
|
109
|
+
* resizing the window while on the home screen adds or drops the column
|
|
110
|
+
* instead of leaving it overlapping the content it was sized to clear.
|
|
111
|
+
*/
|
|
112
|
+
_wireHomeSessions(el) {
|
|
113
|
+
if (this._homeSessionsWired) return;
|
|
114
|
+
this._homeSessionsWired = true;
|
|
115
|
+
|
|
116
|
+
el.addEventListener('click', (event) => {
|
|
117
|
+
const target = event.target?.closest?.('[data-hs-action]');
|
|
118
|
+
if (!target) return;
|
|
119
|
+
if (target.dataset.hsAction === 'session') {
|
|
120
|
+
void this.selectSession(target.dataset.hsSession);
|
|
121
|
+
} else if (target.dataset.hsAction === 'webview') {
|
|
122
|
+
void this.openWebview?.(target.dataset.hsWebview);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
if (window.matchMedia) {
|
|
127
|
+
const mq = window.matchMedia(`(min-width: ${HOME_SESSIONS_MIN_WIDTH}px)`);
|
|
128
|
+
const onChange = () => {
|
|
129
|
+
// Only relevant while the welcome screen is up; entering a session
|
|
130
|
+
// re-decides through hideWelcome()/showWelcome() anyway.
|
|
131
|
+
if (this.activeSessionId) return;
|
|
132
|
+
const overlay = document.getElementById('welcomeOverlay');
|
|
133
|
+
if (!overlay || !overlay.classList.contains('visible')) return;
|
|
134
|
+
this.showHomeSessions();
|
|
135
|
+
};
|
|
136
|
+
if (mq.addEventListener) mq.addEventListener('change', onChange);
|
|
137
|
+
else if (mq.addListener) mq.addListener(onChange);
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
// ═══════════════════════════════════════════════════════════════
|
|
142
|
+
// Model
|
|
143
|
+
// ═══════════════════════════════════════════════════════════════
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* One row per live session, in the user's tab order. State classification is
|
|
147
|
+
* `_mobileOverviewState()` (mobile-overview.js) so both home screens agree on
|
|
148
|
+
* what counts as needing you; the ORDER differs on purpose — the phone sorts
|
|
149
|
+
* by urgency because it shows one screenful at a time, this column mirrors the
|
|
150
|
+
* tab strip so the number badges line up with Alt+1..9.
|
|
151
|
+
* @returns {Array<object>} row descriptors, ready to render
|
|
152
|
+
*/
|
|
153
|
+
buildHomeSessionRows() {
|
|
154
|
+
const cases = Array.isArray(this.cases) ? this.cases : [];
|
|
155
|
+
const order = Array.isArray(this.sessionOrder) ? this.sessionOrder : [];
|
|
156
|
+
const ids = order.filter((id) => this.sessions?.has(id));
|
|
157
|
+
// A session created before the order list caught up would otherwise be
|
|
158
|
+
// invisible here while its tab already exists.
|
|
159
|
+
for (const id of this.sessions?.keys() || []) if (!ids.includes(id)) ids.push(id);
|
|
160
|
+
|
|
161
|
+
return ids.map((id, index) => {
|
|
162
|
+
const session = this.sessions.get(id);
|
|
163
|
+
const matched = this._mobileOverviewCaseFor(session.workingDir, cases);
|
|
164
|
+
const state = this._mobileOverviewState(session, this.pendingHooks?.get(id));
|
|
165
|
+
const mode = session.mode || 'claude';
|
|
166
|
+
return {
|
|
167
|
+
id,
|
|
168
|
+
index,
|
|
169
|
+
name: this.getSessionName ? this.getSessionName(session) : session.name || id.slice(0, 8),
|
|
170
|
+
mode,
|
|
171
|
+
modeBadge: HOME_SESSIONS_MODE_BADGE[mode] || '',
|
|
172
|
+
caseName: matched ? matched.name : '',
|
|
173
|
+
dir: this._shortenHomePath ? this._shortenHomePath(session.workingDir) : session.workingDir || '',
|
|
174
|
+
state,
|
|
175
|
+
pill: HOME_SESSIONS_PILL_LABEL[state] || state,
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
// ═══════════════════════════════════════════════════════════════
|
|
181
|
+
// Render
|
|
182
|
+
// ═══════════════════════════════════════════════════════════════
|
|
183
|
+
|
|
184
|
+
renderHomeSessions() {
|
|
185
|
+
const el = document.getElementById('homeSessions');
|
|
186
|
+
if (!el) return;
|
|
187
|
+
|
|
188
|
+
const rows = this.buildHomeSessionRows();
|
|
189
|
+
const webviews = (this.webviewOrder || []).map((id) => this.webviews?.get(id)).filter(Boolean);
|
|
190
|
+
|
|
191
|
+
// Nothing open means nothing to list: an empty framed box next to a
|
|
192
|
+
// first-run welcome screen is noise, not information.
|
|
193
|
+
if (!rows.length && !webviews.length) {
|
|
194
|
+
el.hidden = true;
|
|
195
|
+
el.replaceChildren();
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
el.hidden = false;
|
|
199
|
+
|
|
200
|
+
el.replaceChildren();
|
|
201
|
+
el.appendChild(this._buildHomeSessionsHeader(rows.length + webviews.length));
|
|
202
|
+
|
|
203
|
+
const list = document.createElement('div');
|
|
204
|
+
list.className = 'home-sessions-list';
|
|
205
|
+
for (const row of rows) list.appendChild(this._buildHomeSessionRow(row));
|
|
206
|
+
for (const webview of webviews) list.appendChild(this._buildHomeSessionsWebviewRow(webview));
|
|
207
|
+
el.appendChild(list);
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
_buildHomeSessionsHeader(count) {
|
|
211
|
+
const header = document.createElement('div');
|
|
212
|
+
header.className = 'home-sessions-header';
|
|
213
|
+
|
|
214
|
+
const label = document.createElement('span');
|
|
215
|
+
label.className = 'home-sessions-title';
|
|
216
|
+
label.textContent = 'Open tabs';
|
|
217
|
+
header.appendChild(label);
|
|
218
|
+
|
|
219
|
+
const badge = document.createElement('span');
|
|
220
|
+
badge.className = 'home-sessions-count';
|
|
221
|
+
badge.setAttribute('data-i18n-skip', '');
|
|
222
|
+
badge.textContent = String(count);
|
|
223
|
+
header.appendChild(badge);
|
|
224
|
+
|
|
225
|
+
return header;
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* A session row. The state class drives the same visual language as the
|
|
230
|
+
* session tabs and the phone overview: green dot when it is fine (pulsing and
|
|
231
|
+
* ringed by the load spinner while working), a yellow row when it wants input,
|
|
232
|
+
* a red row when it asked a question.
|
|
233
|
+
*/
|
|
234
|
+
_buildHomeSessionRow(row) {
|
|
235
|
+
const item = document.createElement('button');
|
|
236
|
+
item.type = 'button';
|
|
237
|
+
item.className = 'home-sessions-row home-sessions-row--' + row.state;
|
|
238
|
+
item.dataset.hsAction = 'session';
|
|
239
|
+
item.dataset.hsSession = row.id;
|
|
240
|
+
item.title = row.dir ? `${row.name} (${row.dir})` : row.name;
|
|
241
|
+
|
|
242
|
+
if (row.index < 9) {
|
|
243
|
+
const number = document.createElement('span');
|
|
244
|
+
number.className = 'home-sessions-number';
|
|
245
|
+
number.setAttribute('data-i18n-skip', '');
|
|
246
|
+
number.textContent = String(row.index + 1);
|
|
247
|
+
item.appendChild(number);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const dot = document.createElement('span');
|
|
251
|
+
dot.className = 'home-sessions-dot home-sessions-dot--' + row.state;
|
|
252
|
+
dot.setAttribute('aria-hidden', 'true');
|
|
253
|
+
item.appendChild(dot);
|
|
254
|
+
|
|
255
|
+
const body = document.createElement('span');
|
|
256
|
+
body.className = 'home-sessions-row-body';
|
|
257
|
+
|
|
258
|
+
const line1 = document.createElement('span');
|
|
259
|
+
line1.className = 'home-sessions-row-title';
|
|
260
|
+
if (row.modeBadge) {
|
|
261
|
+
const badge = document.createElement('span');
|
|
262
|
+
badge.className = `home-sessions-mode ${row.mode}`;
|
|
263
|
+
badge.setAttribute('data-i18n-skip', '');
|
|
264
|
+
badge.textContent = row.modeBadge;
|
|
265
|
+
line1.appendChild(badge);
|
|
266
|
+
}
|
|
267
|
+
const name = document.createElement('span');
|
|
268
|
+
// .session-name is in the i18n skip list: a session name is user content.
|
|
269
|
+
name.className = 'session-name';
|
|
270
|
+
name.textContent = row.name;
|
|
271
|
+
line1.appendChild(name);
|
|
272
|
+
body.appendChild(line1);
|
|
273
|
+
|
|
274
|
+
const line2 = document.createElement('span');
|
|
275
|
+
line2.className = 'home-sessions-row-sub';
|
|
276
|
+
line2.setAttribute('data-i18n-skip', '');
|
|
277
|
+
line2.textContent = row.caseName || row.dir || row.mode;
|
|
278
|
+
body.appendChild(line2);
|
|
279
|
+
|
|
280
|
+
item.appendChild(body);
|
|
281
|
+
|
|
282
|
+
const pill = document.createElement('span');
|
|
283
|
+
pill.className = 'home-sessions-pill home-sessions-pill--' + row.state;
|
|
284
|
+
// Skipped by i18n on purpose: generic single words ("idle", "done", "error")
|
|
285
|
+
// that collide with state strings on other surfaces.
|
|
286
|
+
pill.setAttribute('data-i18n-skip', '');
|
|
287
|
+
pill.textContent = row.pill;
|
|
288
|
+
item.appendChild(pill);
|
|
289
|
+
|
|
290
|
+
return item;
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
/** A saved dashboard, listed after the sessions exactly as in the tab strip. */
|
|
294
|
+
_buildHomeSessionsWebviewRow(webview) {
|
|
295
|
+
const item = document.createElement('button');
|
|
296
|
+
item.type = 'button';
|
|
297
|
+
item.className = 'home-sessions-row home-sessions-row--web';
|
|
298
|
+
item.dataset.hsAction = 'webview';
|
|
299
|
+
item.dataset.hsWebview = webview.id;
|
|
300
|
+
item.title = webview.url || webview.name;
|
|
301
|
+
|
|
302
|
+
const dot = document.createElement('span');
|
|
303
|
+
dot.className = 'home-sessions-dot home-sessions-dot--web';
|
|
304
|
+
dot.setAttribute('aria-hidden', 'true');
|
|
305
|
+
item.appendChild(dot);
|
|
306
|
+
|
|
307
|
+
const body = document.createElement('span');
|
|
308
|
+
body.className = 'home-sessions-row-body';
|
|
309
|
+
|
|
310
|
+
const title = document.createElement('span');
|
|
311
|
+
title.className = 'home-sessions-row-title';
|
|
312
|
+
const name = document.createElement('span');
|
|
313
|
+
// A dashboard name is user content.
|
|
314
|
+
name.className = 'case-name';
|
|
315
|
+
name.textContent = webview.name;
|
|
316
|
+
title.appendChild(name);
|
|
317
|
+
body.appendChild(title);
|
|
318
|
+
|
|
319
|
+
const sub = document.createElement('span');
|
|
320
|
+
sub.className = 'home-sessions-row-sub';
|
|
321
|
+
sub.setAttribute('data-i18n-skip', '');
|
|
322
|
+
sub.textContent = webview.url || '';
|
|
323
|
+
body.appendChild(sub);
|
|
324
|
+
|
|
325
|
+
item.appendChild(body);
|
|
326
|
+
|
|
327
|
+
const pill = document.createElement('span');
|
|
328
|
+
pill.className = 'home-sessions-pill home-sessions-pill--web';
|
|
329
|
+
pill.setAttribute('data-i18n-skip', '');
|
|
330
|
+
pill.textContent = 'web';
|
|
331
|
+
item.appendChild(pill);
|
|
332
|
+
|
|
333
|
+
return item;
|
|
334
|
+
},
|
|
335
|
+
});
|
|
Binary file
|
|
Binary file
|