opencode-session-turns 0.1.3 → 0.1.5

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.
@@ -1,5 +1,5 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const a=require("solid-js/web"),A=require("@opencode/plugin/gui"),i=require("solid-js");var Lt=a.template("<div class=sr-status>"),It=a.template('<span class="sr-title sr-progress">跳转中'),Mt=a.template("<div class=sr-rail><div class=sr-head><button type=button class=sr-close>×</button></div><div class=sr-list>"),Pt=a.template("<span class=sr-title>会话轮次"),Dt=a.template("<div class=sr-note>"),jt=a.template("<button type=button class=sr-turn><span class=sr-text><span class=sr-num>#</span><span class=sr-time></span></span><span class=sr-dash>");const G="v12",wt="message-",ht="session-turns:diag",Y="session-turns:open",qt=40,Rt=200,Ot=50,Jt=120,V=45e3,Nt=4e3,zt=300,Ft=800;let xt=0,Z="",X="";const gt=o=>{const n=String(o?.text??"").split(`
2
- `).map(g=>g.trim()).find(g=>g.length>0);if(n)return n;const f=o?.files?.length??0;return f>0?`[附件 ×${f}]`:"(空消息)"},mt=o=>String(o).padStart(2,"0"),Ht=o=>{const n=new Date(o??0);return`${mt(n.getHours())}:${mt(n.getMinutes())}`},bt=o=>new Promise(n=>setTimeout(n,o)),vt=()=>document.querySelector('[data-slot="session-timeline-scroll"] > .scroll-view__viewport')??document.querySelector('[data-slot="session-timeline-scroll"] [data-scrollable]')??(()=>{let n=document.querySelector("[data-message-id]")?.parentElement;for(;n;){const f=getComputedStyle(n);if(/(auto|scroll)/.test(f.overflowY)&&n.scrollHeight>n.clientHeight+1)return n;n=n.parentElement}})(),$=o=>document.getElementById(`${wt}${o}`)??[...document.querySelectorAll("[data-message-id]")].find(n=>n.getAttribute("data-message-id")===o),W=o=>{const n=vt(),f=[...document.querySelectorAll("[data-message-id]")],g=f[0]?.getAttribute("data-message-id")??"",j=X?` · 目标 ${$(X)?"在":"不在"} DOM`:"",C=g?` · 行ID ${g.slice(0,20)}`:"";let E="(空)";try{E=o.router.path()||"(空)"}catch{}return`轮次 ${xt} · 容器 ${n?"OK":"缺失"} · DOM ${f.length} 行${j}${C} · 路由 ${E} · hash ${window.location.hash||"(空)"}${Z?` · ${Z}`:""}`},K=(o,n)=>{n.scrollIntoView({block:"start"});const f=o.querySelector("[data-session-title]"),g=f instanceof HTMLElement?f.offsetHeight:0;g&&(o.scrollTop-=g+12)},Ut=o=>{let n="";try{n=o.router.path()||""}catch{}return n.startsWith("/")?n:""},Bt=o=>{const n=document.createElement("a");n.setAttribute("href",o),n.setAttribute("replace",""),n.setAttribute("noscroll",""),n.style.display="none",document.body.appendChild(n);try{n.dispatchEvent(new MouseEvent("click",{bubbles:!0,cancelable:!0,button:0,view:window}))}finally{n.remove()}},Gt=`
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const o=require("solid-js/web"),L=require("@opencode/plugin/gui"),i=require("solid-js");var Lt=o.template("<div class=sr-status>"),It=o.template('<span class="sr-title sr-progress">跳转中'),Mt=o.template("<div class=sr-rail><div class=sr-head><button type=button class=sr-close>×</button></div><div class=sr-list>"),Pt=o.template("<span class=sr-title>会话轮次"),jt=o.template("<div class=sr-note>"),Dt=o.template("<button type=button class=sr-turn><span class=sr-text><span class=sr-num>#</span><span class=sr-time></span></span><span class=sr-dash>");const G="v12",Y="message-",gt="session-turns:diag",Z="session-turns:open",qt=40,Rt=200,Ot=50,Jt=120,V=45e3,Nt=4e3,zt=300,Bt=800;let xt=0,X="",Q="";const mt=a=>{const n=String(a?.text??"").split(`
2
+ `).map(b=>b.trim()).find(b=>b.length>0);if(n)return n;const u=a?.files?.length??0;return u>0?`[附件 ×${u}]`:"(空消息)"},bt=a=>String(a).padStart(2,"0"),Ft=a=>{const n=new Date(a??0);return`${bt(n.getHours())}:${bt(n.getMinutes())}`},wt=a=>new Promise(n=>setTimeout(n,a)),vt=()=>document.querySelector('[data-slot="session-timeline-scroll"] > .scroll-view__viewport')??document.querySelector('[data-slot="session-timeline-scroll"] [data-scrollable]')??(()=>{let n=document.querySelector("[data-message-id]")?.parentElement;for(;n;){const u=getComputedStyle(n);if(/(auto|scroll)/.test(u.overflowY)&&n.scrollHeight>n.clientHeight+1)return n;n=n.parentElement}})(),E=a=>document.getElementById(`${Y}${a}`)??[...document.querySelectorAll("[data-message-id]")].find(n=>n.getAttribute("data-message-id")===a),W=a=>{const n=vt(),u=[...document.querySelectorAll("[data-message-id]")],b=u[0]?.getAttribute("data-message-id")??"",O=Q?` · 目标 ${E(Q)?"在":"不在"} DOM`:"",I=b?` · 行ID ${b.slice(0,20)}`:"";let _="(空)";try{_=a.router.path()||"(空)"}catch{}return`轮次 ${xt} · 容器 ${n?"OK":"缺失"} · DOM ${u.length} 行${O}${I} · 路由 ${_} · hash ${window.location.hash||"(空)"}${X?` · ${X}`:""}`},K=(a,n)=>{n.scrollIntoView({block:"start"});const u=a.querySelector("[data-session-title]"),b=u instanceof HTMLElement?u.offsetHeight:0;b&&(a.scrollTop-=b+12)},Ht=a=>{let n="";try{n=a.router.path()||""}catch{}return n.startsWith("/")?n:""},Ut=a=>{const n=document.createElement("a");n.setAttribute("href",a),n.setAttribute("replace",""),n.setAttribute("noscroll",""),n.style.display="none",document.body.appendChild(n);try{n.dispatchEvent(new MouseEvent("click",{bubbles:!0,cancelable:!0,button:0,view:window}))}finally{n.remove()}},Gt=`
3
3
  .sr-rail {
4
4
  position: fixed;
5
5
  top: 50%;
@@ -215,4 +215,4 @@
215
215
  white-space: pre-wrap;
216
216
  max-width: min(720px, 90vw);
217
217
  }
218
- `;function Vt(o){const n=A.usePlugin(),[f,g]=i.createSignal([]),[j,C]=i.createSignal(""),[E,S]=i.createSignal(!1),[yt,kt]=i.createSignal(""),[Q,tt]=i.createSignal(""),[x,q]=i.createSignal(!1),[et,L]=i.createSignal(0),[rt,R]=i.createSignal(""),v=new Map;let O=0,I=!1,M=!1,J="",_;const P=()=>o.session()?.id,N=()=>{const t=[...v.values()].sort((r,l)=>r.created-l.created),e=i.untrack(f);e.length===t.length&&e.every((r,l)=>r===t[l])||(g(t),xt=t.length)},nt=t=>{const e=v.get(t.id);e&&e.created===t.time&&e.text===t.text||v.set(t.id,{id:t.id,created:t.time,text:t.text})},St=async(t,e,r)=>{const l=r?{sessionID:t,type:"user",limit:e,cursor:r}:{sessionID:t,type:"user",limit:e,order:"desc"};return await n.client.message.list(l)},z=async(t,e)=>{const r=++O;I=!0;let l="";try{let s;const c=e==="full"?qt:1,d=e==="full"?Rt:Ot;let p=e!=="full";for(let b=0;b<c;b++){let m;try{m=await St(t,d,s)}catch(h){l=String(h?.message??h);break}if(r!==O)return;p||(v.clear(),p=!0);let u=0;for(const h of m?.data??[])v.has(h.id)||(nt({id:h.id,time:h.time?.created??0,text:gt(h)}),u++);N();const k=m?.cursor?.next;if(!k||u===0)break;s=k}}finally{I=!1,J=t,Z=l,kt(v.size===0?l||"暂无用户消息":""),M&&(M=!1,st())}},st=()=>{_||(_=setTimeout(()=>{_=void 0;const t=i.untrack(P);t&&z(t,"light")},300))},at=t=>{if(!(!t||t!==i.untrack(P))){if(I){M=!0;return}st()}},$t=t=>{const e=t?.type;if(e==="message.updated"){const r=t?.data??{},l=r.info;l?.role==="user"&&l?.id&&(nt({id:l.id,time:l.time?.created??0,text:gt(l)}),N()),at(r.sessionID)}else if(e==="message.removed"){const r=t?.data??{};r.messageID&&v.delete(r.messageID)&&N(),at(r.sessionID)}else if(e==="location.shutdown"){const r=i.untrack(P);r&&J===r&&z(r,"full")}};i.onMount(()=>{let t=!1,e=0,r;(async()=>{for(;!t;){const c=n.client.event.subscribe()[Symbol.asyncIterator]();try{for(;!t;){const d=await c.next();if(d.done)break;e=0,$t(d.value)}}catch{}try{await c.return?.()}catch{}if(t)break;e++,await bt(Math.min(2e3*e,15e3))}})(),i.onCleanup(()=>{t=!0,clearTimeout(r),_&&clearTimeout(_)})}),i.createEffect(()=>{const t=P();t&&i.untrack(()=>{J!==t&&(O++,I=!1,M=!1,v.clear(),g([]),C(""),H="",z(t,"full"))})});let D;const T=()=>(D?.isConnected||(D=vt()),D),Et=()=>{const t=T();if(!t)return;const e=t.querySelectorAll("[data-message-id]");if(e.length===0)return;const r=t.getBoundingClientRect().top+40;for(const l of e)if(l.getBoundingClientRect().bottom>=r){const s=l.getAttribute("data-message-id");s&&C(s);break}};let F=!1;const ot=()=>{F||(F=!0,requestAnimationFrame(()=>{F=!1,Et()}))};i.onMount(()=>{const t=()=>ot();document.addEventListener("scroll",t,!0);const e=setInterval(ot,1500);i.onCleanup(()=>{document.removeEventListener("scroll",t,!0),clearInterval(e)})});let it=0;const w=(t,e=2600)=>{const r=++it;tt(t),setTimeout(()=>{r===it&&tt("")},e)};i.onMount(()=>{const t=r=>{if(r.key==="Escape"){if(i.untrack(x)){pt(),r.stopPropagation();return}i.untrack(E)&&(S(!1),r.stopPropagation())}},e=r=>{if(!i.untrack(E))return;(r.composedPath?.()??[]).some(c=>c instanceof Element&&c.closest?.(".sr-rail"))||S(!1)};window.addEventListener("keydown",t,!0),document.addEventListener("pointerdown",e,!0),i.onCleanup(()=>{window.removeEventListener("keydown",t,!0),document.removeEventListener("pointerdown",e,!0)})}),i.onMount(()=>{const t=()=>w(`${G} · ${W(n)}`),e=()=>S(r=>!r);window.addEventListener(ht,t),window.addEventListener(Y,e),i.onCleanup(()=>{window.removeEventListener(ht,t),window.removeEventListener(Y,e)}),w(`会话轮次 ${G} 已加载`)});let H="",y=0,lt=0,ct="";const U=t=>{try{const e=t.querySelector('[data-slot="user-message-text"]')??t.querySelector('[data-slot="user-message-body"]')??t;e.classList.remove("sr-flash-ring"),e.offsetWidth,e.classList.add("sr-flash-ring"),setTimeout(()=>e.classList.remove("sr-flash-ring"),1900)}catch{}},_t=()=>{for(const t of f())if($(t.id))return t},dt=(t,e)=>{Bt(`${t}#${wt}${e}`),H=e},ut=()=>{const e=T()?.querySelectorAll("[data-message-id]")??[];if(e.length===0)return"0";const r=e[0].getAttribute("data-message-id")??"",l=e[e.length-1].getAttribute("data-message-id")??"";return`${e.length}:${r}:${l}`},Tt=(t,e,r)=>new Promise(l=>{let s=!1,c,d,p;const b=Date.now(),m=ut(),u=B=>{if(!s){s=!0;try{c?.disconnect()}catch{}clearInterval(d),clearTimeout(p),l(B??null)}},k=B=>{if(e!==y)return u(null);const ft=$(t);if(ft)return u(ft);B&&Date.now()-b>zt&&ut()!==m&&u(null)},h=$(t);if(h)return u(h);c=new MutationObserver(()=>k(!1)),c.observe(document.documentElement,{childList:!0,subtree:!0}),d=setInterval(()=>k(!0),120),p=setTimeout(()=>u($(t)),Math.max(400,r))}),pt=()=>{y++,q(!1),L(0),R(""),w("已取消跳转")},At=async t=>{const e=++y;X=t.id;const r=Date.now();if(t.id===ct&&r-lt<Ft){const d=$(t.id),p=T();if(d&&p){K(p,d),U(d);return}}lt=r,ct=t.id;const l=T(),s=$(t.id);if(s&&l){K(l,s),U(s),w("已定位");return}const c=Ut(n);if(!c){w(`无法读取当前路由,无法定位 · ${W(n)}`);return}q(!0),R(t.id),L(0),S(!0),w("正在定位…",V);try{const d=Date.now()+V;for(let p=0;p<Jt&&Date.now()<d;p++){if(e!==y)return;if(L(p+1),w(`跳转中 · 第 ${p+1} 页`,V),H===t.id){const u=_t()??f()[0];u&&(dt(c,u.id),await bt(120))}if(e!==y)return;dt(c,t.id);const b=Math.min(Nt,d-Date.now()),m=await Tt(t.id,e,b);if(e!==y)return;if(m){const u=T()??l;u&&K(u,m),U(m),w("已定位");return}}e===y&&w(`未能定位 · ${W(n)}`)}finally{e===y&&(q(!1),L(0),R(""))}};i.createEffect(()=>{x()&&rt()&&requestAnimationFrame(()=>{try{document.querySelector('.sr-list .sr-turn[data-busy="true"]')?.scrollIntoView({block:"nearest"})}catch{}})});const Ct=[a.createComponent(i.Show,{get when(){return Q()},get children(){var t=Lt();return a.insert(t,Q),t}}),(()=>{var t=Mt(),e=t.firstChild,r=e.firstChild,l=e.nextSibling;return t.addEventListener("mouseleave",()=>{x()||S(!1)}),t.addEventListener("mouseenter",()=>S(!0)),a.insert(e,a.createComponent(i.Show,{get when(){return x()},get fallback(){return(()=>{var s=Pt();return s.firstChild,a.insert(s,(()=>{var c=a.memo(()=>f().length>0);return()=>c()?` · ${f().length}`:""})(),null),s})()},get children(){var s=It();return s.firstChild,a.insert(s,(()=>{var c=a.memo(()=>et()>0);return()=>c()?` · 第 ${et()} 页`:""})(),null),s}}),r),r.$$click=()=>{x()?pt():S(!1)},a.insert(l,a.createComponent(i.Show,{get when(){return f().length>0},get fallback(){return(()=>{var s=Dt();return a.insert(s,()=>yt()||`… ${G}`),s})()},get children(){return a.createComponent(i.For,{get each(){return f()},children:(s,c)=>(()=>{var d=jt(),p=d.firstChild,b=p.firstChild;b.firstChild;var m=b.nextSibling;return d.$$click=()=>{At(s)},a.insert(b,()=>c()+1,null),a.insert(m,()=>Ht(s.created)),a.insert(p,()=>s.text,null),a.effect(u=>{var k=j()===s.id?"true":"false",h=x()&&rt()===s.id?"true":"false";return k!==u.e&&a.setAttribute(d,"data-active",u.e=k),h!==u.t&&a.setAttribute(d,"data-busy",u.t=h),u},{e:void 0,t:void 0}),d})()})}})),a.effect(s=>{var c=E()?"true":"false",d=x()?"true":"false",p=x()?"取消跳转 (Esc)":"关闭 (Esc)";return c!==s.e&&a.setAttribute(t,"data-open",s.e=c),d!==s.t&&a.setAttribute(t,"data-jumping",s.t=d),p!==s.a&&a.setAttribute(r,"title",s.a=p),s},{e:void 0,t:void 0,a:void 0}),t})()];return a.createComponent(a.Portal,{get mount(){return document.body},children:Ct})}const Wt=A.Plugin.define({i18n:{en:{"command.jump":"Session turns: show / jump","command.jump.description":"Reveal the turn rail; click a turn to jump to that message"}},setup(o){o.add(A.Style,Gt),o.add(A.Slot,{at:"window.bottom",render:()=>a.createComponent(i.Show,{get when(){return o.sessions.current()},get children(){return a.createComponent(Vt,{session:()=>o.sessions.current()})}})}),o.add(A.Command,()=>({id:"jump",title:o.t("command.jump"),description:o.t("command.jump.description"),group:o.t("command.jump"),section:"navigation",bind:"mod+alt+r",slash:{name:"turns"},run:()=>{window.dispatchEvent(new Event(Y))}}))}});a.delegateEvents(["click"]);exports.default=Wt;
218
+ `;function Vt(a){const n=L.usePlugin(),[u,b]=i.createSignal([]),[O,I]=i.createSignal(""),[_,$]=i.createSignal(!1),[yt,kt]=i.createSignal(""),[tt,et]=i.createSignal(""),[v,M]=i.createSignal(!1),[rt,T]=i.createSignal(0),[nt,P]=i.createSignal(""),y=new Map;let J=0,j=!1,D=!1,N="",A;const k=()=>a.session()?.id,z=()=>{const t=[...y.values()].sort((r,l)=>r.created-l.created),e=i.untrack(u);e.length===t.length&&e.every((r,l)=>r===t[l])||(b(t),xt=t.length)},st=t=>{const e=y.get(t.id);e&&e.created===t.time&&e.text===t.text||y.set(t.id,{id:t.id,created:t.time,text:t.text})},St=async(t,e,r)=>{const l=r?{sessionID:t,type:"user",limit:e,cursor:r}:{sessionID:t,type:"user",limit:e,order:"desc"};return await n.client.message.list(l)},B=async(t,e)=>{const r=++J;j=!0;let l="";try{let s;const c=e==="full"?qt:1,d=e==="full"?Rt:Ot;let p=e!=="full";for(let h=0;h<c;h++){let x;try{x=await St(t,d,s)}catch(m){l=String(m?.message??m);break}if(r!==J)return;p||(y.clear(),p=!0);let f=0;for(const m of x?.data??[])y.has(m.id)||(st({id:m.id,time:m.time?.created??0,text:mt(m)}),f++);z();const g=x?.cursor?.next;if(!g||f===0)break;s=g}}finally{j=!1,N=t,X=l,kt(y.size===0?l||"暂无用户消息":""),D&&(D=!1,ot())}},ot=()=>{A||(A=setTimeout(()=>{A=void 0;const t=i.untrack(k);t&&B(t,"light")},300))},at=t=>{if(!(!t||t!==i.untrack(k))){if(j){D=!0;return}ot()}},$t=t=>{const e=t?.type;if(e==="message.updated"){const r=t?.data??{},l=r.info;l?.role==="user"&&l?.id&&(st({id:l.id,time:l.time?.created??0,text:mt(l)}),z()),at(r.sessionID)}else if(e==="message.removed"){const r=t?.data??{};r.messageID&&y.delete(r.messageID)&&z(),at(r.sessionID)}else if(e==="location.shutdown"){const r=i.untrack(k);r&&N===r&&B(r,"full")}};i.onMount(()=>{let t=!1,e=0,r;(async()=>{for(;!t;){const c=n.client.event.subscribe()[Symbol.asyncIterator]();try{for(;!t;){const d=await c.next();if(d.done)break;e=0,$t(d.value)}}catch{}try{await c.return?.()}catch{}if(t)break;e++,await wt(Math.min(2e3*e,15e3))}})(),i.onCleanup(()=>{t=!0,clearTimeout(r),A&&clearTimeout(A)})}),i.createEffect(()=>{const t=k();t&&i.untrack(()=>{N!==t&&(J++,j=!1,D=!1,y.clear(),b([]),I(""),H="",B(t,"full"))})});let q;const C=()=>(q?.isConnected||(q=vt()),q),Et=()=>{const t=C();if(!t)return;const e=t.querySelectorAll("[data-message-id]");if(e.length===0)return;const r=t.getBoundingClientRect().top+40;for(const l of e)if(l.getBoundingClientRect().bottom>=r){const s=l.getAttribute("data-message-id");s&&I(s);break}};let F=!1;const it=()=>{F||(F=!0,requestAnimationFrame(()=>{F=!1,Et()}))};i.onMount(()=>{const t=()=>it();document.addEventListener("scroll",t,!0);const e=setInterval(it,1500);i.onCleanup(()=>{document.removeEventListener("scroll",t,!0),clearInterval(e)})});let lt=0;const w=(t,e=2600)=>{const r=++lt;et(t),setTimeout(()=>{r===lt&&et("")},e)};i.onMount(()=>{const t=r=>{if(r.key==="Escape"){if(i.untrack(v)){ft(),r.stopPropagation();return}i.untrack(_)&&($(!1),r.stopPropagation())}},e=r=>{if(!i.untrack(_))return;(r.composedPath?.()??[]).some(c=>c instanceof Element&&c.closest?.(".sr-rail"))||$(!1)};window.addEventListener("keydown",t,!0),document.addEventListener("pointerdown",e,!0),i.onCleanup(()=>{window.removeEventListener("keydown",t,!0),document.removeEventListener("pointerdown",e,!0)})}),i.onMount(()=>{const t=()=>w(`${G} · ${W(n)}`),e=()=>$(r=>!r);window.addEventListener(gt,t),window.addEventListener(Z,e),i.onCleanup(()=>{window.removeEventListener(gt,t),window.removeEventListener(Z,e)}),w(`会话轮次 ${G} 已加载`)});let H="",S=0,ct=0,dt="";const U=t=>{try{const e=t.querySelector('[data-slot="user-message-text"]')??t.querySelector('[data-slot="user-message-body"]')??t;e.classList.remove("sr-flash-ring"),e.offsetWidth,e.classList.add("sr-flash-ring"),setTimeout(()=>e.classList.remove("sr-flash-ring"),1900)}catch{}},_t=()=>{for(const t of u())if(E(t.id))return t},ut=(t,e)=>{Ut(`${t}#${Y}${e}`),H=e},pt=()=>{const e=C()?.querySelectorAll("[data-message-id]")??[];if(e.length===0)return"0";const r=e[0].getAttribute("data-message-id")??"",l=e[e.length-1].getAttribute("data-message-id")??"";return`${e.length}:${r}:${l}`},Tt=(t,e,r)=>new Promise(l=>{let s=!1,c,d,p;const h=Date.now(),x=pt(),f=R=>{if(!s){s=!0;try{c?.disconnect()}catch{}clearInterval(d),clearTimeout(p),l(R??null)}},g=R=>{if(e!==S)return f(null);const ht=R?E(t):document.getElementById(`${Y}${t}`);if(ht)return f(ht);R&&Date.now()-h>zt&&pt()!==x&&f(null)},m=E(t);if(m)return f(m);c=new MutationObserver(()=>g(!1)),c.observe(document.documentElement,{childList:!0,subtree:!0}),d=setInterval(()=>g(!0),120),p=setTimeout(()=>f(E(t)),Math.max(400,r))}),ft=()=>{S++,M(!1),T(0),P(""),w("已取消跳转")},At=async t=>{const e=++S;M(!1),T(0),P(""),Q=t.id;const r=Date.now();if(t.id===dt&&r-ct<Bt){const p=E(t.id),h=C();if(p&&h){K(h,p),U(p);return}}ct=r,dt=t.id;const l=C(),s=E(t.id);if(s&&l){K(l,s),U(s),w("已定位");return}const c=Ht(n);if(!c){w(`无法读取当前路由,无法定位 · ${W(n)}`);return}const d=k();M(!0),P(t.id),T(0),$(!0),w("正在定位…",V);try{const p=Date.now()+V;for(let h=0;h<Jt&&Date.now()<p;h++){if(e!==S||k()!==d)return;if(T(h+1),w(`跳转中 · 第 ${h+1} 页`,V),H===t.id){const g=_t()??u()[0];g&&(ut(c,g.id),await wt(120))}if(e!==S||k()!==d)return;ut(c,t.id);const x=Math.min(Nt,p-Date.now()),f=await Tt(t.id,e,x);if(e!==S||k()!==d)return;if(f){const g=C()??l;g&&K(g,f),U(f),w("已定位");return}}e===S&&w(`未能定位 · ${W(n)}`)}finally{e===S&&(M(!1),T(0),P(""))}};i.createEffect(()=>{v()&&nt()&&requestAnimationFrame(()=>{try{document.querySelector('.sr-list .sr-turn[data-busy="true"]')?.scrollIntoView({block:"nearest"})}catch{}})});const Ct=[o.createComponent(i.Show,{get when(){return tt()},get children(){var t=Lt();return o.insert(t,tt),t}}),(()=>{var t=Mt(),e=t.firstChild,r=e.firstChild,l=e.nextSibling;return t.addEventListener("mouseleave",()=>{v()||$(!1)}),t.addEventListener("mouseenter",()=>$(!0)),o.insert(e,o.createComponent(i.Show,{get when(){return v()},get fallback(){return(()=>{var s=Pt();return s.firstChild,o.insert(s,(()=>{var c=o.memo(()=>u().length>0);return()=>c()?` · ${u().length}`:""})(),null),s})()},get children(){var s=It();return s.firstChild,o.insert(s,(()=>{var c=o.memo(()=>rt()>0);return()=>c()?` · 第 ${rt()} 页`:""})(),null),s}}),r),r.$$click=()=>{v()?ft():$(!1)},o.insert(l,o.createComponent(i.Show,{get when(){return u().length>0},get fallback(){return(()=>{var s=jt();return o.insert(s,()=>yt()||`… ${G}`),s})()},get children(){return o.createComponent(i.For,{get each(){return u()},children:(s,c)=>(()=>{var d=Dt(),p=d.firstChild,h=p.firstChild;h.firstChild;var x=h.nextSibling;return d.$$click=()=>{At(s)},o.insert(h,()=>c()+1,null),o.insert(x,()=>Ft(s.created)),o.insert(p,()=>s.text,null),o.effect(f=>{var g=O()===s.id?"true":"false",m=v()&&nt()===s.id?"true":"false";return g!==f.e&&o.setAttribute(d,"data-active",f.e=g),m!==f.t&&o.setAttribute(d,"data-busy",f.t=m),f},{e:void 0,t:void 0}),d})()})}})),o.effect(s=>{var c=_()?"true":"false",d=v()?"true":"false",p=v()?"取消跳转 (Esc)":"关闭 (Esc)";return c!==s.e&&o.setAttribute(t,"data-open",s.e=c),d!==s.t&&o.setAttribute(t,"data-jumping",s.t=d),p!==s.a&&o.setAttribute(r,"title",s.a=p),s},{e:void 0,t:void 0,a:void 0}),t})()];return o.createComponent(o.Portal,{get mount(){return document.body},children:Ct})}const Wt=L.Plugin.define({i18n:{en:{"command.jump":"Session turns: show / jump","command.jump.description":"Reveal the turn rail; click a turn to jump to that message"}},setup(a){a.add(L.Style,Gt),a.add(L.Slot,{at:"window.bottom",render:()=>o.createComponent(i.Show,{get when(){return a.sessions.current()},get children(){return o.createComponent(Vt,{session:()=>a.sessions.current()})}})}),a.add(L.Command,()=>({id:"jump",title:a.t("command.jump"),description:a.t("command.jump.description"),group:a.t("command.jump"),section:"navigation",bind:"mod+alt+r",slash:{name:"turns"},run:()=>{window.dispatchEvent(new Event(Z))}}))}});o.delegateEvents(["click"]);exports.default=Wt;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-session-turns",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "OpenCode Desktop 插件:在会话右缘列出每一轮用户提问,点击即可跳转到对应消息。A right-edge rail of user turns; click one to jump to that message.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/gui.tsx CHANGED
@@ -745,11 +745,11 @@ function Rail(props) {
745
745
  }
746
746
  const check = (full) => {
747
747
  if (token !== jumpToken) return finish(null)
748
- const row = findRow(messageID)
748
+ // Observer callbacks use the cheap id lookup; only the poll pays for the
749
+ // data-attribute fallback (a full scan) and the row fingerprint.
750
+ const row = full ? findRow(messageID) : document.getElementById(`${HASH_PREFIX}${messageID}`)
749
751
  if (row) return finish(row)
750
752
  // Let the click settle before trusting a DOM change as "a page arrived".
751
- // Only the poll evaluates the row fingerprint: streaming output fires
752
- // the observer constantly and a full query on every mutation is wasted.
753
753
  if (full && Date.now() - started > JUMP_SETTLE_MS && rowSignature() !== baseline) finish(null)
754
754
  }
755
755
  const existing = findRow(messageID)
@@ -770,6 +770,12 @@ function Rail(props) {
770
770
 
771
771
  const jump = async (turn) => {
772
772
  const token = ++jumpToken
773
+ // A new click always owns the progress UI. This also clears a previous
774
+ // jump's state when the new target takes the mounted fast path, where the
775
+ // superseded loop's finally no longer touches these signals.
776
+ setJumping(false)
777
+ setJumpPage(0)
778
+ setBusyID("")
773
779
  lastJumpID = turn.id
774
780
  const now = Date.now()
775
781
 
@@ -810,6 +816,7 @@ function Rail(props) {
810
816
  // another seek/page. A round ends as soon as rows appear instead of always
811
817
  // sleeping 6s. A newer click bumps jumpToken, which cancels this loop at
812
818
  // every await; Esc cancels it too.
819
+ const sessionAtJump = sessionID()
813
820
  setJumping(true)
814
821
  setBusyID(turn.id)
815
822
  setJumpPage(0)
@@ -819,7 +826,9 @@ function Rail(props) {
819
826
  try {
820
827
  const deadline = Date.now() + JUMP_TOTAL_MS
821
828
  for (let round = 0; round < JUMP_ROUNDS && Date.now() < deadline; round++) {
822
- if (token !== jumpToken) return
829
+ // Abandon the loop if the user switched sessions mid-jump: toggling the
830
+ // old route hash would navigate back to the previous session.
831
+ if (token !== jumpToken || sessionID() !== sessionAtJump) return
823
832
  setJumpPage(round + 1)
824
833
  flash(`跳转中 · 第 ${round + 1} 页`, JUMP_TOTAL_MS)
825
834
 
@@ -830,12 +839,12 @@ function Rail(props) {
830
839
  await sleep(120)
831
840
  }
832
841
  }
833
- if (token !== jumpToken) return
842
+ if (token !== jumpToken || sessionID() !== sessionAtJump) return
834
843
  navTo(path, turn.id)
835
844
 
836
845
  const remaining = Math.min(JUMP_ROW_TIMEOUT_MS, deadline - Date.now())
837
846
  const landed = await waitForRow(turn.id, token, remaining)
838
- if (token !== jumpToken) return
847
+ if (token !== jumpToken || sessionID() !== sessionAtJump) return
839
848
  if (landed) {
840
849
  const live = scroller() ?? sc
841
850
  if (live) scrollToRow(live, landed)