llmd 0.4.0 → 0.4.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/README.md CHANGED
@@ -10,7 +10,7 @@ Want to see what llmd can do? **Try it on its own documentation:**
10
10
 
11
11
  ```bash
12
12
  # Install llmd
13
- npm install -g llmd
13
+ pnpm install -g llmd
14
14
 
15
15
  # View the documentation
16
16
  llmd docs
@@ -79,18 +79,6 @@ bun test
79
79
  bun run build
80
80
  ```
81
81
 
82
- ## Tech Stack
83
-
84
- - **Runtime**: Bun
85
- - **Markdown**: marked (GFM support)
86
- - **Highlighting**: Shiki (VS Code themes)
87
- - **Server**: Node.js http + ws
88
- - **Bundler**: Bun's built-in bundler
89
-
90
- ## License
91
-
92
- MIT
93
-
94
82
  ## Contributing
95
83
 
96
84
  Issues and PRs welcome. This tool is intentionally minimal—new features should materially improve the "view markdown now" workflow.
package/dist/client.js CHANGED
@@ -1,4 +1,4 @@
1
- var R=()=>{let C=document.querySelector(".admin-section"),J=document.querySelector(".admin-header");if(!(C&&J))return;if(localStorage.getItem("llmd-admin-collapsed")==="true")C.classList.add("collapsed");J.addEventListener("click",()=>{let j=C.classList.toggle("collapsed");localStorage.setItem("llmd-admin-collapsed",j.toString())})};var k=/\/$/,w=(C)=>{let J=C.querySelector(":scope > .dir-label");if(!J)return null;let q=Array.from(J.querySelectorAll("span"));if(q.length===0)return null;let j=q[q.length-1];if(!j.textContent)return null;return j.textContent.replace(k,"")},T=(C)=>{let J=[],q=C;while(q){let j=w(q);if(j)J.unshift(j);let K=q.parentElement;q=K?K.closest(".dir-item"):null}return J.length>0?J.join("/"):null},v=()=>{let C=[],J=document.querySelectorAll(".dir-item.collapsed");for(let q of Array.from(J)){let j=T(q);if(j)C.push(j)}localStorage.setItem("llmd-nav-collapsed",JSON.stringify(C))},I=()=>{try{let C=localStorage.getItem("llmd-nav-collapsed");if(!C)return;let J=JSON.parse(C),q=document.querySelectorAll(".dir-item");for(let j of Array.from(q)){let K=T(j);if(K&&J.includes(K))j.classList.add("collapsed")}}catch(C){console.error("[collapsible] Failed to restore state:",C)}},P=()=>{let C=document.querySelectorAll(".dir-label");for(let J of Array.from(C)){let q=J,j=document.createElement("span");j.className="dir-chevron",j.innerHTML='<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>',q.insertBefore(j,q.firstChild),q.style.cursor="pointer",q.addEventListener("click",(K)=>{K.preventDefault();let M=q.closest(".dir-item");if(M)M.classList.toggle("collapsed"),v()})}I()},f=()=>{let C=document.querySelector(".toc");if(!C)return;let J=C.querySelector("h3");if(!J)return;let q=document.createElement("span");q.className="toc-chevron",q.innerHTML='<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>',J.style.cursor="pointer",J.insertBefore(q,J.firstChild),J.addEventListener("click",()=>{C.classList.toggle("collapsed")})};if(typeof window<"u")window.addEventListener("DOMContentLoaded",()=>{P(),f()});var z=()=>{for(let C of Array.from(document.querySelectorAll("pre code"))){let J=C.parentElement;if(!J)continue;if(J.querySelector(".copy-button"))continue;let q=document.createElement("button");q.className="copy-button",q.textContent="Copy",q.setAttribute("aria-label","Copy code to clipboard"),q.addEventListener("click",async()=>{try{await navigator.clipboard.writeText(C.textContent||""),q.textContent="Copied!",q.classList.add("copied"),setTimeout(()=>{q.textContent="Copy",q.classList.remove("copied")},2000)}catch(j){console.error("Failed to copy:",j),q.textContent="Failed",setTimeout(()=>{q.textContent="Copy"},2000)}}),J.appendChild(q)}};if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",z);else z();var m=(C)=>{let q=`${window.location.protocol==="https:"?"wss:":"ws:"}//${window.location.host}/_ws`,j=null,K=null,M=()=>{j=new WebSocket(q),j.addEventListener("open",()=>{console.log("[llmd] Connected to file watcher"),j?.send(JSON.stringify({type:"watch",file:C}))}),j.addEventListener("message",(Q)=>{try{let Z=JSON.parse(Q.data);if(Z.type==="reload"&&Z.file===C)console.log(`[llmd] File changed: ${C}, reloading...`),window.location.reload()}catch(Z){console.error("[llmd] Failed to parse message:",Z)}}),j.addEventListener("close",()=>{console.log("[llmd] Disconnected from file watcher"),K=window.setTimeout(()=>{console.log("[llmd] Reconnecting..."),M()},2000)}),j.addEventListener("error",(Q)=>{console.error("[llmd] WebSocket error:",Q),j?.close()})};M(),window.addEventListener("beforeunload",()=>{if(K)clearTimeout(K);j?.close()})};window.connectFileWatcher=m;var F=[],G=null,B=null,y=new Map,A=()=>{if(!document.querySelector(".content"))return;u(),document.addEventListener("mouseup",b),document.addEventListener("touchend",b)},u=async()=>{try{let C=window.location.pathname.replace("/view/",""),J=await fetch(`/api/highlights/resource?path=${encodeURIComponent(C)}`);if(!J.ok)return;F=(await J.json()).highlights||[],x()}catch(C){console.error("[highlights] Failed to fetch highlights:",C)}},b=(C)=>{let J=C.target;if(G&&G.contains(J))return;let q=window.getSelection();if(!q||q.isCollapsed){L();return}let j=q.toString().trim();if(!j){L();return}let K=document.querySelector(".content");if(!K)return;let M=q.getRangeAt(0),Q=M.commonAncestorContainer,Z=Q.nodeType===Node.TEXT_NODE?Q.parentElement:Q;if(!(Z&&K.contains(Z))){L();return}B={text:j,range:M.cloneRange()},d(C)},d=(C)=>{if(!G)G=p(),document.body.appendChild(G);let{pageX:J,pageY:q}=C;G.style.left=`${J}px`,G.style.top=`${q-50}px`,G.style.display="block"},L=()=>{if(G)G.style.display="none";B=null},c=(C,J)=>{let q=y.get(C.id);if(q){q.remove(),y.delete(C.id);return}let j=document.createElement("div");j.className="highlight-notes-popup",j.style.cssText=`
1
+ var D=()=>{let C=document.querySelector(".admin-section"),j=document.querySelector(".admin-header");if(!(C&&j))return;if(localStorage.getItem("llmd-admin-collapsed")==="true")C.classList.add("collapsed");j.addEventListener("click",()=>{let J=C.classList.toggle("collapsed");localStorage.setItem("llmd-admin-collapsed",J.toString())})};var b=/\/$/,O=(C)=>{let j=C.querySelector(":scope > .dir-label");if(!j)return null;let q=Array.from(j.querySelectorAll("span"));if(q.length===0)return null;let J=q[q.length-1];if(!J.textContent)return null;return J.textContent.replace(b,"")},W=(C)=>{let j=[],q=C;while(q){let J=O(q);if(J)j.unshift(J);let K=q.parentElement;q=K?K.closest(".dir-item"):null}return j.length>0?j.join("/"):null},v=()=>{let C=[],j=document.querySelectorAll(".dir-item.collapsed");for(let q of Array.from(j)){let J=W(q);if(J)C.push(J)}localStorage.setItem("llmd-nav-collapsed",JSON.stringify(C))},I=()=>{try{let C=localStorage.getItem("llmd-nav-collapsed");if(!C)return;let j=JSON.parse(C),q=document.querySelectorAll(".dir-item");for(let J of Array.from(q)){let K=W(J);if(K&&j.includes(K))J.classList.add("collapsed")}}catch(C){console.error("[collapsible] Failed to restore state:",C)}},P=()=>{let C=document.querySelectorAll(".dir-label");for(let j of Array.from(C)){let q=j,J=document.createElement("span");J.className="dir-chevron",J.innerHTML='<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>',q.insertBefore(J,q.firstChild),q.style.cursor="pointer",q.addEventListener("click",(K)=>{K.preventDefault();let Q=q.closest(".dir-item");if(Q)Q.classList.toggle("collapsed"),v()})}I()},x=()=>{let C=document.querySelector(".toc");if(!C)return;let j=C.querySelector("h3");if(!j)return;let q=document.createElement("span");q.className="toc-chevron",q.innerHTML='<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"></polyline></svg>',j.style.cursor="pointer",j.insertBefore(q,j.firstChild),j.addEventListener("click",()=>{C.classList.toggle("collapsed")})};if(typeof window<"u")window.addEventListener("DOMContentLoaded",()=>{P(),x()});var E=()=>{for(let C of Array.from(document.querySelectorAll("pre code"))){let j=C.parentElement;if(!j)continue;if(j.querySelector(".copy-button"))continue;let q=document.createElement("button");q.className="copy-button",q.textContent="Copy",q.setAttribute("aria-label","Copy code to clipboard"),q.addEventListener("click",async()=>{try{await navigator.clipboard.writeText(C.textContent||""),q.textContent="Copied!",q.classList.add("copied"),setTimeout(()=>{q.textContent="Copy",q.classList.remove("copied")},2000)}catch(J){console.error("Failed to copy:",J),q.textContent="Failed",setTimeout(()=>{q.textContent="Copy"},2000)}}),j.appendChild(q)}};if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",E);else E();var k=(C)=>{let q=`${window.location.protocol==="https:"?"wss:":"ws:"}//${window.location.host}/_ws`,J=null,K=null,Q=()=>{J=new WebSocket(q),J.addEventListener("open",()=>{console.log("[llmd] Connected to file watcher"),J?.send(JSON.stringify({type:"watch",file:C}))}),J.addEventListener("message",(Z)=>{try{let $=JSON.parse(Z.data);if($.type==="reload"&&$.file===C)console.log(`[llmd] File changed: ${C}, reloading...`),window.location.reload()}catch($){console.error("[llmd] Failed to parse message:",$)}}),J.addEventListener("close",()=>{console.log("[llmd] Disconnected from file watcher"),K=window.setTimeout(()=>{console.log("[llmd] Reconnecting..."),Q()},2000)}),J.addEventListener("error",(Z)=>{console.error("[llmd] WebSocket error:",Z),J?.close()})};Q(),window.addEventListener("beforeunload",()=>{if(K)clearTimeout(K);J?.close()})};window.connectFileWatcher=k;var z=(C,j="smooth")=>{let q=document.querySelectorAll(`mark.llmd-highlight[data-highlight-id="${C}"]`);if(q.length===0)return!1;let J=q[0];if(!J)return!1;J.scrollIntoView({behavior:j,block:"center"});for(let K of Array.from(q))K.classList.add("highlight-flash"),setTimeout(()=>{K.classList.remove("highlight-flash")},1000);return!0};var N=[],_=null,B=null,L=new Map,R=()=>{if(!document.querySelector(".content"))return;f(),document.addEventListener("mouseup",A),document.addEventListener("touchend",A),u()},u=()=>{let C=window.location.hash;if(!C.startsWith("#highlight-"))return;let j=C.slice(11);if(!j)return;setTimeout(()=>{if(!z(j,"smooth"))console.warn(`[highlights] Could not find highlight with ID: ${j}`)},100)},f=async()=>{try{let C=window.location.pathname.replace("/view/",""),j=await fetch(`/api/highlights/resource?path=${encodeURIComponent(C)}`);if(!j.ok)return;N=(await j.json()).highlights||[],m()}catch(C){console.error("[highlights] Failed to fetch highlights:",C)}},m=()=>{let C=document.querySelector(".content");if(!C)return;let j=C.querySelectorAll("mark.llmd-highlight");for(let q of Array.from(j)){let J=q.getAttribute("data-highlight-id");if(!J)continue;let K=N.find((Q)=>Q.id===J);if(!K)continue;q.addEventListener("click",(Q)=>{Q.stopPropagation(),p(K,Q)})}},A=(C)=>{let j=C.target;if(_?.contains(j))return;let q=window.getSelection();if(!q||q.isCollapsed){y();return}let J=q.toString().trim();if(!J){y();return}let K=document.querySelector(".content");if(!K)return;let Q=q.getRangeAt(0),Z=(G)=>{let V=G.nodeType===Node.TEXT_NODE?G.parentElement:G;while(V&&V!==K){let F=V.tagName?.toLowerCase();if(F&&["p","h1","h2","h3","h4","h5","h6","li","blockquote","pre"].includes(F))return V;V=V.parentElement}return null},$=Z(Q.startContainer),U=Z(Q.endContainer);if($!==U){y();return}let Y=Q.commonAncestorContainer,X=Y.nodeType===Node.TEXT_NODE?Y.parentElement:Y;if(!(X&&K.contains(X))){y();return}B={text:J,range:Q.cloneRange()},c(C)},c=(C)=>{if(!_)_=d(),document.body.appendChild(_);let{pageX:j,pageY:q}=C;_.style.left=`${j}px`,_.style.top=`${q-50}px`,_.style.display="block"},y=()=>{if(_)_.style.display="none";B=null},p=(C,j)=>{let q=L.get(C.id);if(q){q.remove(),L.delete(C.id);return}let J=document.createElement("div");J.className="highlight-notes-popup",J.style.cssText=`
2
2
  position: absolute;
3
3
  background: var(--bg);
4
4
  border: 1px solid var(--border);
@@ -9,7 +9,7 @@ var R=()=>{let C=document.querySelector(".admin-section"),J=document.querySelect
9
9
  z-index: 10000;
10
10
  font-size: 13px;
11
11
  line-height: 1.5;
12
- `;let K=document.createElement("div");K.style.cssText="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;";let M=document.createElement("span");M.textContent="Highlight Notes",M.style.cssText="font-weight: 600; color: var(--fg);";let Q=document.createElement("button");Q.textContent="×",Q.style.cssText="background: none; border: none; color: var(--fg); cursor: pointer; padding: 0; font-size: 18px; line-height: 1; opacity: 0.7;",Q.addEventListener("click",(Y)=>{Y.stopPropagation(),j.remove(),y.delete(C.id)}),K.appendChild(M),K.appendChild(Q);let Z=document.createElement("div");Z.style.cssText="color: var(--fg); white-space: pre-wrap; word-wrap: break-word;",Z.textContent=C.notes||"(No notes)",j.appendChild(K),j.appendChild(Z),j.addEventListener("click",(Y)=>{Y.stopPropagation()}),j.style.left=`${J.pageX}px`,j.style.top=`${J.pageY+10}px`,document.body.appendChild(j),y.set(C.id,j);let $=(Y)=>{if(!j.contains(Y.target))j.remove(),y.delete(C.id),document.removeEventListener("click",$)};setTimeout(()=>{document.addEventListener("click",$)},0)},p=()=>{let C=document.createElement("div");C.className="highlight-popup",C.innerHTML=`
12
+ `;let K=document.createElement("div");K.style.cssText="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;";let Q=document.createElement("span");Q.textContent="Highlight Notes",Q.style.cssText="font-weight: 600; color: var(--fg);";let Z=document.createElement("button");Z.textContent="×",Z.style.cssText="background: none; border: none; color: var(--fg); cursor: pointer; padding: 0; font-size: 18px; line-height: 1; opacity: 0.7;",Z.addEventListener("click",(Y)=>{Y.stopPropagation(),J.remove(),L.delete(C.id)}),K.appendChild(Q),K.appendChild(Z);let $=document.createElement("div");$.style.cssText="color: var(--fg); white-space: pre-wrap; word-wrap: break-word;",$.textContent=C.notes||"(No notes)",J.appendChild(K),J.appendChild($),J.addEventListener("click",(Y)=>{Y.stopPropagation()}),J.style.left=`${j.pageX}px`,J.style.top=`${j.pageY+10}px`,document.body.appendChild(J),L.set(C.id,J);let U=(Y)=>{if(!J.contains(Y.target))J.remove(),L.delete(C.id),document.removeEventListener("click",U)};setTimeout(()=>{document.addEventListener("click",U)},0)},d=()=>{let C=document.createElement("div");C.className="highlight-popup",C.innerHTML=`
13
13
  <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
14
14
  <span style="font-size: 13px; font-weight: 500; color: var(--fg);">Add Highlight</span>
15
15
  <button class="highlight-close-btn" type="button" style="background: none; border: none; color: var(--fg); cursor: pointer; padding: 0; font-size: 18px; line-height: 1; opacity: 0.7;">&times;</button>
@@ -23,23 +23,23 @@ var R=()=>{let C=document.querySelector(".admin-section"),J=document.querySelect
23
23
  <button class="highlight-create-btn" type="button">
24
24
  Create Highlight
25
25
  </button>
26
- `;let J=C.querySelector(".highlight-create-btn");if(J)J.addEventListener("click",async(j)=>{j.stopPropagation(),await g()});let q=C.querySelector(".highlight-close-btn");if(q)q.addEventListener("click",(j)=>{j.stopPropagation(),L()});return C},g=async()=>{if(!B)return;try{let C=document.querySelector(".content");if(!C)return;let J=C.textContent||"",q=B.range,j=document.createRange();j.selectNodeContents(C),j.setEnd(q.startContainer,q.startOffset);let K=j.toString().length,M=K+B.text.length,Q=G?.querySelector(".highlight-notes-input"),Z=Q?.value.trim()||void 0,$=window.location.pathname.replace("/view/",""),Y=await fetch("/api/highlights",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({resourcePath:$,startOffset:K,endOffset:M,highlightedText:B.text,notes:Z})});if(!Y.ok)throw Error("Failed to create highlight");let _=await Y.json();if(F.push({id:_.id,startOffset:K,endOffset:M,highlightedText:B.text,isStale:!1,notes:Z||null,createdAt:Date.now()}),x(),Q)Q.value="";L(),window.getSelection()?.removeAllRanges()}catch(C){console.error("[highlights] Failed to create highlight:",C)}},x=()=>{let C=document.querySelector(".content");if(!C)return;let J=C.querySelectorAll("mark.llmd-highlight");for(let K of J){let M=K.parentNode;if(M)M.replaceChild(document.createTextNode(K.textContent||""),K)}if(C.normalize(),F.length===0)return;let q=[...F].sort((K,M)=>M.startOffset-K.startOffset),j=C.textContent||"";for(let K of q)try{let M=document.createTreeWalker(C,NodeFilter.SHOW_TEXT),Q=0,Z=null,$=0,Y=null,_=0,U=M.nextNode();while(U){let D=U.textContent?.length||0,W=Q+D;if(Z===null&&Q<=K.startOffset&&K.startOffset<W)Z=U,$=K.startOffset-Q;if(Q<K.endOffset&&K.endOffset<=W){Y=U,_=K.endOffset-Q;break}Q=W,U=M.nextNode()}if(!(Z&&Y))continue;let V=document.createRange();V.setStart(Z,$),V.setEnd(Y,_);let X=document.createElement("mark");X.className=K.isStale?"llmd-highlight llmd-highlight-stale":"llmd-highlight",X.dataset.highlightId=K.id,X.title=K.isStale?"This highlight may be outdated":"",X.style.cursor="pointer",X.addEventListener("click",(D)=>{D.stopPropagation(),c(K,D)}),V.surroundContents(X)}catch(M){console.error("[highlights] Failed to render highlight:",M)}};var o=(C)=>{return new Date(C).toLocaleString("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit"})},l=(C)=>{let J=document.querySelector(`[data-highlight-id="${C}"]`);if(J)J.scrollIntoView({behavior:"smooth",block:"center"}),J.classList.add("highlight-flash"),setTimeout(()=>{J.classList.remove("highlight-flash")},1000)},a=(C,J)=>C.length>J?`${C.slice(0,J)}...`:C,O=()=>{let C=document.querySelector(".content");if(!C)return;let J=window.location.pathname;if(!J.startsWith("/view/"))return;let q=J.slice(6);fetch(`/api/highlights/resource?path=${encodeURIComponent(q)}`).then((j)=>j.json()).then((j)=>{let K=j.highlights||[];if(K.length===0)return;let M=document.createElement("div");M.className="highlights-summary",M.innerHTML=`
26
+ `;let j=C.querySelector(".highlight-create-btn");if(j)j.addEventListener("click",async(J)=>{J.stopPropagation(),await l()});let q=C.querySelector(".highlight-close-btn");if(q)q.addEventListener("click",(J)=>{J.stopPropagation(),y()});return C},g=(C,j)=>{let q=document.querySelector(".content");if(!q)return 0;let J=document.createRange();J.selectNodeContents(q),J.setEnd(j.startContainer,j.startOffset);let K=J.toString(),Q=0,Z=0;while(!0){let $=K.indexOf(C,Z);if($===-1)break;Q+=1,Z=$+1}return Q},l=async()=>{if(!B)return;try{let{text:C,range:j}=B,q=g(C,j),K=_?.querySelector(".highlight-notes-input")?.value.trim()||void 0,Q=window.location.pathname.replace("/view/",""),Z=await fetch("/api/highlights",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({resourcePath:Q,highlightedText:C,occurrenceIndex:q,notes:K})});if(!Z.ok){let $=await Z.json();console.error("[highlights] Server error:",$.error||"Failed to create highlight");return}window.location.reload()}catch(C){console.error("[highlights] Failed to create highlight:",C)}};var h=(C)=>{return new Date(C).toLocaleString("en-US",{month:"short",day:"numeric",hour:"numeric",minute:"2-digit"})},s=(C)=>{let j=document.querySelector(`[data-highlight-id="${C}"]`);if(j)j.scrollIntoView({behavior:"smooth",block:"center"}),j.classList.add("highlight-flash"),setTimeout(()=>{j.classList.remove("highlight-flash")},1000)},o=(C,j)=>C.length>j?`${C.slice(0,j)}...`:C,S=()=>{let C=document.querySelector(".content");if(!C)return;let j=window.location.pathname;if(!j.startsWith("/view/"))return;let q=j.slice(6);fetch(`/api/highlights/resource?path=${encodeURIComponent(q)}`).then((J)=>J.json()).then((J)=>{let K=J.highlights||[];if(K.length===0)return;let Q=document.createElement("div");Q.className="highlights-summary",Q.innerHTML=`
27
27
  <div style="background: var(--code-bg); border: 1px solid var(--border); border-radius: 8px; padding: 16px 20px; margin: 24px 0;">
28
28
  <h3 style="font-size: 0.875rem; margin: 0 0 12px 0; text-transform: uppercase; letter-spacing: 0.08em; font-weight: 700; opacity: 0.8; display: flex; align-items: center; gap: 6px;">
29
29
  <span style="font-size: 16px;">✨</span>
30
30
  Highlights (${K.length})
31
31
  </h3>
32
32
  <ul style="list-style: none; padding: 0; margin: 0; font-size: 14px;">
33
- ${K.map(($)=>{let Y=$.isStale?'<span style="color: #f44336; margin-left: 4px;" title="Stale - file has changed">⚠️</span>':"",_=$.isStale?' style="text-decoration: line-through; opacity: 0.5;"':"",U=$.highlightedText.length>80?`${$.highlightedText.slice(0,80)}...`:$.highlightedText,V=$.notes?a($.notes,60):"",X=V?`<div style="margin-top: 4px; padding: 6px; background: var(--sidebar-bg); border-radius: 3px; font-size: 11px; opacity: 0.8;">\uD83D\uDCAD ${V}</div>`:"";return`
33
+ ${K.map((U)=>{let Y=U.isStale?'<span style="color: #f44336; margin-left: 4px;" title="Stale - file has changed">⚠️</span>':"",X=U.isStale?' style="text-decoration: line-through; opacity: 0.5;"':"",G=U.highlightedText.length>80?`${U.highlightedText.slice(0,80)}...`:U.highlightedText,V=U.notes?o(U.notes,60):"",F=V?`<div style="margin-top: 4px; padding: 6px; background: var(--sidebar-bg); border-radius: 3px; font-size: 11px; opacity: 0.8;">\uD83D\uDCAD ${V}</div>`:"";return`
34
34
  <li style="margin: 8px 0; padding: 8px; background: var(--bg); border-radius: 4px; border-left: 3px solid var(--accent); cursor: pointer; transition: background 0.2s;"
35
- data-highlight-id="${$.id}"
35
+ data-highlight-id="${U.id}"
36
36
  class="highlight-summary-item"
37
37
  onmouseover="this.style.background='var(--hover)'"
38
38
  onmouseout="this.style.background='var(--bg)'">
39
- <div${_}>${U}</div>
40
- ${X}
39
+ <div${X}>${G}</div>
40
+ ${F}
41
41
  <div style="opacity: 0.5; font-size: 12px; margin-top: 4px;">
42
- ${o($.createdAt)}${Y}
42
+ ${h(U.createdAt)}${Y}
43
43
  </div>
44
44
  </li>
45
45
  `}).join("")}
@@ -50,4 +50,4 @@ var R=()=>{let C=document.querySelector(".admin-section"),J=document.querySelect
50
50
  </a>
51
51
  </div>
52
52
  </div>
53
- `;let Q=C.querySelector("h1, h2");if(Q)Q.parentNode?.insertBefore(M,Q);else C.insertBefore(M,C.firstChild);let Z=M.querySelectorAll(".highlight-summary-item");for(let $ of Array.from(Z)){let Y=$.getAttribute("data-highlight-id");if(Y)$.addEventListener("click",()=>l(Y))}}).catch((j)=>{console.error("Failed to load highlights summary:",j)})};var s=()=>{let C=document.querySelector(".sidebar");if(!C)return;let J=document.createElement("div");J.className="sidebar-resize-handle",C.appendChild(J);let q=!1,j=0,K=0,M=(Y)=>{q=!0,j=Y.clientX,K=C.offsetWidth,document.body.style.cursor="ew-resize",document.body.style.userSelect="none",Y.preventDefault()},Q=(Y)=>{if(!q)return;let _=Y.clientX-j,U=K+_,V=Math.max(200,Math.min(600,U));C.style.width=`${V}px`,localStorage.setItem("llmd-sidebar-width",V.toString())},Z=()=>{if(!q)return;q=!1,document.body.style.cursor="",document.body.style.userSelect=""};J.addEventListener("mousedown",M),document.addEventListener("mousemove",Q),document.addEventListener("mouseup",Z);let $=localStorage.getItem("llmd-sidebar-width");if($){let Y=Number.parseInt($,10);if(Y>=200&&Y<=600)C.style.width=`${Y}px`}};if(typeof window<"u")window.addEventListener("DOMContentLoaded",()=>{s()});var S=(C,J,q)=>{fetch("/api/events",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:C,path:J,resourceType:q})}).catch(()=>{})},N=(C)=>{S("open",C,"dir")},H=(C)=>{S("view",C,"file")};var E=async()=>{try{let C=window.location.pathname.replace("/view/",""),J=C.includes("/")?C.substring(0,C.lastIndexOf("/")):"",q=await fetch(`/api/highlights/directory?path=${encodeURIComponent(J||".")}`);if(!q.ok)return;let K=(await q.json()).highlights||[],M=new Set;for(let Z of K)M.add(Z.resourcePath);let Q=document.querySelectorAll("a[data-file-path]");for(let Z of Array.from(Q)){let $=Z.getAttribute("data-file-path");if($&&M.has($)){let Y=Z.querySelector(".file-icon svg");if(Y)Y.setAttribute("fill","var(--accent)"),Y.setAttribute("fill-opacity","0.3")}}}catch(C){console.error("[file-highlights] Failed to load highlight indicators:",C)}};if(typeof window<"u")window.addEventListener("DOMContentLoaded",()=>{E()});window.trackDirectoryOpen=N;window.trackFileView=H;R();E();if(document.querySelector(".content"))A(),O();console.log("[llmd] Client initialized");
53
+ `;let Z=C.querySelector("h1, h2");if(Z)Z.parentNode?.insertBefore(Q,Z);else C.insertBefore(Q,C.firstChild);let $=Q.querySelectorAll(".highlight-summary-item");for(let U of Array.from($)){let Y=U.getAttribute("data-highlight-id");if(Y)U.addEventListener("click",()=>s(Y))}}).catch((J)=>{console.error("Failed to load highlights summary:",J)})};var n=()=>{let C=document.querySelector(".sidebar");if(!C)return;let j=document.createElement("div");j.className="sidebar-resize-handle",C.appendChild(j);let q=!1,J=0,K=0,Q=(Y)=>{q=!0,J=Y.clientX,K=C.offsetWidth,document.body.style.cursor="ew-resize",document.body.style.userSelect="none",Y.preventDefault()},Z=(Y)=>{if(!q)return;let X=Y.clientX-J,G=K+X,V=Math.max(200,Math.min(600,G));C.style.width=`${V}px`,localStorage.setItem("llmd-sidebar-width",V.toString())},$=()=>{if(!q)return;q=!1,document.body.style.cursor="",document.body.style.userSelect=""};j.addEventListener("mousedown",Q),document.addEventListener("mousemove",Z),document.addEventListener("mouseup",$);let U=localStorage.getItem("llmd-sidebar-width");if(U){let Y=Number.parseInt(U,10);if(Y>=200&&Y<=600)C.style.width=`${Y}px`}};if(typeof window<"u")window.addEventListener("DOMContentLoaded",()=>{n()});var T=(C,j,q)=>{fetch("/api/events",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({type:C,path:j,resourceType:q})}).catch(()=>{})},w=(C)=>{T("open",C,"dir")},H=(C)=>{T("view",C,"file")};var M=async()=>{try{let C=window.location.pathname.replace("/view/",""),j=C.includes("/")?C.substring(0,C.lastIndexOf("/")):"",q=await fetch(`/api/highlights/directory?path=${encodeURIComponent(j||".")}`);if(!q.ok)return;let K=(await q.json()).highlights||[],Q=new Set;for(let $ of K)Q.add($.resourcePath);let Z=document.querySelectorAll("a[data-file-path]");for(let $ of Array.from(Z)){let U=$.getAttribute("data-file-path");if(U&&Q.has(U)){let Y=$.querySelector(".file-icon svg");if(Y)Y.setAttribute("fill","var(--accent)"),Y.setAttribute("fill-opacity","0.3")}}}catch(C){console.error("[file-highlights] Failed to load highlight indicators:",C)}};if(typeof window<"u")window.addEventListener("DOMContentLoaded",()=>{M()});window.trackDirectoryOpen=w;window.trackFileView=H;D();M();if(document.querySelector(".content"))R(),S();console.log("[llmd] Client initialized");