@floless/app 0.25.2 → 0.26.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.
@@ -52777,7 +52777,7 @@ function appVersion() {
52777
52777
  return resolveVersion({
52778
52778
  isSea: isSea2(),
52779
52779
  sqVersionXml: readSqVersionXml(),
52780
- define: true ? "0.25.2" : void 0,
52780
+ define: true ? "0.26.0" : void 0,
52781
52781
  pkgVersion: readPkgVersion()
52782
52782
  });
52783
52783
  }
@@ -52787,7 +52787,7 @@ function resolveChannel(s) {
52787
52787
  return "dev";
52788
52788
  }
52789
52789
  function appChannel() {
52790
- return resolveChannel({ isSea: isSea2(), define: true ? "0.25.2" : void 0 });
52790
+ return resolveChannel({ isSea: isSea2(), define: true ? "0.26.0" : void 0 });
52791
52791
  }
52792
52792
 
52793
52793
  // oauth-presets.ts
@@ -54211,6 +54211,14 @@ async function runRoutine(id) {
54211
54211
  runId: res.runId ?? void 0,
54212
54212
  durationMs: Date.now() - startedAt.getTime()
54213
54213
  });
54214
+ try {
54215
+ const report = withBadge(extractReportHtml(events, r.workflow));
54216
+ if (report?.html) {
54217
+ broadcast({ type: "routine-report", id: r.id, workflow: r.workflow, nodeId: report.nodeId, html: report.html });
54218
+ }
54219
+ } catch (err) {
54220
+ console.warn(`[floless] routine "${r.id}" report surfacing failed (run still recorded ${outcome.status}):`, err instanceof Error ? err.message : err);
54221
+ }
54214
54222
  } catch (err) {
54215
54223
  const msg = err instanceof Error ? err.message : String(err);
54216
54224
  recordRun(r, {
package/dist/web/app.css CHANGED
@@ -344,6 +344,12 @@
344
344
  overflow: hidden;
345
345
  min-width: 0;
346
346
  position: relative;
347
+ /* The whole canvas is a pannable infinite surface (drag the background / middle-mouse),
348
+ so the grab affordance covers the entire grid-area — not just .topology, which is
349
+ sized around the node cards and left distant background areas as a plain arrow (#115).
350
+ Interactive controls (toolbar buttons, template chips, find input, node cards) carry
351
+ their own cursor, so grab never reads as draggable over a clickable target. */
352
+ cursor: grab;
347
353
  }
348
354
  /* While middle-mouse panning, force the grab cursor over the whole canvas. */
349
355
  .canvas.panning, .canvas.panning * { cursor: grabbing !important; }
@@ -378,9 +384,9 @@
378
384
  overflow:visible also means no scrollbar — drag the background / middle-mouse
379
385
  to pan, zoom, or Fit this infinite canvas. */
380
386
  overflow: visible;
381
- cursor: grab;
382
387
  }
383
- /* (the grabbing cursor during a pan is already forced by `.canvas.panning *` above) */
388
+ /* The grab cursor lives on the parent `.canvas` (covers the full surface, #115); the
389
+ grabbing cursor during a pan is forced by `.canvas.panning *` above. */
384
390
 
385
391
  /* Canvas chrome (top label, bottom hint + Templates bar) paints ABOVE the
386
392
  transform-panned topology with an OPAQUE background, so dragging the canvas
@@ -2864,6 +2870,9 @@ body {
2864
2870
  .canvas.view-dashboard .fav-bar,
2865
2871
  .canvas.view-dashboard .notes-strip,
2866
2872
  .canvas.view-dashboard .find-overlay { display: none; }
2873
+ /* In dashboard view the pannable topology is hidden, so the canvas isn't a drag surface —
2874
+ drop the grab affordance the .canvas rule sets for the normal view (#115 follow-up). */
2875
+ .canvas.view-dashboard { cursor: default; }
2867
2876
  .dashboard { flex: 1; min-height: 0; overflow-y: auto; padding: 18px 24px 24px; }
2868
2877
  .dashboard[hidden] { display: none; }
2869
2878
 
package/dist/web/app.js CHANGED
@@ -1422,11 +1422,15 @@ $canvasEl.addEventListener('mousedown', (e) => {
1422
1422
  startPan(e);
1423
1423
  });
1424
1424
  // Left-drag the empty canvas background also pans — so the infinite, scrollbar-less
1425
- // canvas is navigable with any pointer (trackpads have no middle button). Excludes
1426
- // node cards (those keep click-to-select + their HTML5 drag); the toolbar/hint/
1427
- // fav-bar/find aren't inside .topology, so they're naturally excluded.
1428
- $topology.addEventListener('mousedown', (e) => {
1429
- if (e.button !== 0 || e.target.closest('.agent-card')) return;
1425
+ // canvas is navigable with any pointer (trackpads have no middle button). On the full
1426
+ // `.canvas` (not just `.topology`) so panning reaches every background area, matching the
1427
+ // middle-mouse handler above (#115). Bail on interactive chrome so clicks/drags there still
1428
+ // work: node cards (their own select + HTML5 drag), the zoom toolbar, the Templates
1429
+ // fav-bar, and the find input. Non-interactive chrome (panel label, hint, notes) still pans.
1430
+ // `.dashboard` (the custom-panels view that replaces the topology) is excluded too — it has
1431
+ // its own scroll/click surface and the topology it would pan is hidden in that view.
1432
+ $canvasEl.addEventListener('mousedown', (e) => {
1433
+ if (e.button !== 0 || e.target.closest('.agent-card, .canvas-toolbar, .fav-bar, .find-overlay, .dashboard')) return;
1430
1434
  e.preventDefault();
1431
1435
  startPan(e);
1432
1436
  });
package/dist/web/aware.js CHANGED
@@ -1240,6 +1240,9 @@
1240
1240
  const ovStop = document.querySelector('.overlay-stop');
1241
1241
  if (ovStop) { ovStop.disabled = true; ovStop.textContent = 'Stopping…'; }
1242
1242
  try { await api(`/api/trigger-run/${encodeURIComponent(ft.appId)}/stop`, { method: 'POST' }); } catch { /* best effort — the session may already be gone */ }
1243
+ // Clear the canvas "running" pulse just like the regular-run stop below — the
1244
+ // early return here previously skipped it, leaving nodes stuck on `running` (#116).
1245
+ clearNodeStatus();
1243
1246
  // A deliberate stop needs no toast — the subtitle change is the confirmation.
1244
1247
  paintForegroundStopped('stopped');
1245
1248
  syncRunControls();
@@ -3336,6 +3339,8 @@
3336
3339
  else applyTriggerSnapshot(m.id, m.snapshot);
3337
3340
  } else if (m.type === 'trigger-report') {
3338
3341
  applyTriggerReport(m);
3342
+ } else if (m.type === 'routine-report') {
3343
+ applyRoutineReport(m);
3339
3344
  } else if (m.type === 'connect-result') {
3340
3345
  // A run's credential-reconnect card may be waiting on this integration —
3341
3346
  // drive its success/fail state from the same SSE result.
@@ -3506,7 +3511,7 @@
3506
3511
  if (!tpls.length) { $favChipRow.innerHTML = ''; $favBarEmpty.style.display = 'block'; return; }
3507
3512
  $favBarEmpty.style.display = 'none';
3508
3513
  $favChipRow.innerHTML = tpls.map((t) => `
3509
- <div class="fav-chip" data-tpl="${escapeAttr(t.id)}" data-tip="Click to use · ✎ rename · ${escapeAttr(t.category)} · ${escapeAttr((t.node.agent || t.node.kind) + (t.node.command ? '/' + t.node.command : ''))}">
3514
+ <div class="fav-chip" data-tpl="${escapeAttr(t.id)}" data-tip="Click to use · ${escapeAttr(t.category)} · ${escapeAttr((t.node.agent || t.node.kind) + (t.node.command ? '/' + t.node.command : ''))}">
3510
3515
  <span class="cat">${escapeHtml(t.category)}</span>
3511
3516
  <span class="name">${escapeHtml(t.name)}</span>
3512
3517
  <span class="edit" data-tip="Rename / recategorize" aria-label="Edit template">✎</span>
@@ -4295,6 +4300,32 @@
4295
4300
  pulseReportFrame();
4296
4301
  }
4297
4302
 
4303
+ // A SCHEDULED routine (a timed `aware app run`) produced a report (#113). Mirrors
4304
+ // applyTriggerReport for the timed path: always refresh the cache so "View report" shows
4305
+ // the latest result, and if THIS app's viewer is open, live-repaint it. An unattended
4306
+ // routine (e.g. hourly) shouldn't beep on every tick, so the toast fires ONLY when the
4307
+ // report HTML actually changed (e.g. an exception-monitor flips from "all clear" to
4308
+ // "exception detected"). Never auto-opens the viewer, never hijacks another app's open
4309
+ // viewer, never stomps an in-flight manual run or a live foreground session.
4310
+ function applyRoutineReport(m) {
4311
+ if (!m || !m.workflow || typeof m.html !== 'string') return;
4312
+ const prev = lastReportByApp.get(m.workflow);
4313
+ const changed = !prev || prev.html !== m.html;
4314
+ lastReportByApp.set(m.workflow, { nodeId: m.nodeId || null, label: 'scheduled', html: m.html });
4315
+ const open = $reportModal && $reportModal.classList.contains('show');
4316
+ if (open && currentId === m.workflow && !reportRunning && !foregroundTrigger) {
4317
+ paintReport(m.html); // unhides Share + clears the overlay
4318
+ const t = new Date().toLocaleTimeString();
4319
+ $reportSub.innerHTML =
4320
+ `<span class="live-pip" aria-hidden="true"></span>Scheduled &middot; updated ${escapeHtml(t)} — rendered from the node's output, never composed by the UI.`;
4321
+ pulseReportFrame();
4322
+ }
4323
+ if (changed) {
4324
+ const app = apps.get(m.workflow);
4325
+ showToast(`Report updated — ${app ? app.displayName : m.workflow}`, 'info');
4326
+ }
4327
+ }
4328
+
4298
4329
  // Brief edge-flash on the report stage when a live update lands, so the change
4299
4330
  // is noticeable. Forced reflow re-arms it on rapid repeats. CSS honors
4300
4331
  // prefers-reduced-motion.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floless/app",
3
- "version": "0.25.2",
3
+ "version": "0.26.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": {