@floless/app 0.16.0 → 0.16.1
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/dist/floless-server.cjs +2 -2
- package/dist/web/app.css +25 -9
- package/dist/web/app.js +72 -15
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
package/dist/floless-server.cjs
CHANGED
|
@@ -52666,7 +52666,7 @@ function appVersion() {
|
|
|
52666
52666
|
return resolveVersion({
|
|
52667
52667
|
isSea: isSea2(),
|
|
52668
52668
|
sqVersionXml: readSqVersionXml(),
|
|
52669
|
-
define: true ? "0.16.
|
|
52669
|
+
define: true ? "0.16.1" : void 0,
|
|
52670
52670
|
pkgVersion: readPkgVersion()
|
|
52671
52671
|
});
|
|
52672
52672
|
}
|
|
@@ -52676,7 +52676,7 @@ function resolveChannel(s) {
|
|
|
52676
52676
|
return "dev";
|
|
52677
52677
|
}
|
|
52678
52678
|
function appChannel() {
|
|
52679
|
-
return resolveChannel({ isSea: isSea2(), define: true ? "0.16.
|
|
52679
|
+
return resolveChannel({ isSea: isSea2(), define: true ? "0.16.1" : void 0 });
|
|
52680
52680
|
}
|
|
52681
52681
|
|
|
52682
52682
|
// oauth-presets.ts
|
package/dist/web/app.css
CHANGED
|
@@ -109,9 +109,10 @@
|
|
|
109
109
|
border-bottom: 1px solid var(--border);
|
|
110
110
|
/* Let the header's flex children shrink rather than force the whole grid wider
|
|
111
111
|
than the viewport (the old cause of a page-wide horizontal scrollbar): the
|
|
112
|
-
workflow picker truncates
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
workflow picker is the shrink sink — it truncates down to its 180px floor
|
|
113
|
+
while the run-spine buttons stay on one line (never wrapping). Below that
|
|
114
|
+
floor a horizontal scrollbar is the final fallback (the run spine stays
|
|
115
|
+
reachable by scrolling), at viewport widths narrower than the design (#53). */
|
|
115
116
|
min-width: 0;
|
|
116
117
|
}
|
|
117
118
|
/* Brand + view-toggle stay full size; .controls is the shrink sink instead. */
|
|
@@ -130,6 +131,10 @@
|
|
|
130
131
|
.brand .name { font-size: 15px; font-weight: 700; letter-spacing: 0.08em; }
|
|
131
132
|
.brand .tag { font-size: 11px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.12em; }
|
|
132
133
|
.controls { display: flex; align-items: center; gap: 10px; min-width: 0; }
|
|
134
|
+
/* Action buttons keep their labels on ONE line — under a tight header they may
|
|
135
|
+
compress, but the workflow picker (the shrink sink) gives way first; a button
|
|
136
|
+
must never wrap to two lines (#53 follow-up). */
|
|
137
|
+
.controls button { white-space: nowrap; }
|
|
133
138
|
/* Vertical divider separating "which workflow + reference" from the run spine
|
|
134
139
|
(state → Compile → Simulate → Run). Uses the existing border token. */
|
|
135
140
|
.ctl-sep { width: 1px; height: 20px; background: var(--border-strong); flex: none; }
|
|
@@ -351,8 +356,16 @@
|
|
|
351
356
|
padding: 24px 36px;
|
|
352
357
|
position: relative;
|
|
353
358
|
min-height: 0;
|
|
354
|
-
|
|
359
|
+
/* This element is the transformed "world": it's translated/scaled for pan+zoom,
|
|
360
|
+
so it must NOT clip its own content — clipping here happens BEFORE the scale,
|
|
361
|
+
so zooming out would shrink an already-clipped view instead of revealing the
|
|
362
|
+
whole graph. The stable clip box is the parent `.canvas` (overflow:hidden).
|
|
363
|
+
overflow:visible also means no scrollbar — drag the background / middle-mouse
|
|
364
|
+
to pan, zoom, or Fit this infinite canvas. */
|
|
365
|
+
overflow: visible;
|
|
366
|
+
cursor: grab;
|
|
355
367
|
}
|
|
368
|
+
/* (the grabbing cursor during a pan is already forced by `.canvas.panning *` above) */
|
|
356
369
|
|
|
357
370
|
/* LINEAR */
|
|
358
371
|
.topology:not(.dag) .agent-card { flex: 0 0 210px; }
|
|
@@ -1429,13 +1442,16 @@
|
|
|
1429
1442
|
#rtn-delete-confirm:hover { color: var(--err); border-color: var(--err); background: color-mix(in srgb, var(--err) 10%, transparent); }
|
|
1430
1443
|
|
|
1431
1444
|
/* ========== WORKFLOW COMBOBOX (searchable, provider-grouped) ========== */
|
|
1432
|
-
/* Shrinkable in the header flex row:
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1445
|
+
/* Shrinkable in the header flex row: the trigger's 300px width is the combo's
|
|
1446
|
+
preferred size (flex-basis:auto reads it), and flex-shrink gives space back
|
|
1447
|
+
down to a 180px floor when the header is tight, so the run spine fits without
|
|
1448
|
+
wrapping its buttons. The name ellipsizes within. The 300px lives on the
|
|
1449
|
+
TRIGGER (not flex-basis on the combo) so the combo's intrinsic size matches
|
|
1450
|
+
its flex size — otherwise the shrink leaks onto the buttons and wraps them. */
|
|
1451
|
+
.wf-combo { position: relative; display: inline-flex; flex: 0 1 auto; min-width: 180px; }
|
|
1436
1452
|
.wf-trigger {
|
|
1437
1453
|
display: inline-flex; align-items: center; gap: 8px;
|
|
1438
|
-
min-width: 0; width: 100%;
|
|
1454
|
+
min-width: 0; width: 300px; max-width: 100%;
|
|
1439
1455
|
background: var(--surface-2); color: var(--text);
|
|
1440
1456
|
border: 1px solid var(--border-strong); border-radius: 4px;
|
|
1441
1457
|
padding: 7px 12px; font-family: var(--ui); font-size: 12px; cursor: pointer;
|
package/dist/web/app.js
CHANGED
|
@@ -226,7 +226,7 @@ function renderTopology() {
|
|
|
226
226
|
} else {
|
|
227
227
|
selectAgent(state.selectedAgentId);
|
|
228
228
|
}
|
|
229
|
-
|
|
229
|
+
fitView(); // auto-fit the whole workflow into the canvas per render (never clipped)
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
function renderLinearTopology(p) {
|
|
@@ -354,7 +354,17 @@ function cardEl(id, ports) {
|
|
|
354
354
|
`;
|
|
355
355
|
div.onclick = (e) => {
|
|
356
356
|
if (e.target.closest('.fav-btn')) return;
|
|
357
|
-
|
|
357
|
+
// A plain card click only SELECTS. Revealing a collapsed Inspect panel is
|
|
358
|
+
// reserved for the explicit "inspect ▸" affordance (handler below) — a general
|
|
359
|
+
// node click must not pop open a panel the user deliberately collapsed (#52).
|
|
360
|
+
selectAgent(id);
|
|
361
|
+
};
|
|
362
|
+
// The "inspect ▸" hint is the explicit inspect affordance: select AND reveal the
|
|
363
|
+
// Inspect panel if it's collapsed. stopPropagation so the card's plain-select
|
|
364
|
+
// onclick doesn't also fire (it would just re-select the same node).
|
|
365
|
+
div.querySelector('.inspect-hint').onclick = (e) => {
|
|
366
|
+
e.stopPropagation();
|
|
367
|
+
selectAgent(id, { reveal: true });
|
|
358
368
|
};
|
|
359
369
|
div.querySelector('.fav-btn').onclick = (e) => {
|
|
360
370
|
e.stopPropagation();
|
|
@@ -1167,8 +1177,16 @@ function showToast(msg, type) {
|
|
|
1167
1177
|
const ZOOM_LEVELS = [0.5, 0.6, 0.75, 0.85, 1.0, 1.15, 1.3, 1.5, 1.75, 2.0];
|
|
1168
1178
|
const MIN_ZOOM = ZOOM_LEVELS[0];
|
|
1169
1179
|
const MAX_ZOOM = ZOOM_LEVELS[ZOOM_LEVELS.length - 1];
|
|
1180
|
+
// Auto-fit/Fit may zoom further out than the interactive MIN_ZOOM so a large graph
|
|
1181
|
+
// in a tight canvas is shown whole rather than floored and cut. Floored at 20% for
|
|
1182
|
+
// readability — below that, hold here and let the user pan (never scale to noise).
|
|
1183
|
+
const FIT_MIN_ZOOM = 0.2;
|
|
1170
1184
|
let zoom = 1; // continuous scale (fit produces arbitrary values, not just presets)
|
|
1171
1185
|
let panX = 0, panY = 0; // pan offset in screen px (transform-origin is the canvas centre)
|
|
1186
|
+
// The view auto-fits the WHOLE workflow into the canvas (so nodes are never clipped)
|
|
1187
|
+
// and keeps re-fitting as the canvas resizes (panels toggle, window resize) — UNTIL
|
|
1188
|
+
// the user takes manual control (zoom / pan / 100%), after which their view is kept.
|
|
1189
|
+
let autoFit = true;
|
|
1172
1190
|
|
|
1173
1191
|
function applyTransform() {
|
|
1174
1192
|
$topology.style.transformOrigin = '50% 50%';
|
|
@@ -1189,17 +1207,19 @@ function zoomBy(delta) {
|
|
|
1189
1207
|
: ([...ZOOM_LEVELS].reverse().find((z) => z < zoom - 1e-3) ?? MIN_ZOOM);
|
|
1190
1208
|
if (Math.abs(next - zoom) < 1e-3) return;
|
|
1191
1209
|
zoom = next;
|
|
1210
|
+
autoFit = false; // user chose a zoom — stop auto-fitting on resize
|
|
1192
1211
|
applyTransform();
|
|
1193
1212
|
}
|
|
1194
1213
|
|
|
1195
|
-
// Reset to 100% + recentre.
|
|
1196
|
-
// matches the transform it clears (otherwise the % label would go stale on switch).
|
|
1214
|
+
// Reset to 100% + recentre. Pure — callers decide whether it's a user action.
|
|
1197
1215
|
function resetView() { zoom = 1; panX = 0; panY = 0; applyTransform(); }
|
|
1198
1216
|
|
|
1199
1217
|
// Fit the whole workflow into the visible canvas (zooms in OR out), then recentre.
|
|
1200
1218
|
// Measures the real node cluster — offset* are unscaled layout coords, so they're
|
|
1201
|
-
// unaffected by the current transform.
|
|
1202
|
-
|
|
1219
|
+
// unaffected by the current transform. `maxZoom` caps the scale: the auto-fit on
|
|
1220
|
+
// load/resize passes 1 so a small workflow stays 1:1 (never blown up); the explicit
|
|
1221
|
+
// Fit button passes the full range (may zoom in to fill).
|
|
1222
|
+
function fitToScreen(maxZoom = MAX_ZOOM) {
|
|
1203
1223
|
const cards = [...$topology.querySelectorAll('.agent-card')];
|
|
1204
1224
|
if (!cards.length) { resetView(); return; }
|
|
1205
1225
|
let minL = Infinity, minT = Infinity, maxR = -Infinity, maxB = -Infinity;
|
|
@@ -1213,16 +1233,27 @@ function fitToScreen() {
|
|
|
1213
1233
|
if (contentW <= 0 || contentH <= 0) { resetView(); return; }
|
|
1214
1234
|
const pad = 40;
|
|
1215
1235
|
const fit = Math.min(($topology.clientWidth - pad * 2) / contentW, ($topology.clientHeight - pad * 2) / contentH);
|
|
1216
|
-
|
|
1217
|
-
|
|
1236
|
+
// Floor at FIT_MIN_ZOOM (below the interactive MIN_ZOOM) so a big graph in a
|
|
1237
|
+
// tight canvas is fully visible, never clipped — that's the whole point of Fit.
|
|
1238
|
+
zoom = Math.max(FIT_MIN_ZOOM, Math.min(maxZoom, fit));
|
|
1239
|
+
// Centre the content's bounding box in the viewport. transform-origin is the
|
|
1240
|
+
// topology centre and translate sits OUTSIDE the scale, so pan = (viewport
|
|
1241
|
+
// centre − content centre) × zoom. Required because the DAG grid sits top-left
|
|
1242
|
+
// in the topology — a 0 pan would leave it off-centre and clip the edge nodes.
|
|
1243
|
+
panX = ($topology.clientWidth / 2 - (minL + maxR) / 2) * zoom;
|
|
1244
|
+
panY = ($topology.clientHeight / 2 - (minT + maxB) / 2) * zoom;
|
|
1218
1245
|
applyTransform();
|
|
1219
1246
|
}
|
|
1220
1247
|
|
|
1248
|
+
// Auto-fit used on render + canvas resize: show the whole workflow (capped at 100%
|
|
1249
|
+
// so small graphs aren't blown up) and stay in auto-fit mode so it tracks resizes.
|
|
1250
|
+
function fitView() { fitToScreen(1); autoFit = true; }
|
|
1251
|
+
|
|
1221
1252
|
document.getElementById('zoom-out').onclick = () => zoomBy(-1);
|
|
1222
1253
|
document.getElementById('zoom-in').onclick = () => zoomBy(+1);
|
|
1223
|
-
document.getElementById('zoom-reset').onclick = () => resetView();
|
|
1254
|
+
document.getElementById('zoom-reset').onclick = () => { resetView(); autoFit = false; };
|
|
1224
1255
|
const $zoomFit = document.getElementById('zoom-fit');
|
|
1225
|
-
if ($zoomFit) $zoomFit.onclick = () => fitToScreen();
|
|
1256
|
+
if ($zoomFit) $zoomFit.onclick = () => { fitToScreen(); autoFit = true; };
|
|
1226
1257
|
|
|
1227
1258
|
// Ctrl/Cmd + scroll wheel = zoom on canvas (skips OS-level browser zoom)
|
|
1228
1259
|
let zoomAccum = 0;
|
|
@@ -1241,18 +1272,32 @@ document.querySelector('.canvas').addEventListener('wheel', (e) => {
|
|
|
1241
1272
|
// cursor) because translate sits OUTSIDE the scale in the transform list.
|
|
1242
1273
|
const $canvasEl = document.querySelector('.canvas');
|
|
1243
1274
|
let panning = false, panFromX = 0, panFromY = 0, panBaseX = 0, panBaseY = 0;
|
|
1244
|
-
|
|
1245
|
-
if (e.button !== 1) return;
|
|
1246
|
-
e.preventDefault();
|
|
1275
|
+
function startPan(e) {
|
|
1247
1276
|
panning = true;
|
|
1248
1277
|
panFromX = e.clientX; panFromY = e.clientY;
|
|
1249
1278
|
panBaseX = panX; panBaseY = panY;
|
|
1250
1279
|
$canvasEl.classList.add('panning');
|
|
1280
|
+
}
|
|
1281
|
+
// Middle-mouse drag anywhere on the canvas pans (the classic gesture).
|
|
1282
|
+
$canvasEl.addEventListener('mousedown', (e) => {
|
|
1283
|
+
if (e.button !== 1) return;
|
|
1284
|
+
e.preventDefault();
|
|
1285
|
+
startPan(e);
|
|
1286
|
+
});
|
|
1287
|
+
// Left-drag the empty canvas background also pans — so the infinite, scrollbar-less
|
|
1288
|
+
// canvas is navigable with any pointer (trackpads have no middle button). Excludes
|
|
1289
|
+
// node cards (those keep click-to-select + their HTML5 drag); the toolbar/hint/
|
|
1290
|
+
// fav-bar/find aren't inside .topology, so they're naturally excluded.
|
|
1291
|
+
$topology.addEventListener('mousedown', (e) => {
|
|
1292
|
+
if (e.button !== 0 || e.target.closest('.agent-card')) return;
|
|
1293
|
+
e.preventDefault();
|
|
1294
|
+
startPan(e);
|
|
1251
1295
|
});
|
|
1252
1296
|
window.addEventListener('mousemove', (e) => {
|
|
1253
1297
|
if (!panning) return;
|
|
1254
1298
|
panX = panBaseX + (e.clientX - panFromX);
|
|
1255
1299
|
panY = panBaseY + (e.clientY - panFromY);
|
|
1300
|
+
autoFit = false; // user dragged the view — stop auto-fitting on resize
|
|
1256
1301
|
applyTransform();
|
|
1257
1302
|
});
|
|
1258
1303
|
window.addEventListener('mouseup', () => {
|
|
@@ -1262,6 +1307,18 @@ window.addEventListener('mouseup', () => {
|
|
|
1262
1307
|
});
|
|
1263
1308
|
$canvasEl.addEventListener('auxclick', (e) => { if (e.button === 1) e.preventDefault(); });
|
|
1264
1309
|
|
|
1310
|
+
// Keep the auto-fitted view fitted as the canvas resizes (a panel toggling/dragging,
|
|
1311
|
+
// the window resizing) — but never override a view the user has zoomed/panned. A
|
|
1312
|
+
// trailing debounce fires ONE clean re-fit after the resize settles (rather than on
|
|
1313
|
+
// every frame of a panel's 0.25s slide, which reads as jitter). The transform
|
|
1314
|
+
// fitToScreen sets doesn't change layout size, so this can't loop.
|
|
1315
|
+
let _fitTimer = 0;
|
|
1316
|
+
new ResizeObserver(() => {
|
|
1317
|
+
if (!autoFit) return;
|
|
1318
|
+
clearTimeout(_fitTimer);
|
|
1319
|
+
_fitTimer = setTimeout(() => { if (autoFit) fitToScreen(1); }, 120);
|
|
1320
|
+
}).observe($canvasEl);
|
|
1321
|
+
|
|
1265
1322
|
/* ============= INTEGRATIONS ============= */
|
|
1266
1323
|
// The real, server-backed Integrations window lives in aware.js (openIntegrations/
|
|
1267
1324
|
// renderIntegrations are reassigned there from /api/integrations). app.js only owns
|
|
@@ -1292,8 +1349,8 @@ document.addEventListener('keydown', (e) => {
|
|
|
1292
1349
|
if (cmd && e.key.toLowerCase() === 'i') { e.preventDefault(); openIntegrations(); return; }
|
|
1293
1350
|
if (cmd && (e.key === '=' || e.key === '+')) { e.preventDefault(); zoomBy(+1); return; }
|
|
1294
1351
|
if (cmd && e.key === '-') { e.preventDefault(); zoomBy(-1); return; }
|
|
1295
|
-
if (cmd && e.key === '0') { e.preventDefault(); resetView(); return; }
|
|
1296
|
-
if (e.key === 'Home') { e.preventDefault(); fitToScreen(); return; }
|
|
1352
|
+
if (cmd && e.key === '0') { e.preventDefault(); resetView(); autoFit = false; return; }
|
|
1353
|
+
if (e.key === 'Home') { e.preventDefault(); fitToScreen(); autoFit = true; return; }
|
|
1297
1354
|
if (e.key === 'Escape') {
|
|
1298
1355
|
if ($findOverlay.classList.contains('show')) closeFind();
|
|
1299
1356
|
if ($integrationsModal.classList.contains('show')) $integrationsModal.classList.remove('show');
|
package/dist/web/index.html
CHANGED
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
~/.floless/ui/extensions.json here; the canvas children hide via
|
|
115
115
|
.canvas.view-dashboard). Composed by the terminal AI, rendered by us. -->
|
|
116
116
|
<div class="dashboard" id="dashboard" hidden></div>
|
|
117
|
-
<div class="hint" id="canvas-hint">Click any node to inspect. Star ★ a node to save it as a reusable Template.</div>
|
|
117
|
+
<div class="hint" id="canvas-hint">Click any node to inspect. Star ★ a node to save it as a reusable Template. Drag the background to pan — or press Home to fit.</div>
|
|
118
118
|
<div class="fav-bar" id="fav-bar">
|
|
119
119
|
<div class="fav-bar-label"><span class="star">★</span><span>Templates</span></div>
|
|
120
120
|
<div class="fav-chip-row" id="fav-chip-row"></div>
|