@floless/app 0.27.0 → 0.28.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.
@@ -52792,7 +52792,7 @@ function appVersion() {
52792
52792
  return resolveVersion({
52793
52793
  isSea: isSea2(),
52794
52794
  sqVersionXml: readSqVersionXml(),
52795
- define: true ? "0.27.0" : void 0,
52795
+ define: true ? "0.28.0" : void 0,
52796
52796
  pkgVersion: readPkgVersion()
52797
52797
  });
52798
52798
  }
@@ -52802,7 +52802,7 @@ function resolveChannel(s) {
52802
52802
  return "dev";
52803
52803
  }
52804
52804
  function appChannel() {
52805
- return resolveChannel({ isSea: isSea2(), define: true ? "0.27.0" : void 0 });
52805
+ return resolveChannel({ isSea: isSea2(), define: true ? "0.28.0" : void 0 });
52806
52806
  }
52807
52807
 
52808
52808
  // oauth-presets.ts
package/dist/web/app.css CHANGED
@@ -344,6 +344,9 @@
344
344
  overflow: hidden;
345
345
  min-width: 0;
346
346
  position: relative;
347
+ /* Touch: the canvas is a one-finger-pan + pinch-zoom surface on phones (handled in app.js);
348
+ touch-action:none stops the browser claiming the gesture first. .dashboard re-enables scroll. */
349
+ touch-action: none;
347
350
  /* The whole canvas is a pannable infinite surface (drag the background / middle-mouse),
348
351
  so the grab affordance covers the entire grid-area — not just .topology, which is
349
352
  sized around the node cards and left distant background areas as a plain arrow (#115).
@@ -2900,7 +2903,7 @@ body {
2900
2903
  /* In dashboard view the pannable topology is hidden, so the canvas isn't a drag surface —
2901
2904
  drop the grab affordance the .canvas rule sets for the normal view (#115 follow-up). */
2902
2905
  .canvas.view-dashboard { cursor: default; }
2903
- .dashboard { flex: 1; min-height: 0; overflow-y: auto; padding: 18px 24px 24px; }
2906
+ .dashboard { flex: 1; min-height: 0; overflow-y: auto; padding: 18px 24px 24px; touch-action: pan-y; }
2904
2907
  .dashboard[hidden] { display: none; }
2905
2908
 
2906
2909
  /* Notices above the panels: dim note (degraded validation / warnings) and the
@@ -3006,3 +3009,19 @@ body {
3006
3009
 
3007
3010
  /* Inspect-tab panels reuse the same block styles inside the inspect body. */
3008
3011
  .ext-inspect { display: flex; flex-direction: column; gap: 10px; overflow-y: auto; min-height: 0; }
3012
+
3013
+ /* ===== MOBILE REFLOW =====
3014
+ Phone/touch access (e.g. over Tailscale). The desktop 3-column shell is wider than a phone
3015
+ and html,body{overflow:hidden} (top of file) clips the off-screen columns with no way to pan
3016
+ to them — single-finger AND pinch do nothing. On narrow screens, WITHIN the locked skin:
3017
+ (1) let the shell scroll so nothing is ever unreachable;
3018
+ (2) collapse both side panels by default (see app.js) — canvas full-width, tap the existing
3019
+ 48px stubs to reveal chat/inspect one at a time;
3020
+ (3) let the header wrap and the workflow picker shrink so it fits one phone-width row.
3021
+ Guarded by the media query → desktop layout is 100% untouched. No new fonts/colors/components. */
3022
+ @media (max-width: 760px) {
3023
+ html, body { overflow: auto; }
3024
+ .app { height: auto; min-height: 100vh; grid-template-rows: auto 1fr 44px; }
3025
+ header { flex-wrap: wrap; gap: 8px; padding: 8px 14px; }
3026
+ select, .wf-combo { min-width: 0; }
3027
+ }
package/dist/web/app.js CHANGED
@@ -31,6 +31,13 @@ function loadState() {
31
31
  if (c.left !== undefined) state.collapse.left = c.left;
32
32
  if (c.right !== undefined) state.collapse.right = c.right;
33
33
  } catch {}
34
+ // Mobile/touch: start with both side panels collapsed so the canvas is full-width and the
35
+ // desktop 3-column shell fits a phone. In-memory only — NOT persisted, so it never clobbers
36
+ // the desktop collapse preference. The user can still tap the stubs to reveal chat/inspect.
37
+ if (window.matchMedia('(max-width: 760px)').matches) {
38
+ state.collapse.left = true;
39
+ state.collapse.right = true;
40
+ }
34
41
  }
35
42
  function saveFavs() {
36
43
  document.getElementById('fav-count').textContent = state.favorites.length;
@@ -1448,6 +1455,54 @@ window.addEventListener('mouseup', () => {
1448
1455
  });
1449
1456
  $canvasEl.addEventListener('auxclick', (e) => { if (e.button === 1) e.preventDefault(); });
1450
1457
 
1458
+ // ----- Touch: one-finger pan + two-finger pinch-zoom (phones). The mouse handlers above are
1459
+ // untouched (touch events never fire from a mouse), so desktop is unaffected. touch-action:none
1460
+ // on .canvas (CSS) stops the browser hijacking the gesture and suppresses synthetic mouse events.
1461
+ let tPan = false, tPinch = false, tFromX = 0, tFromY = 0, tBaseX = 0, tBaseY = 0, pinchDist0 = 0, pinchZoom0 = 1;
1462
+ const pinchSpan = (ts) => Math.hypot(ts[0].clientX - ts[1].clientX, ts[0].clientY - ts[1].clientY);
1463
+ $canvasEl.addEventListener('touchstart', (e) => {
1464
+ if (e.touches.length === 2) {
1465
+ e.preventDefault();
1466
+ tPinch = true; tPan = false;
1467
+ pinchDist0 = pinchSpan(e.touches) || 1; pinchZoom0 = zoom;
1468
+ } else if (e.touches.length === 1) {
1469
+ // Let taps on interactive chrome through (node select, zoom toolbar, fav-bar, find, dashboard).
1470
+ if (e.target.closest('.agent-card, .canvas-toolbar, .fav-bar, .find-overlay, .dashboard')) return;
1471
+ e.preventDefault();
1472
+ tPan = true; tPinch = false;
1473
+ tFromX = e.touches[0].clientX; tFromY = e.touches[0].clientY;
1474
+ tBaseX = panX; tBaseY = panY;
1475
+ $canvasEl.classList.add('panning');
1476
+ }
1477
+ }, { passive: false });
1478
+ $canvasEl.addEventListener('touchmove', (e) => {
1479
+ if (tPinch && e.touches.length === 2) {
1480
+ e.preventDefault();
1481
+ const ratio = pinchSpan(e.touches) / pinchDist0;
1482
+ zoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, pinchZoom0 * ratio));
1483
+ autoFit = false;
1484
+ applyTransform();
1485
+ } else if (tPan && e.touches.length === 1) {
1486
+ e.preventDefault();
1487
+ panX = tBaseX + (e.touches[0].clientX - tFromX);
1488
+ panY = tBaseY + (e.touches[0].clientY - tFromY);
1489
+ autoFit = false;
1490
+ applyTransform();
1491
+ }
1492
+ }, { passive: false });
1493
+ function endTouch(e) {
1494
+ if (e.touches.length === 0) {
1495
+ tPan = false; tPinch = false; $canvasEl.classList.remove('panning');
1496
+ } else if (e.touches.length === 1 && tPinch) {
1497
+ // 2->1 finger: hand off from pinch to pan without a jump.
1498
+ tPinch = false; tPan = true;
1499
+ tFromX = e.touches[0].clientX; tFromY = e.touches[0].clientY;
1500
+ tBaseX = panX; tBaseY = panY;
1501
+ }
1502
+ }
1503
+ $canvasEl.addEventListener('touchend', endTouch);
1504
+ $canvasEl.addEventListener('touchcancel', endTouch);
1505
+
1451
1506
  // Keep the auto-fitted view fitted as the canvas resizes (a panel toggling/dragging,
1452
1507
  // the window resizing) — but never override a view the user has zoomed/panned. A
1453
1508
  // trailing debounce fires ONE clean re-fit after the resize settles (rather than on
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floless/app",
3
- "version": "0.27.0",
3
+ "version": "0.28.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": {