@floless/app 0.49.0 → 0.50.0

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.
@@ -52856,7 +52856,7 @@ function appVersion() {
52856
52856
  return resolveVersion({
52857
52857
  isSea: isSea2(),
52858
52858
  sqVersionXml: readSqVersionXml(),
52859
- define: true ? "0.49.0" : void 0,
52859
+ define: true ? "0.50.0" : void 0,
52860
52860
  pkgVersion: readPkgVersion()
52861
52861
  });
52862
52862
  }
@@ -52866,7 +52866,7 @@ function resolveChannel(s) {
52866
52866
  return "dev";
52867
52867
  }
52868
52868
  function appChannel() {
52869
- return resolveChannel({ isSea: isSea2(), define: true ? "0.49.0" : void 0 });
52869
+ return resolveChannel({ isSea: isSea2(), define: true ? "0.50.0" : void 0 });
52870
52870
  }
52871
52871
 
52872
52872
  // workflow-update.ts
@@ -212,6 +212,7 @@
212
212
  "segments": { "type": "array", "items": { "$ref": "#/$defs/segment" } },
213
213
  "labels": { "type": "array", "items": { "$ref": "#/$defs/label" } },
214
214
  "details": { "type": "array", "items": { "$ref": "#/$defs/detail" } },
215
+ "callouts": { "type": "array", "items": { "$ref": "#/$defs/callout" }, "description": "Drawing cross-references drawn ON this plan that point to another sheet (section cuts, elevation marks, detail refs). The editor renders a clickable bubble at each `disp` that jumps to the referenced sheet." },
215
216
  "tos_callouts": { "type": "array", "items": { "$ref": "#/$defs/tos_callout" } },
216
217
  "members": { "type": "array", "items": { "$ref": "#/$defs/member" } },
217
218
  "dims": { "type": "array", "items": { "$ref": "#/$defs/dim" }, "description": "Draft-only on-drawing dimensions (editor annotations). NOT baked into the lock / 3D / IFC / BOM." },
@@ -281,6 +282,26 @@
281
282
  "disp": { "$ref": "#/$defs/point2" }
282
283
  }
283
284
  },
285
+ "callout": {
286
+ "type": "object",
287
+ "required": ["kind", "target"],
288
+ "additionalProperties": true,
289
+ "description": "A drawing cross-reference drawn ON this plan that points to another sheet — a section cut, an elevation mark, or a detail reference. Clicking its bubble in the editor jumps to the referenced sheet (a loaded plan → switch to it & frame it; otherwise the detail lightbox).",
290
+ "properties": {
291
+ "kind": { "enum": ["section", "elevation", "detail-ref"], "description": "section = a section cut symbol; elevation = an elevation / interior-elevation mark; detail-ref = a detail callout pointing to another sheet." },
292
+ "text": { "type": "string", "description": "The mark as drawn, e.g. \"A\", \"A-A\", \"3/S-501\"." },
293
+ "disp": { "$ref": "#/$defs/point2", "description": "Where the mark is drawn on THIS plan (display space) — the clickable hotspot anchor." },
294
+ "target": {
295
+ "type": "object",
296
+ "additionalProperties": true,
297
+ "description": "Where the mark points.",
298
+ "properties": {
299
+ "sheet": { "type": "string", "description": "Referenced sheet, e.g. S-301." },
300
+ "number": { "type": "string", "description": "Detail / section number on that sheet, e.g. \"A\" or \"3\"." }
301
+ }
302
+ }
303
+ }
304
+ },
284
305
  "tos_callout": {
285
306
  "type": "object",
286
307
  "required": ["elev_in"],
@@ -237,6 +237,7 @@ Important optional fields:
237
237
  "segments": [ { "id": "seg-1", "a": [x,y], "b": [x,y], "o": "H" } ],
238
238
  "labels": [ { "text": "W16X26", "disp": [x,y] } ],
239
239
  "details": [ { "text": "5-S504", "disp": [x,y] } ],
240
+ "callouts": [ { "kind": "section", "text": "A", "disp": [x,y], "target": { "sheet": "S-301", "number": "A" } } ],
240
241
  "tos_callouts": [ { "elev_in": 198, "type": "TOS", "disp": [x,y] } ],
241
242
  "members": [
242
243
  {
@@ -296,6 +297,14 @@ Key field rules:
296
297
  `company-rules.md`: "moment → 50S504 → Tekla 146"), read it and emit those rows in `connections[]`
297
298
  with `source: "company-rules"`, then PUT. The user reviews/edits them in the editor's
298
299
  **Connections** library panel and assigns a row per member end. No new agent verb — it's contract data.
300
+ - **Clickable callouts (`callouts[]`).** Harvest the cross-reference marks drawn ON the plan that
301
+ point to ANOTHER sheet — **section cuts** (e.g. a circle with `A` over `A` and a sheet ref),
302
+ **elevation/interior-elevation marks**, and **detail references** — as per-plan
303
+ `callouts: [{ kind:"section"|"elevation"|"detail-ref", text:"A", disp:[x,y], target:{ sheet:"S-301", number:"A" } }]`.
304
+ `disp` is where the mark sits on THIS plan (display space — the clickable hotspot); `target` is where
305
+ it points. The editor draws a clickable bubble at each `disp`: clicking jumps to the referenced sheet
306
+ if it's loaded as a plan, else opens the detail lightbox. (Keep `details[]` as-is — those are the
307
+ detail callouts that already preview; `callouts[]` adds the section/elevation marks and off-sheet refs.)
299
308
 
300
309
  ### 5. Write to the contract store
301
310
 
@@ -68,6 +68,15 @@
68
68
  rect.dethot{cursor:pointer;fill:rgba(168,85,247,.16);stroke:#a855f7;stroke-width:1;stroke-dasharray:3 3;vector-effect:non-scaling-stroke}
69
69
  rect.dethot:hover{fill:rgba(168,85,247,.34)}
70
70
  text.dettx{fill:#ddd6fe;font:bold 11px system-ui;text-anchor:middle;pointer-events:none}
71
+ /* clickable callout bubbles (Workstream B): section/elevation/detail-ref + plain details — navigable cross-references drawn on the plan in select mode. Existing palette only: purple #a855f7 (details), cyan #22d3ee (sections/elevations); solid vs dashed separates the kinds. */
72
+ g.cohot{cursor:pointer}
73
+ circle.cobub{stroke-width:1.5;vector-effect:non-scaling-stroke;opacity:.55}
74
+ circle.cobub.k-detail,circle.cobub.k-detailref{stroke:#a855f7;color:#a855f7;fill:rgba(168,85,247,.12)}
75
+ circle.cobub.k-section,circle.cobub.k-elev{stroke:#22d3ee;color:#22d3ee;fill:rgba(34,211,238,.10)}
76
+ circle.cobub.k-detailref,circle.cobub.k-elev{stroke-dasharray:3 3}
77
+ g.cohot:hover circle.cobub{opacity:1;stroke-width:2;filter:drop-shadow(0 0 4px currentColor)}
78
+ text.cotx{fill:#e2e8f0;font:bold 11px system-ui;text-anchor:middle;dominant-baseline:central;pointer-events:none;opacity:.6}
79
+ g.cohot:hover text.cotx{opacity:1}
71
80
  #detailsModal,#framesModal,#rfiModal,#confModal,#askAiModal,#connModal{position:fixed;inset:0;z-index:20;display:none;align-items:center;justify-content:center}
72
81
  #askAiDrop:hover{border-color:var(--brand);color:var(--text)} #askAiDrop.has{border-style:solid;border-color:var(--brand)}
73
82
  .aithumb{position:relative;display:inline-flex} .aithumb img{height:60px;border-radius:4px;border:1px solid var(--line);background:#fff;display:block}
@@ -215,6 +224,7 @@
215
224
  <div id=moreMenu role=menu>
216
225
  <div class=mlabel>Dimensions</div>
217
226
  <button id=dimToggleB title="Show or hide all placed dimensions on the plan">Hide dimensions</button>
227
+ <button id=calloutToggleB title="Show or hide the clickable callout bubbles (section / elevation / detail references) on the plan">Hide callouts</button>
218
228
  <hr>
219
229
  <div class=mlabel>Coordinate system</div>
220
230
  <button id=csSetB title="Define a local X axis by clicking two points (origin, then X-direction). Y follows at 90°. Orthogonal drawing and X/Y dimensions then snap to this frame — for skewed framing.">↺ Set local axes…</button>
@@ -438,6 +448,7 @@ function main() {
438
448
  let P, X0,Y0,X1,Y1, FT, RB64, EXTX, EXTY, profs; // per-plan, set by setPlan()
439
449
  let mode='sel', drag=null, picking=false, pickKind='profile', pickEnd=null, geoMode=null;
440
450
  let dimMode=false, dimDraft=null, dimAxis='free', selDimIds=new Set(), dimsVisible=true; // Dimension tool: armed flag, in-progress {a,b,axis}, sticky axis, selected dim ids (multi-select), show/hide
451
+ let calloutsVisible=true; // Workstream B: show/hide the clickable callout bubbles (details + section/elevation/detail-ref) on the plan in select mode
441
452
  let csaxisMode=false, csDraft=null; // Local coordinate system "set axes" tool: armed flag + in-progress origin [x,y] (null until click 1). P.frame={o,u} holds the committed local frame (null = global).
442
453
  let dimChain=false, dimChainPrev=null, dimSeq=0; // chained "continuous" dimensioning: toggle, the running {point,axis,off,rot}, and a counter for unique ids on rapid clicks
443
454
  let dimSplitMode=false; // "add split point" mode on a selected dim — each click inserts a point and splits the dim segment under it into two
@@ -750,6 +761,22 @@ function render(){
750
761
  s+=`<rect class=lblhot data-prof="${esc(lb.text)}" x="${lb.disp[0]-w/2}" y="${lb.disp[1]-10}" width="${w}" height="20" rx="3"><title>${esc(lb.text)}</title></rect>`;}
751
762
  if(picking&&pickKind==='detail'&&P.details) for(const d of P.details){const w=Math.max(34,d.text.length*8);
752
763
  s+=`<rect class=dethot data-det="${esc(d.text)}" x="${d.disp[0]-w/2}" y="${d.disp[1]-9}" width="${w}" height="18" rx="3"><title>${esc(d.text)}</title></rect><text class=dettx x="${d.disp[0]}" y="${d.disp[1]+4}">${esc(d.text)}</text>`;}
764
+ // Workstream B: clickable callout bubbles in normal select mode — details[] + per-plan callouts[].
765
+ // data-co-sheet drives the smart jump (loaded plan → switch; else data-co-prev → detail lightbox).
766
+ // hidden while a sub-tool is armed (dim-split / geo extend-trim-split) so the bubbles never distract
767
+ // or shadow a tool click — those handlers run earlier in pointerdown, but don't draw navigables mid-tool.
768
+ if(calloutsVisible&&mode==='sel'&&!picking&&!dimMode&&!csaxisMode&&!dimSplitMode&&!geoMode){
769
+ const sheetLoaded=sh=>!!sh&&C.plans.some(p=>p.sheet&&p.sheet.toUpperCase()===String(sh).toUpperCase());
770
+ // honest destination hint: loaded → no suffix (jumps); a resolvable preview → "(lightbox)"; else "(not loaded)".
771
+ const hint=(sh,prev)=>sheetLoaded(sh)?'':(prev&&previewFor(prev)?' (lightbox)':' (not loaded)');
772
+ const cob=(kc,mark,disp,sheet,prev,tip)=>`<g class=cohot data-co-sheet="${esc(sheet||'')}" data-co-prev="${esc(prev||'')}"><title>${esc(tip)}</title><circle class="cobub k-${kc}" cx="${disp[0]}" cy="${disp[1]}" r="11"/><text class=cotx x="${disp[0]}" y="${disp[1]}">${esc(String(mark==null?'':mark).slice(0,3))}</text></g>`;
773
+ for(const d of (P.details||[])){if(!Array.isArray(d.disp))continue;const sh=sheetOf(d.text),nu=numOf(d.text);
774
+ s+=cob('detail',nu||d.text,d.disp,sh,d.text,'Detail '+(nu||d.text||'')+(sh?' → '+sh:'')+hint(sh,d.text));}
775
+ for(const co of (P.callouts||[])){if(!Array.isArray(co.disp))continue;const sh=co.target&&co.target.sheet,nu=co.target&&co.target.number;
776
+ const kc=co.kind==='section'?'section':co.kind==='elevation'?'elev':'detailref';const kl=co.kind==='section'?'Section':co.kind==='elevation'?'Elevation':'Detail ref';
777
+ const prev=(nu&&sh)?(nu+'-'+sh):(co.text||'');
778
+ s+=cob(kc,co.text||nu,co.disp,sh,prev,kl+' '+(co.text||nu||'')+(sh?' → '+sh:'')+hint(sh,prev));}
779
+ }
753
780
  // numbered badges on the selected members (1..N in selection order; coincident ones fan out so duplicates are countable)
754
781
  // sized in 1/zoom units → constant on screen at any zoom (updateBadges keeps them in sync while zooming)
755
782
  const selM=selArr();
@@ -1177,6 +1204,12 @@ svg.addEventListener('pointerdown',e=>{if(e.button!==0)return;const t=e.target;
1177
1204
  return;}
1178
1205
  if(t.classList.contains('dethot')){const det=t.dataset.det;
1179
1206
  if(picking&&pickKind==='detail'&&pickEnd){const o=pickEnd;picking=false;pickEnd=null;edit(()=>{o.detail=det;});}return;}
1207
+ {const cg=t.closest('g.cohot');if(cg){const sheet=cg.dataset.coSheet,prev=cg.dataset.coPrev; // Workstream B smart jump
1208
+ const idx=sheet?C.plans.findIndex(p=>p.sheet&&p.sheet.toUpperCase()===sheet.toUpperCase()):-1;
1209
+ if(idx>=0){if(idx!==C.active)setPlan(idx);else fitToWindow();} // referenced sheet is loaded → switch to it (or re-frame if already here)
1210
+ else if(prev&&previewFor(prev))openPreview(prev); // not loaded but we have the detail image → lightbox
1211
+ else toast(sheet?('Sheet '+sheet+' isn’t loaded — open it to jump there'):'No target sheet for this callout');
1212
+ return;}}
1180
1213
  if(t.classList.contains('seg')&&mode==='add'){addFromSeg(t.dataset.seg);return;}
1181
1214
  if(t.tagName==='image'&&mode==='add'){buildSnap(null);let q=toSvg(e),x0=q.x,y0=q.y;if(!e.altKey){const sn=snap(x0,y0);x0=sn.x;y0=sn.y;}const ln=document.createElementNS('http://www.w3.org/2000/svg','line');ln.setAttribute('class','draw');ln.setAttribute('x1',x0);ln.setAttribute('y1',y0);ln.setAttribute('x2',x0);ln.setAttribute('y2',y0);svg.appendChild(ln);drag={type:'draw',x0,y0,line:ln};svg.setPointerCapture(e.pointerId);e.preventDefault();return;}
1182
1215
  if(t.classList.contains('member')&&mode==='sel'){const id=t.dataset.id;
@@ -1239,6 +1272,8 @@ document.getElementById('csSetB').onclick=()=>{csaxisMode=!csaxisMode;setCsMode(
1239
1272
  document.getElementById('csResetB').onclick=()=>{resetFrame();render();};
1240
1273
  function updDimToggle(){const b=document.getElementById('dimToggleB');if(b)b.textContent=dimsVisible?'Hide dimensions':'Show dimensions';}
1241
1274
  document.getElementById('dimToggleB').onclick=()=>{dimsVisible=!dimsVisible;updDimToggle();render();};
1275
+ function updCalloutToggle(){const b=document.getElementById('calloutToggleB');if(b)b.textContent=calloutsVisible?'Hide callouts':'Show callouts';}
1276
+ document.getElementById('calloutToggleB').onclick=()=>{calloutsVisible=!calloutsVisible;updCalloutToggle();render();};
1242
1277
  document.getElementById('dupB').onclick=()=>{const ids=redundantDups(); // re-scan on demand (also runs live after every edit)
1243
1278
  if(!ids.length){const b=document.getElementById('dupB');b.dataset.flash='1';b.classList.add('ok');b.textContent='No duplicates ✓';
1244
1279
  setTimeout(()=>{delete b.dataset.flash;b.classList.remove('ok');updDup();},1500);return;}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floless/app",
3
- "version": "0.49.0",
3
+ "version": "0.50.0",
4
4
  "type": "module",
5
5
  "description": "Thin localhost host for floless.app — serves web/ and shells the aware CLI. No engine, no LLM.",
6
6
  "bin": {