bosia 0.5.12 → 0.5.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bosia",
3
- "version": "0.5.12",
3
+ "version": "0.5.13",
4
4
  "type": "module",
5
5
  "description": "A fast, batteries-included fullstack framework — SSR · Svelte 5 Runes · Bun · ElysiaJS. File-based routing inspired by SvelteKit. No Node.js, no Vite, no adapters.",
6
6
  "keywords": [
@@ -66,18 +66,40 @@ function injectLocs(source: string, relPath: string): string {
66
66
  if (!ast.fragment) return source;
67
67
 
68
68
  const ms = new MagicString(source);
69
+ const safeAttr = relPath.replace(/"/g, """);
70
+ // HTML comment data cannot contain `--`; neutralise defensively. (Almost no
71
+ // real path contains it, but a stray `--foo` directory shouldn't break parsing.)
72
+ const safeComment = relPath.replace(/--/g, "__");
69
73
  walk(ast.fragment, (node) => {
70
- if (node.type !== "RegularElement") return;
71
- const name = node.name ?? "";
72
- if (!name) return;
73
- if (name === "script" || name === "style") return;
74
- if (/^[A-Z]/.test(name)) return;
75
- if (name.includes(":")) return;
76
- if (typeof node.start !== "number") return;
77
- const insertAt = node.start + 1 + name.length;
78
- const { line, col } = lineColFromOffset(source, node.start);
79
- const safe = relPath.replace(/"/g, """);
80
- ms.appendLeft(insertAt, ` data-bosia-loc="${safe}:${line}:${col}"`);
74
+ if (node.type === "RegularElement") {
75
+ const name = node.name ?? "";
76
+ if (!name) return;
77
+ if (name === "script" || name === "style") return;
78
+ if (/^[A-Z]/.test(name)) return;
79
+ if (name.includes(":")) return;
80
+ if (typeof node.start !== "number") return;
81
+ const insertAt = node.start + 1 + name.length;
82
+ const { line, col } = lineColFromOffset(source, node.start);
83
+ ms.appendLeft(insertAt, ` data-bosia-loc="${safeAttr}:${line}:${col}"`);
84
+ return;
85
+ }
86
+ // Bracket component invocations with HTML comments so the runtime can
87
+ // walk DOM siblings to reconstruct the call-site chain (Page → Layout →
88
+ // Button). Without this the per-element `data-bosia-loc` only points at
89
+ // the component's *definition* file, which is misleading when the user
90
+ // (or an AI agent) wants to edit the page that rendered it. Comments
91
+ // survive into the rendered DOM because `preserveComments: dev` is set
92
+ // on the compile call below.
93
+ if (
94
+ node.type === "Component" ||
95
+ node.type === "SvelteComponent" ||
96
+ node.type === "SvelteSelf"
97
+ ) {
98
+ if (typeof node.start !== "number" || typeof node.end !== "number") return;
99
+ const { line, col } = lineColFromOffset(source, node.start);
100
+ ms.appendLeft(node.start, `<!--bosia:o=${safeComment}:${line}:${col}-->`);
101
+ ms.appendRight(node.end, `<!--bosia:c-->`);
102
+ }
81
103
  });
82
104
  return ms.toString();
83
105
  }
@@ -26,7 +26,7 @@ function ensureOutline(){
26
26
  outline.style.cssText="position:fixed;pointer-events:none;border:2px solid #f73b27;background:rgba(247,59,39,.08);z-index:2147483646;border-radius:2px;transition:all .05s linear;display:none";
27
27
  document.body.appendChild(outline);
28
28
  tip=document.createElement("div");
29
- tip.style.cssText="position:fixed;pointer-events:none;background:#111;color:#fff;font:11px/1.4 ui-monospace,monospace;padding:3px 6px;border-radius:3px;z-index:2147483647;display:none;white-space:nowrap";
29
+ tip.style.cssText="position:fixed;pointer-events:none;background:#111;color:#fff;font:11px/1.4 ui-monospace,monospace;padding:3px 6px;border-radius:3px;z-index:2147483647;display:none;white-space:nowrap;max-width:90vw;overflow:hidden;text-overflow:ellipsis";
30
30
  document.body.appendChild(tip);
31
31
  }
32
32
  function hideOutline(){if(outline)outline.style.display="none";if(tip)tip.style.display="none"}
@@ -43,6 +43,38 @@ function showOutline(el,loc){
43
43
  function parseLoc(s){var m=/^(.+):(\\d+):(\\d+)$/.exec(s);if(!m)return null;return{file:m[1],line:+m[2],col:+m[3]}}
44
44
  function findTarget(e){var n=e.target;while(n&&n.nodeType===1){if(n.hasAttribute&&n.hasAttribute("data-bosia-loc"))return n;n=n.parentNode}return null}
45
45
 
46
+ // Walk DOM ancestors collecting <Component> call-site markers (<!--bosia:o=…-->
47
+ // / <!--bosia:c-->) into an outermost-first array. At each ancestor we scan
48
+ // previous siblings tracking a depth counter so an earlier sibling component's
49
+ // open marker doesn't get attributed to a later sibling's element.
50
+ function collectStack(el){
51
+ var stack=[],cur=el;
52
+ while(cur){
53
+ var depth=0,sib=cur.previousSibling;
54
+ while(sib){
55
+ if(sib.nodeType===8){
56
+ var v=sib.nodeValue||"";
57
+ if(v==="bosia:c") depth++;
58
+ else if(v.lastIndexOf("bosia:o=",0)===0){
59
+ if(depth>0) depth--;
60
+ else stack.push(v.slice(8));
61
+ }
62
+ }
63
+ sib=sib.previousSibling;
64
+ }
65
+ var p=cur.parentNode;
66
+ if(!p||p.nodeType!==1) break;
67
+ cur=p;
68
+ }
69
+ return stack.reverse();
70
+ }
71
+ function chainString(el){
72
+ var stack=collectStack(el);
73
+ var leaf=el.getAttribute("data-bosia-loc")||"";
74
+ if(!stack.length) return leaf;
75
+ return stack.concat(leaf).join(" → ");
76
+ }
77
+
46
78
  function toast(msg,err){
47
79
  var t=document.createElement("div");
48
80
  t.textContent=msg;
@@ -62,10 +94,12 @@ function send(payload,onOk,onErr){
62
94
  function closeForm(){if(form){form.remove();form=null}}
63
95
  function openForm(loc,el){
64
96
  closeForm();
97
+ var chain=chainString(el);
65
98
  var r=el.getBoundingClientRect();
66
99
  form=document.createElement("div");
67
100
  form.style.cssText="position:fixed;left:"+r.left+"px;top:"+(r.bottom+6)+"px;background:#fff;color:#111;border:1px solid #d4d4d8;border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.18);padding:10px;width:340px;z-index:2147483647;font:13px ui-sans-serif,system-ui,sans-serif";
68
- form.innerHTML='<div style="font-size:11px;color:#71717a;margin-bottom:6px;font-family:ui-monospace,monospace">'+loc.file+":"+loc.line+'</div>'+
101
+ var header='<div style="font-size:11px;color:#71717a;margin-bottom:6px;font-family:ui-monospace,monospace;word-break:break-all">'+chain+'</div>';
102
+ form.innerHTML=header+
69
103
  '<textarea placeholder="Describe a fix (Enter to send, Esc to cancel, empty = open in editor)" style="width:100%;min-height:64px;border:1px solid #e4e4e7;border-radius:4px;padding:6px;font:13px ui-sans-serif,system-ui,sans-serif;resize:vertical;box-sizing:border-box;outline:none"></textarea>'+
70
104
  '<div style="margin-top:8px;display:flex;gap:6px;justify-content:flex-end">'+
71
105
  '<button data-cancel style="padding:4px 10px;border:1px solid #e4e4e7;background:#fff;border-radius:4px;cursor:pointer;font-size:12px">Cancel</button>'+
@@ -75,9 +109,12 @@ function openForm(loc,el){
75
109
  var ta=form.querySelector("textarea");
76
110
  ta.focus();
77
111
  function submit(){
78
- var comment=ta.value.trim();
112
+ var userComment=ta.value.trim();
79
113
  var payload={file:loc.file,line:loc.line,col:loc.col};
80
- if(comment)payload.comment=comment;
114
+ if(userComment){
115
+ var tree=chain.indexOf(" → ")>=0?("Component tree (outer → leaf): "+chain+"\\n\\n"):"";
116
+ payload.comment=tree+userComment;
117
+ }
81
118
  send(payload,function(j){toast(j.mode==="ai"?"sent to AI":"opened "+loc.file+":"+loc.line)});
82
119
  closeForm();
83
120
  }
@@ -100,7 +137,7 @@ window.addEventListener("mousemove",function(e){
100
137
  if(!altDown||form){hideOutline();return}
101
138
  var el=findTarget(e);
102
139
  if(!el){hideOutline();return}
103
- showOutline(el,el.getAttribute("data-bosia-loc"));
140
+ showOutline(el,chainString(el));
104
141
  },true);
105
142
 
106
143
  window.addEventListener("click",function(e){
@@ -137,7 +174,7 @@ if(ERR_ENABLED){
137
174
  var t=e.target;
138
175
  if(!t||!t.closest)return;
139
176
  var el=t.closest("[data-bosia-loc]");
140
- if(el)lastInteraction=el.getAttribute("data-bosia-loc");
177
+ if(el)lastInteraction=chainString(el);
141
178
  }
142
179
  window.addEventListener("mousedown",trackInteraction,true);
143
180
  window.addEventListener("keydown",trackInteraction,true);