@floless/app 0.48.1 → 0.49.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.48.1" : void 0,
52859
+ define: true ? "0.49.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.48.1" : void 0 });
52869
+ return resolveChannel({ isSea: isSea2(), define: true ? "0.49.0" : void 0 });
52870
52870
  }
52871
52871
 
52872
52872
  // workflow-update.ts
@@ -223,6 +223,9 @@ Important optional fields:
223
223
  "weights": { "W16X26": 26, "HSS6X6X3/8": 27.48 }, // profile → plf; null for unresolved
224
224
  "moment_frames": [ … ], // from Frame Elevation sheets
225
225
  "detail_bubbles": { "S-504": { "5": [0.32, 0.71] } }, // sheet → { number → [fx,fy] }
226
+ "connections": [ // vendor-neutral connection library
227
+ { "id": "cx-m1", "type": "moment", "detail": "5-S504", "targets": { "tekla": "146" }, "source": "drawing" }
228
+ ],
226
229
  "plans": [
227
230
  {
228
231
  "sheet": "S-202",
@@ -245,7 +248,7 @@ Important optional fields:
245
248
  "rfi": false,
246
249
  "mf": false,
247
250
  "ends": [
248
- { "tos": 198, "note": "moment", "tosDef": false, "detail": "5-S504" },
251
+ { "tos": 198, "note": "moment", "tosDef": false, "detail": "5-S504", "conn": "cx-m1" },
249
252
  { "tos": 198, "note": "shear", "tosDef": true, "detail": "" }
250
253
  ]
251
254
  },
@@ -282,6 +285,17 @@ Key field rules:
282
285
  moment-frame member (persistent; survives AISC resolution).
283
286
  - `raster_b64` is a base64 JPEG of the framing area (title block excluded). It is
284
287
  **machine-local** and **never committed**.
288
+ - **Connection library (`connections[]`) + per-end refs.** A vendor-neutral top-level
289
+ `connections: [{ id, type, detail, targets:{ tekla:"146", … }, source }]` maps a connection
290
+ TYPE → the firm's design detail# → per-platform component ids. A member end references a row by id
291
+ via `ends[].conn` (or `col.conn` for a column); the legacy `note`/`detail` stay as the fallback
292
+ when `conn` is empty. `source` is `drawing` (read from a callout — best evidence), `company-rules`
293
+ (the firm's standard), or `user`. Prefer a `conn` reference over free text when the connection is
294
+ known — the editor's per-end picker and the Connections confidence card both key on it.
295
+ - **Company-rules pre-fill (§5.5).** If a firm-standard knowledge file is present (e.g.
296
+ `company-rules.md`: "moment → 50S504 → Tekla 146"), read it and emit those rows in `connections[]`
297
+ with `source: "company-rules"`, then PUT. The user reviews/edits them in the editor's
298
+ **Connections** library panel and assigns a row per member end. No new agent verb — it's contract data.
285
299
 
286
300
  ### 5. Write to the contract store
287
301
 
@@ -68,7 +68,7 @@
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
- #detailsModal,#framesModal,#rfiModal,#confModal,#askAiModal{position:fixed;inset:0;z-index:20;display:none;align-items:center;justify-content:center}
71
+ #detailsModal,#framesModal,#rfiModal,#confModal,#askAiModal,#connModal{position:fixed;inset:0;z-index:20;display:none;align-items:center;justify-content:center}
72
72
  #askAiDrop:hover{border-color:var(--brand);color:var(--text)} #askAiDrop.has{border-style:solid;border-color:var(--brand)}
73
73
  .aithumb{position:relative;display:inline-flex} .aithumb img{height:60px;border-radius:4px;border:1px solid var(--line);background:#fff;display:block}
74
74
  .aithumb button{position:absolute;top:-5px;right:-5px;width:16px;height:16px;padding:0;border-radius:8px;font-size:10px;line-height:16px;text-align:center;background:#7f1d1d;border-color:#991b1b;color:#fecaca}
@@ -93,7 +93,11 @@
93
93
  .chip{display:inline-block;padding:1px 6px;border-radius:4px;font-size:10px;border:1px solid var(--line);color:var(--mut);margin:0 3px 3px 0}
94
94
  .chip.assumed,.chip.weak{border-color:#713f12;color:#fbbf24} .chip.fail{border-color:#7f1d1d;color:#fca5a5}
95
95
  tr.confrow{cursor:pointer} .conf-expand{color:var(--mut);font-size:12px;line-height:1.6;background:#0b1220}
96
- .ftab td .combo{padding:4px 6px} .ftab .rea{color:var(--mut);font-size:12px} .rfichip{display:inline-block;min-width:18px;height:18px;line-height:18px;text-align:center;background:#7f1d1d;color:#fecaca;border-radius:9px;font-size:11px;padding:0 5px}
96
+ .ftab td .combo{padding:4px 6px} .ftab .rea{color:var(--mut);font-size:12px}
97
+ .ftab-inp{background:#0f172a;color:var(--text);border:1px solid #475569;border-radius:4px;padding:4px 6px;width:100%;font:13px system-ui}
98
+ .ftab-inp:focus{outline:none;border-color:var(--brand)}
99
+ .ftab .cdel{padding:3px 7px;font-size:11px} .ftab .csrc{white-space:nowrap}
100
+ .connset{opacity:.55} .rfichip{display:inline-block;min-width:18px;height:18px;line-height:18px;text-align:center;background:#7f1d1d;color:#fecaca;border-radius:9px;font-size:11px;padding:0 5px}
97
101
  .mbackdrop{position:absolute;inset:0;background:rgba(2,8,23,.62)}
98
102
  .mpanel{position:relative;background:var(--panel);border:1px solid var(--line);border-radius:10px;width:min(880px,92vw);max-height:86vh;display:flex;flex-direction:column;box-shadow:0 20px 60px rgba(0,0,0,.6)}
99
103
  .mhead{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--line)}
@@ -223,6 +227,7 @@
223
227
  <hr>
224
228
  <div class=mlabel>Reference</div>
225
229
  <button id=detailsBtn>Details</button>
230
+ <button id=connBtn title="Connection library — map each connection type to its design detail# and per-platform component id; reference a row from each member end">Connections</button>
226
231
  <button id=framesBtn>Frames</button>
227
232
  <hr>
228
233
  <div class=mlabel>Session</div>
@@ -272,6 +277,12 @@
272
277
  <div class=hint id=dnHint>For anything the auto‑detection missed. The image stays on your machine.</div>
273
278
  </div>
274
279
  <div class=mgrid id=detailsGrid></div></div></div>
280
+ <div id=connModal><div class=mbackdrop id=connBackdrop></div>
281
+ <div class=mpanel><div class=mhead><b>Connection library</b><div style="display:flex;gap:8px;align-items:center"><button id=connNew>+ New row</button><button id=connClose>✕</button></div></div>
282
+ <div class=conf-filter><span class=glab>Platform</span><div class=grp id=connPlatGrp><button class=on data-plat=tekla>Tekla</button><button data-plat=sds2>SDS2</button><button data-plat=revit>Revit</button></div><span class=hint style="margin:0">The design detail# is platform-neutral; the component id is per-platform.</span></div>
283
+ <div style="overflow:auto;flex:1;padding:14px;scrollbar-width:thin">
284
+ <table class=ftab id=connTable><thead><tr><th style="width:24%">Type</th><th style="width:24%">Detail #</th><th id=connCompHead style="width:24%">Tekla id</th><th style="width:18%">Source</th><th style="width:10%"></th></tr></thead><tbody id=connBody></tbody></table>
285
+ </div></div></div>
275
286
  <div id=framesModal><div class=mbackdrop id=framesBackdrop></div>
276
287
  <div class=mpanel><div class=mhead><b>Moment frame schedule</b><button id=framesClose>✕</button></div>
277
288
  <div id=framesGrid style="padding:14px;overflow:auto"></div></div></div>
@@ -748,7 +759,7 @@ function render(){
748
759
  s+=`<circle class=numbg ${d} cx="${x}" cy="${y}" r="${R}"/><text class=numtx ${d} x="${x}" y="${y}" style="font-size:${F}px">${it.idx+1}</text>`;});}}
749
760
  s+=renderDims();
750
761
  if(P.frame)s+=axisGlyphSvg(P.frame.o,P.frame.u,false); // local-axes glyph at the origin (only when a frame is set; removed on reset)
751
- svg.innerHTML=s; document.getElementById('profiles').innerHTML=profs.map(p=>`<option value="${esc(p)}">`).join(''); document.getElementById('details').innerHTML=(P.details||[]).map(d=>`<option value="${esc(d.text)}">`).join(''); stats(); panel(); updUR(); updDup(); updConf(); updCS();
762
+ svg.innerHTML=s; document.getElementById('profiles').innerHTML=profs.map(p=>`<option value="${esc(p)}">`).join(''); document.getElementById('details').innerHTML=(P.details||[]).map(d=>`<option value="${esc(d.text)}">`).join(''); stats(); panel(); updUR(); updDup(); updConf(); updCS(); updConnBtn();
752
763
  if(view3d&&window.Steel3DView)window.Steel3DView.setSelection(selIds); // keep the 3D highlight in sync with the 2D selection
753
764
  }
754
765
  function updDup(){const n=redundantDups().length;
@@ -901,12 +912,20 @@ function panel(){
901
912
  return `<div class=elabrow><span class=elab>${label}</span><label class=defck><input type=checkbox id=${id}_ck ${def?'checked':''}>default</label></div><input id=${id} inputmode=decimal placeholder="5 3/4&quot; · 1&#39;-0 1/4&quot;" value="${esc(fmtFtIn(v))}"${def?' disabled':''}><span class=edec>${esc(fmtDecIn(v))}</span>`;};
902
913
  const nin=(id,v)=>`<input id=${id} class="f combo" data-src=conntypes placeholder="connection / note" value="${esc(v||'')}" autocomplete=off>`;
903
914
  const dFld=(id,o)=>{const hasPrev=o.detail&&previewFor(o.detail);return `<div class=elab>Connection detail</div><div style="display:flex;gap:6px"><input id=${id} class=combo data-src=details placeholder="e.g. 5-S504" value="${esc(o.detail||'')}" style="flex:1" autocomplete=off>${hasPrev?`<button id=${id}_open class=ghost title="Open detail ${esc(o.detail)}">⤢</button>`:''}<button id=${id}_pk class="ghost${(picking&&pickKind==='detail'&&pickEnd===o)?' on':''}" title="Pick a detail callout from the drawing">⌖</button></div>`;};
915
+ // Per-end connection-library picker (sets o.conn = a connections[] row id). When set, the row's
916
+ // type/detail# are the source of truth (shown read-only below); the free-text note/detail dim to a
917
+ // fallback. Readonly combo → pick only, no free typing; the connrows source shows every row.
918
+ const cFld=(id,o)=>{const row=o.conn?connRowById(o.conn):null;
919
+ const cap=row?`<span class=edec style="text-align:left">${esc(row.type||'?')}${row.detail?' · '+esc(row.detail):''} — ${esc(row.source||'user')}</span>`
920
+ :(o.conn?'<span class=edec style="text-align:left;color:#fca5a5">references a missing row — re-pick</span>':'');
921
+ return `<div class=elab>Connection</div><input id=${id} class=combo data-src=connrows readonly placeholder="— none — (pick from the library)" value="${esc(row?connLabel(row):'')}" autocomplete=off>${cap}`;};
922
+ const fb=(o,html)=>`<div class="${o.conn?'connset':''}">${html}</div>`; // dim the note/detail fallback when a library row is referenced
904
923
  const dotS='<span class=epdot style="background:#facc15"></span>',dotE='<span class=epdot style="background:#f472b6"></span>'; // start=yellow, end=magenta — matches the canvas end markers
905
924
  const elev = col
906
- ? `<div class="sect f">Elevation</div>${ftFld('bos',dotS+'Bottom (BOS)',m.col.bos)}${tosFld('tosC',dotE+'Top (TOS)',m.col)}${nin('ntC',m.col.note)}${dFld('dtC',m.col)}`
907
- : `<div class="sect f">${dotS}Start</div>${tosFld('tosA','TOS',m.ends[0])}${nin('ntA',m.ends[0].note)}${dFld('dtA',m.ends[0])}
925
+ ? `<div class="sect f">Elevation</div>${ftFld('bos',dotS+'Bottom (BOS)',m.col.bos)}${tosFld('tosC',dotE+'Top (TOS)',m.col)}${cFld('cnC',m.col)}${fb(m.col,nin('ntC',m.col.note)+dFld('dtC',m.col))}`
926
+ : `<div class="sect f">${dotS}Start</div>${tosFld('tosA','TOS',m.ends[0])}${cFld('cnA',m.ends[0])}${fb(m.ends[0],nin('ntA',m.ends[0].note)+dFld('dtA',m.ends[0]))}
908
927
  <div class=divrow><hr><button class=ghost id=matchEnds>Match → both ends</button><hr></div>
909
- <div class=sect>${dotE}End</div>${tosFld('tosB','TOS',m.ends[1])}${nin('ntB',m.ends[1].note)}${dFld('dtB',m.ends[1])}`;
928
+ <div class=sect>${dotE}End</div>${tosFld('tosB','TOS',m.ends[1])}${cFld('cnB',m.ends[1])}${fb(m.ends[1],nin('ntB',m.ends[1].note)+dFld('dtB',m.ends[1]))}`;
910
929
  p.innerHTML=`<h3>Member ${esc(m.id)}</h3>
911
930
  <div class=row><label>Profile</label><div style="display:flex;gap:6px"><input id=pf class=combo data-src=profiles value="${esc(m.profile)}" style="flex:1" autocomplete=off><button id=pickProf class="ghost${(picking&&pickKind==='profile')?' on':''}" title="Pick profile by clicking a label in the drawing">⌖ pick</button></div>${(picking&&pickKind==='profile')?'<div class="hint" style="margin-top:4px;font-style:italic;color:var(--brand)">Click a profile label in the drawing…</div>':(picking&&pickKind==='detail')?'<div class="hint" style="margin-top:4px;font-style:italic;color:#a855f7">Click a detail callout in the drawing…</div>':''}</div>
912
931
  <div class="seg2 f"><button id=rBeam class="${col?'':'on'}">Beam</button><button id=rCol class="${col?'on':''}">Column</button></div>
@@ -939,18 +958,19 @@ function panel(){
939
958
  const wireDet=(id,o)=>{const i=document.getElementById(id);if(i)i.onchange=e=>edit(()=>{o.detail=e.target.value.trim();});
940
959
  const pk=document.getElementById(id+'_pk');if(pk)pk.onclick=()=>{if(picking&&pickKind==='detail'&&pickEnd===o){picking=false;pickEnd=null;}else{picking=true;pickKind='detail';pickEnd=o;}render();};
941
960
  const op=document.getElementById(id+'_open');if(op)op.onclick=()=>openPreview(o.detail);};
961
+ const wireConn=(id,o)=>{const i=document.getElementById(id);if(!i)return;i.onchange=e=>edit(()=>{const v=(e.target.value||'').trim();o.conn=(!v||v==='— none —')?'':v.split(' · ')[0];});};
942
962
  if(col){
943
963
  document.getElementById('bos').onchange=e=>edit(()=>{m.col.bos=parseLen(e.target.value);});
944
964
  wireTos('tosC',m.col);
945
965
  document.getElementById('ntC').onchange=e=>edit(()=>{m.col.note=e.target.value;});
946
- wireDet('dtC',m.col);
966
+ wireDet('dtC',m.col);wireConn('cnC',m.col);
947
967
  }else{
948
968
  wireTos('tosA',m.ends[0]);
949
969
  document.getElementById('ntA').onchange=e=>edit(()=>{m.ends[0].note=e.target.value;});
950
- wireDet('dtA',m.ends[0]);
970
+ wireDet('dtA',m.ends[0]);wireConn('cnA',m.ends[0]);
951
971
  wireTos('tosB',m.ends[1]);
952
972
  document.getElementById('ntB').onchange=e=>edit(()=>{m.ends[1].note=e.target.value;});
953
- wireDet('dtB',m.ends[1]);
973
+ wireDet('dtB',m.ends[1]);wireConn('cnB',m.ends[1]);
954
974
  document.getElementById('matchEnds').onclick=()=>edit(()=>{m.ends[1]={tos:m.ends[0].tos,note:m.ends[0].note,tosDef:m.ends[0].tosDef,detail:m.ends[0].detail,conn:m.ends[0].conn,connVerified:m.ends[0].connVerified};});
955
975
  }
956
976
  document.getElementById('del').onclick=()=>edit(()=>{P.members=P.members.filter(x=>x.id!==m.id);selIds.clear();if(selDimIds.size){P.dims=P.dims.filter(d=>!selDimIds.has(d.id));selDimIds.clear();}}); // also drop any dims a mixed marquee selected
@@ -1240,6 +1260,7 @@ addEventListener('keydown',e=>{
1240
1260
  if(e.key==='Escape'&&lightboxOpen()){closeLightbox();return;}
1241
1261
  if(e.key==='Escape'&&askAiIsOpen()){askAiClose();return;}
1242
1262
  if(e.key==='Escape'&&detailsOpen()){closeDetails();return;}
1263
+ if(e.key==='Escape'&&connLibOpen()){closeConnLib();return;}
1243
1264
  if(e.key==='Escape'&&framesOpen()){closeFrames();return;}
1244
1265
  if(e.key==='Escape'&&rfiOpen()){closeRFI();return;}
1245
1266
  if(e.key==='Escape'&&confOpen()){closeConf();return;}
@@ -1428,6 +1449,37 @@ function detailsOpen(){return document.getElementById('detailsModal').style.disp
1428
1449
  document.getElementById('detailsBtn').onclick=openDetails;
1429
1450
  document.getElementById('detailsClose').onclick=closeDetails;
1430
1451
  document.getElementById('detailsBackdrop').onclick=closeDetails;
1452
+ // --- connection library: edit connections[] (type → design detail# → per-platform component id) ---
1453
+ // The library is the vendor-neutral source; member ends reference a row via ends[].conn / col.conn.
1454
+ // connPlat scopes the editable "component id" column to one platform; the detail# is platform-neutral.
1455
+ let connPlat='tekla';const PLAT_LABEL={tekla:'Tekla',sds2:'SDS2',revit:'Revit'};
1456
+ function openConnLib(){if(!Array.isArray(C.connections))C.connections=[];renderConnTable();document.getElementById('connModal').style.display='flex';}
1457
+ function closeConnLib(){document.getElementById('connModal').style.display='none';}
1458
+ function connLibOpen(){return document.getElementById('connModal').style.display==='flex';}
1459
+ function updConnBtn(){const b=document.getElementById('connBtn');if(b)b.textContent='Connections'+((C.connections||[]).length?' ('+C.connections.length+')':'');}
1460
+ function renderConnTable(){const tb=document.getElementById('connBody');if(!tb)return;
1461
+ document.getElementById('connCompHead').textContent=(PLAT_LABEL[connPlat]||connPlat)+' id';
1462
+ const rows=C.connections||[];
1463
+ if(!rows.length){tb.innerHTML='<tr><td colspan=5 style="text-align:center;padding:32px;color:var(--mut);font-size:12px">No connections defined — click + New row to add the first.</td></tr>';updConnBtn();return;}
1464
+ const srcChip=s=>s==='drawing'?'<span class="chip assumed">drawing</span>':s==='company-rules'?'<span class=chip>company-rules</span>':'<span class=chip style="border-color:#1d4ed8;color:#93c5fd">user</span>';
1465
+ tb.innerHTML=rows.map(r=>`<tr data-id="${esc(r.id)}">`
1466
+ +`<td><input class="combo ftab-inp" data-src=conntypes data-f=type value="${esc(r.type||'')}" placeholder="moment" autocomplete=off></td>`
1467
+ +`<td><input class="combo ftab-inp" data-src=details data-f=detail value="${esc(r.detail||'')}" placeholder="e.g. 50S504" autocomplete=off></td>`
1468
+ +`<td><input class=ftab-inp data-f=target value="${esc((r.targets&&r.targets[connPlat])||'')}" placeholder="e.g. 146" autocomplete=off></td>`
1469
+ +`<td class=csrc>${srcChip(r.source||'user')}</td>`
1470
+ +`<td><button class="danger cdel" title="Remove this row">✕</button></td></tr>`).join('');
1471
+ tb.querySelectorAll('tr[data-id]').forEach(tr=>{const id=tr.dataset.id;const row=connRowById(id);if(!row)return;
1472
+ tr.querySelector('[data-f=type]').onchange=e=>edit(()=>{row.type=e.target.value.trim();});
1473
+ tr.querySelector('[data-f=detail]').onchange=e=>edit(()=>{row.detail=e.target.value.trim();});
1474
+ tr.querySelector('[data-f=target]').onchange=e=>edit(()=>{if(!row.targets)row.targets={};const v=e.target.value.trim();if(v)row.targets[connPlat]=v;else delete row.targets[connPlat];});
1475
+ tr.querySelector('.cdel').onclick=()=>{edit(()=>{C.connections=C.connections.filter(x=>x.id!==id);
1476
+ for(const pl of (C.plans||[]))for(const m of (pl.members||[])){if(m.col&&m.col.conn===id)m.col.conn='';for(const en of (m.ends||[]))if(en&&en.conn===id)en.conn='';}});renderConnTable();};});
1477
+ updConnBtn();}
1478
+ document.getElementById('connBtn').onclick=openConnLib;
1479
+ document.getElementById('connClose').onclick=closeConnLib;
1480
+ document.getElementById('connBackdrop').onclick=closeConnLib;
1481
+ document.getElementById('connNew').onclick=()=>{edit(()=>{if(!Array.isArray(C.connections))C.connections=[];C.connections.push({id:'conn'+Date.now(),type:'moment',detail:'',targets:{[connPlat]:''},source:'user'});});renderConnTable();const tb=document.getElementById('connBody');if(tb.lastElementChild)tb.lastElementChild.scrollIntoView({block:'nearest'});};
1482
+ document.getElementById('connPlatGrp').querySelectorAll('button').forEach(b=>b.onclick=()=>{connPlat=b.dataset.plat;document.getElementById('connPlatGrp').querySelectorAll('button').forEach(x=>x.classList.toggle('on',x===b));renderConnTable();});
1431
1483
  // --- create your own detail (name + screenshot) — for anything the auto-detect missed ---
1432
1484
  function readImageCompressed(file,cb){const r=new FileReader();r.onload=()=>{const im=new Image();im.onload=()=>{
1433
1485
  const max=1400;let w=im.naturalWidth,h=im.naturalHeight;const sc=Math.min(1,max/Math.max(w,h));w=Math.max(1,Math.round(w*sc));h=Math.max(1,Math.round(h*sc));
@@ -1678,12 +1730,18 @@ lbView.addEventListener('dblclick',lbReset);
1678
1730
  const COMBO_SRC={
1679
1731
  profiles:()=>profs,
1680
1732
  conntypes:()=>['moment','drag','shear','braced','pinned','fixed'],
1681
- details:()=>{const s=new Set();(P.details||[]).forEach(d=>s.add(d.text));Object.keys(C.custom_details).forEach(n=>s.add(n));return [...s].sort((a,b)=>String(a).localeCompare(String(b),undefined,{numeric:true}));}
1733
+ details:()=>{const s=new Set();(P.details||[]).forEach(d=>s.add(d.text));Object.keys(C.custom_details).forEach(n=>s.add(n));return [...s].sort((a,b)=>String(a).localeCompare(String(b),undefined,{numeric:true}));},
1734
+ connrows:()=>['— none —',...(C.connections||[]).map(r=>connLabel(r))]
1682
1735
  };
1736
+ // A connection-library row's one-line label for the per-end picker. The id is the prefix (parsed
1737
+ // back on select); type/detail follow for the human. Kept here so COMBO_SRC + cFld + wireConn agree.
1738
+ function connLabel(r){return r.id+' · '+(r.type||'?')+(r.detail?' · '+r.detail:'');}
1739
+ function connRowById(id){return (C.connections||[]).find(r=>r&&r.id===id)||null;}
1683
1740
  const comboPop=document.createElement('div');comboPop.id='comboPop';document.body.appendChild(comboPop);
1684
1741
  let comboInput=null,comboIdx=-1;
1685
1742
  function showCombo(inp){const src=COMBO_SRC[inp.dataset.src];if(!src)return;comboInput=inp;
1686
- const q=(inp.value||'').trim().toUpperCase();const opts=src().filter(o=>String(o).toUpperCase().includes(q));
1743
+ // connrows is a row PICKER (readonly), not a type-to-filter field — always offer every row.
1744
+ const q=inp.dataset.src==='connrows'?'':(inp.value||'').trim().toUpperCase();const opts=src().filter(o=>String(o).toUpperCase().includes(q));
1687
1745
  if(!opts.length){comboPop.style.display='none';return;}
1688
1746
  comboPop.innerHTML=opts.map(o=>`<div class=opt data-v="${esc(o)}">${esc(o)}</div>`).join('');
1689
1747
  const r=inp.getBoundingClientRect();comboPop.style.left=Math.round(r.left)+'px';comboPop.style.top=Math.round(r.bottom+2)+'px';comboPop.style.minWidth=Math.round(r.width)+'px';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floless/app",
3
- "version": "0.48.1",
3
+ "version": "0.49.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": {