@floless/app 0.12.3 → 0.13.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.
- package/dist/floless-server.cjs +29 -14
- package/dist/skills/floless-app-workflows/SKILL.md +9 -2
- package/dist/web/app.css +21 -0
- package/dist/web/aware.js +69 -1
- package/dist/web/index.html +13 -5
- package/package.json +1 -1
package/dist/floless-server.cjs
CHANGED
|
@@ -52609,7 +52609,7 @@ function appVersion() {
|
|
|
52609
52609
|
return resolveVersion({
|
|
52610
52610
|
isSea: isSea2(),
|
|
52611
52611
|
sqVersionXml: readSqVersionXml(),
|
|
52612
|
-
define: true ? "0.
|
|
52612
|
+
define: true ? "0.13.0" : void 0,
|
|
52613
52613
|
pkgVersion: readPkgVersion()
|
|
52614
52614
|
});
|
|
52615
52615
|
}
|
|
@@ -52619,7 +52619,7 @@ function resolveChannel(s) {
|
|
|
52619
52619
|
return "dev";
|
|
52620
52620
|
}
|
|
52621
52621
|
function appChannel() {
|
|
52622
|
-
return resolveChannel({ isSea: isSea2(), define: true ? "0.
|
|
52622
|
+
return resolveChannel({ isSea: isSea2(), define: true ? "0.13.0" : void 0 });
|
|
52623
52623
|
}
|
|
52624
52624
|
|
|
52625
52625
|
// oauth-presets.ts
|
|
@@ -52755,6 +52755,19 @@ function withBadge(report, opts) {
|
|
|
52755
52755
|
return { ...report, html: injectBadge(report.html, opts) };
|
|
52756
52756
|
}
|
|
52757
52757
|
|
|
52758
|
+
// report-html.ts
|
|
52759
|
+
function extractReportHtml(events) {
|
|
52760
|
+
let found = null;
|
|
52761
|
+
for (const ev of events) {
|
|
52762
|
+
const data = ev?.data;
|
|
52763
|
+
if (!data || typeof data !== "object") continue;
|
|
52764
|
+
const result = data.result;
|
|
52765
|
+
const html = typeof data.html === "string" ? data.html : result && typeof result.html === "string" ? result.html : null;
|
|
52766
|
+
if (html) found = { nodeId: ev.node ?? null, html };
|
|
52767
|
+
}
|
|
52768
|
+
return found;
|
|
52769
|
+
}
|
|
52770
|
+
|
|
52758
52771
|
// index.ts
|
|
52759
52772
|
var import_node_crypto6 = require("node:crypto");
|
|
52760
52773
|
|
|
@@ -53180,7 +53193,8 @@ function deriveTriggerProgress(events, sourceNodeId2) {
|
|
|
53180
53193
|
}
|
|
53181
53194
|
var defaultDeps = {
|
|
53182
53195
|
start: (id, opts) => aware.startTrigger(id, opts),
|
|
53183
|
-
onChange: (id, snapshot) => broadcast({ type: "trigger-session-changed", id, snapshot })
|
|
53196
|
+
onChange: (id, snapshot) => broadcast({ type: "trigger-session-changed", id, snapshot }),
|
|
53197
|
+
onReport: (id, workflow, report) => broadcast({ type: "trigger-report", id, workflow, nodeId: report.nodeId, html: report.html })
|
|
53184
53198
|
};
|
|
53185
53199
|
var sessions = /* @__PURE__ */ new Map();
|
|
53186
53200
|
function isLive(s) {
|
|
@@ -53213,6 +53227,7 @@ function startSession(spec, deps = defaultDeps) {
|
|
|
53213
53227
|
const emit = () => {
|
|
53214
53228
|
if (current()) deps.onChange(spec.id, session.snapshot);
|
|
53215
53229
|
};
|
|
53230
|
+
let lastReportCount = 0;
|
|
53216
53231
|
const handle = deps.start(spec.workflow, { inputs: spec.inputs });
|
|
53217
53232
|
session.handle = handle;
|
|
53218
53233
|
handle.onTrace((events) => {
|
|
@@ -53221,6 +53236,17 @@ function startSession(spec, deps = defaultDeps) {
|
|
|
53221
53236
|
session.snapshot.firedCount = p.firedCount;
|
|
53222
53237
|
session.snapshot.lastEvent = p.lastEvent;
|
|
53223
53238
|
emit();
|
|
53239
|
+
let reportCount = 0;
|
|
53240
|
+
for (const ev of events) {
|
|
53241
|
+
const d = ev?.data;
|
|
53242
|
+
const r = d && d.result;
|
|
53243
|
+
if (ev.kind === "node-output" && d && (typeof d.html === "string" || r && typeof r.html === "string")) reportCount++;
|
|
53244
|
+
}
|
|
53245
|
+
if (reportCount > lastReportCount) {
|
|
53246
|
+
lastReportCount = reportCount;
|
|
53247
|
+
const raw = extractReportHtml(events);
|
|
53248
|
+
if (raw && raw.html) deps.onReport?.(spec.id, spec.workflow, withBadge(raw));
|
|
53249
|
+
}
|
|
53224
53250
|
});
|
|
53225
53251
|
handle.onError((msg) => {
|
|
53226
53252
|
if (!current()) return;
|
|
@@ -56812,17 +56838,6 @@ var WEB_ROOT = [(0, import_node_path20.join)(__dirname4, "web"), (0, import_node
|
|
|
56812
56838
|
) ?? (0, import_node_path20.join)(__dirname4, "..", "web");
|
|
56813
56839
|
var PORT2 = Number(process.env.PORT ?? 4317);
|
|
56814
56840
|
var HOST = "127.0.0.1";
|
|
56815
|
-
function extractReportHtml(events) {
|
|
56816
|
-
let found = null;
|
|
56817
|
-
for (const ev of events) {
|
|
56818
|
-
const data = ev?.data;
|
|
56819
|
-
if (!data || typeof data !== "object") continue;
|
|
56820
|
-
const result = data.result;
|
|
56821
|
-
const html = typeof data.html === "string" ? data.html : result && typeof result.html === "string" ? result.html : null;
|
|
56822
|
-
if (html) found = { nodeId: ev.node ?? null, html };
|
|
56823
|
-
}
|
|
56824
|
-
return found;
|
|
56825
|
-
}
|
|
56826
56841
|
var crashHandlersInstalled = false;
|
|
56827
56842
|
function installCrashHandlers() {
|
|
56828
56843
|
if (crashHandlersInstalled) return;
|
|
@@ -268,8 +268,15 @@ manifest edit + recompile, no engine. See the design doc for the Stage-1 plan.
|
|
|
268
268
|
per-app cache (`lastReportByApp`), instantly, **no run**. If nothing has run yet, it shows a
|
|
269
269
|
"click ▶ Run workflow" prompt (never a spinner). Do NOT make this trigger a run — a live run is
|
|
270
270
|
~15s and showing a spinner over stale content reads as "stuck".
|
|
271
|
-
- Rendered in a **sandboxed iframe `srcdoc`** (`sandbox="allow-same-origin
|
|
272
|
-
|
|
271
|
+
- Rendered in a **sandboxed iframe `srcdoc`** (`sandbox="allow-same-origin allow-popups
|
|
272
|
+
allow-popups-to-escape-sandbox"`, **no `allow-scripts`** → static reports only). The UI never
|
|
273
|
+
builds the HTML; it relays exactly what the exec returned. **`allow-same-origin` is load-bearing —
|
|
274
|
+
never drop it:** without it the srcdoc gets an opaque origin, and Chrome (149+) does NOT repaint an
|
|
275
|
+
opaque-origin sandboxed srcdoc on the 2nd+ assignment — and `runReport` always clears `srcdoc=''`
|
|
276
|
+
first, so the report HTML is ALWAYS a 2nd assignment → **blank viewer**. Chromium that repaints
|
|
277
|
+
reassigned srcdoc (e.g. Playwright's bundled build, and the very first render) renders fine, which
|
|
278
|
+
masks the bug — verify report-viewer changes in real Chrome, not just Playwright. Regressed once
|
|
279
|
+
(09813c8 added badge popups but replaced the whole sandbox value, dropping the flag); restored.
|
|
273
280
|
- **`Simulate` can't run an exec-data-carrying app**: stubs have no `result.<field>`, so the
|
|
274
281
|
cross-node templates (`{{ bom.result.html }}`) render undefined and the run aborts
|
|
275
282
|
(`template render: undefined value`, exit 3). `/api/run` returns that **in-band** (HTTP 200
|
package/dist/web/app.css
CHANGED
|
@@ -2059,6 +2059,27 @@ body {
|
|
|
2059
2059
|
#report-close { font-size: 18px; line-height: 1; padding: 4px 10px; }
|
|
2060
2060
|
.report-stage { position: relative; flex: 1; background: #0f172a; }
|
|
2061
2061
|
#report-frame { width: 100%; height: 100%; border: 0; display: block; background: #0f172a; }
|
|
2062
|
+
|
|
2063
|
+
/* Live "On trigger" report: a pulsing pip in the viewer subtitle + a brief edge
|
|
2064
|
+
flash on the stage when a trigger routine (e.g. tekla-selection-report) pushes a
|
|
2065
|
+
fresh report into the already-open viewer. Brand blue — within the baseline. */
|
|
2066
|
+
.live-pip {
|
|
2067
|
+
display: inline-block; width: 7px; height: 7px; margin-right: 7px; border-radius: 999px;
|
|
2068
|
+
background: #3b82f6; vertical-align: middle; animation: live-pip-pulse 1.8s ease-out infinite;
|
|
2069
|
+
}
|
|
2070
|
+
@keyframes live-pip-pulse {
|
|
2071
|
+
0% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.5); }
|
|
2072
|
+
70% { box-shadow: 0 0 0 6px rgba(59, 130, 246, 0); }
|
|
2073
|
+
100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); }
|
|
2074
|
+
}
|
|
2075
|
+
.report-live-pulse { animation: report-live-flash 0.6s ease-out; }
|
|
2076
|
+
@keyframes report-live-flash {
|
|
2077
|
+
0% { box-shadow: inset 0 0 0 2px rgba(59, 130, 246, 0.85); }
|
|
2078
|
+
100% { box-shadow: inset 0 0 0 2px rgba(59, 130, 246, 0); }
|
|
2079
|
+
}
|
|
2080
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2081
|
+
.live-pip, .report-live-pulse { animation: none; }
|
|
2082
|
+
}
|
|
2062
2083
|
.report-overlay {
|
|
2063
2084
|
position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
|
|
2064
2085
|
flex-direction: column; gap: 12px; background: rgba(7, 10, 15, 0.82);
|
package/dist/web/aware.js
CHANGED
|
@@ -2756,6 +2756,8 @@
|
|
|
2756
2756
|
if (window.flolessPanels) window.flolessPanels.refreshData(); // routine runs also refresh bindings
|
|
2757
2757
|
} else if (m.type === 'trigger-session-changed') {
|
|
2758
2758
|
applyTriggerSnapshot(m.id, m.snapshot);
|
|
2759
|
+
} else if (m.type === 'trigger-report') {
|
|
2760
|
+
applyTriggerReport(m);
|
|
2759
2761
|
} else if (m.type === 'connect-result') {
|
|
2760
2762
|
// A run's credential-reconnect card may be waiting on this integration —
|
|
2761
2763
|
// drive its success/fail state from the same SSE result.
|
|
@@ -3378,9 +3380,22 @@
|
|
|
3378
3380
|
if (!r) { loadRoutinesData(); return; } // unknown id (created in another tab) → resync
|
|
3379
3381
|
const prevFired = (r.session && r.session.firedCount) || 0;
|
|
3380
3382
|
r.session = snapshot;
|
|
3383
|
+
const fired = !!(snapshot && snapshot.firedCount > prevFired);
|
|
3384
|
+
// A new fire lands a beat BEFORE its report (the source node-output streams
|
|
3385
|
+
// first, then the exec runs ~1–3s and emits the report HTML). If the HTML
|
|
3386
|
+
// Viewer is open for this app, show a "collecting…" spinner over the last
|
|
3387
|
+
// report until applyTriggerReport paints the fresh one.
|
|
3388
|
+
const viewerOpenForApp = currentId === r.workflow && $reportModal.classList.contains('show') && !reportRunning;
|
|
3389
|
+
if (fired && viewerOpenForApp) {
|
|
3390
|
+
showTriggerCollecting();
|
|
3391
|
+
} else if (viewerOpenForApp && (snapshot.state === 'error' || snapshot.state === 'stopped') && $reportOverlay.querySelector('.spinner')) {
|
|
3392
|
+
// The session ended before this fire produced a report — don't spin forever.
|
|
3393
|
+
// Fall back to the last report if we have one, else an idle prompt.
|
|
3394
|
+
clearTriggerCollecting();
|
|
3395
|
+
}
|
|
3381
3396
|
if (!$routinesModal.classList.contains('show')) return;
|
|
3382
3397
|
renderRoutinesList();
|
|
3383
|
-
if (
|
|
3398
|
+
if (fired) flashRoutineRow(id);
|
|
3384
3399
|
}
|
|
3385
3400
|
|
|
3386
3401
|
// Briefly tint a row when it just fired. Re-applies on rapid repeats via a forced
|
|
@@ -3394,6 +3409,59 @@
|
|
|
3394
3409
|
el.classList.add('rtn-fired-flash');
|
|
3395
3410
|
}
|
|
3396
3411
|
|
|
3412
|
+
// A trigger routine (e.g. tekla-selection-report on each SelectionChange) just
|
|
3413
|
+
// produced a fresh report. Always cache it (so opening the viewer shows the
|
|
3414
|
+
// latest), and if the HTML Viewer is ALREADY open for THIS app, live-repaint it
|
|
3415
|
+
// — the user asked to watch the report update on each selection change. We never
|
|
3416
|
+
// auto-open the viewer, never hijack another app's open viewer, and never stomp
|
|
3417
|
+
// an in-flight manual run. (The live repaint is another srcdoc assignment, so it
|
|
3418
|
+
// depends on the report-frame `allow-same-origin` fix to repaint in Chrome.)
|
|
3419
|
+
function applyTriggerReport(m) {
|
|
3420
|
+
if (!m || !m.workflow || typeof m.html !== 'string') return;
|
|
3421
|
+
lastReportByApp.set(m.workflow, { nodeId: m.nodeId || null, label: 'live · on selection change', html: m.html });
|
|
3422
|
+
const open = $reportModal && $reportModal.classList.contains('show');
|
|
3423
|
+
if (!open || currentId !== m.workflow || reportRunning) return;
|
|
3424
|
+
paintReport(m.html); // unhides Share + clears the overlay
|
|
3425
|
+
const t = new Date().toLocaleTimeString();
|
|
3426
|
+
$reportSub.innerHTML =
|
|
3427
|
+
`<span class="live-pip" aria-hidden="true"></span>Live · updated ${escapeHtml(t)} · on selection change — rendered from the node's output, never composed by the UI.`;
|
|
3428
|
+
pulseReportFrame();
|
|
3429
|
+
}
|
|
3430
|
+
|
|
3431
|
+
// Brief edge-flash on the report stage when a live update lands, so the change
|
|
3432
|
+
// is noticeable. Forced reflow re-arms it on rapid repeats. CSS honors
|
|
3433
|
+
// prefers-reduced-motion.
|
|
3434
|
+
function pulseReportFrame() {
|
|
3435
|
+
const stage = document.getElementById('report-stage');
|
|
3436
|
+
if (!stage) return;
|
|
3437
|
+
stage.classList.remove('report-live-pulse');
|
|
3438
|
+
void stage.offsetWidth;
|
|
3439
|
+
stage.classList.add('report-live-pulse');
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
// A trigger fired (new selection) but its report is still computing on the host.
|
|
3443
|
+
// Show a spinner over the viewer — the previous report dims behind the overlay —
|
|
3444
|
+
// until applyTriggerReport paints the fresh one. Built with DOM nodes (no
|
|
3445
|
+
// innerHTML); reuses the same .spinner the manual run overlay uses.
|
|
3446
|
+
function showTriggerCollecting() {
|
|
3447
|
+
$reportOverlay.hidden = false;
|
|
3448
|
+
$reportOverlay.replaceChildren();
|
|
3449
|
+
const sp = mkEl('div', 'spinner');
|
|
3450
|
+
const msg = mkEl('div', null, 'New selection — collecting report…');
|
|
3451
|
+
$reportOverlay.append(sp, msg);
|
|
3452
|
+
}
|
|
3453
|
+
|
|
3454
|
+
// The trigger session errored/stopped while a "collecting…" spinner was up (a
|
|
3455
|
+
// fire whose report never arrived) — clear it instead of spinning forever:
|
|
3456
|
+
// revert to the last cached report if there is one, else an idle prompt.
|
|
3457
|
+
function clearTriggerCollecting() {
|
|
3458
|
+
const cached = currentId && lastReportByApp.get(currentId);
|
|
3459
|
+
if (cached && cached.html) { paintReport(cached.html); return; }
|
|
3460
|
+
$reportOverlay.replaceChildren();
|
|
3461
|
+
$reportOverlay.hidden = false;
|
|
3462
|
+
$reportOverlay.append(mkEl('div', null, 'Listening for the next selection…'));
|
|
3463
|
+
}
|
|
3464
|
+
|
|
3397
3465
|
function renderRoutinesList() {
|
|
3398
3466
|
const $list = document.getElementById('routines-list');
|
|
3399
3467
|
const $quota = document.getElementById('rtn-quota');
|
package/dist/web/index.html
CHANGED
|
@@ -295,11 +295,19 @@
|
|
|
295
295
|
</div>
|
|
296
296
|
</div>
|
|
297
297
|
<div class="report-stage" id="report-stage">
|
|
298
|
-
<!--
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
298
|
+
<!-- sandbox flags, each load-bearing:
|
|
299
|
+
• allow-same-origin — REQUIRED. Without it the srcdoc document gets an
|
|
300
|
+
opaque origin, and Chrome (149+) fails to repaint an opaque-origin
|
|
301
|
+
sandboxed srcdoc on the 2nd+ assignment: every report after the first
|
|
302
|
+
render (and runReport always clears srcdoc='' first, so it's ALWAYS a
|
|
303
|
+
2nd assignment) shows blank. Dropping this flag was a regression in the
|
|
304
|
+
badge-popups change (09813c8) that replaced the whole sandbox value.
|
|
305
|
+
Verified blank in Chrome 149; fine in Chromium that repaints reassigned srcdoc.
|
|
306
|
+
• allow-popups (+ escape) — the report's own links (the "Made with FloLess"
|
|
307
|
+
badge) must open in a real, top-level tab.
|
|
308
|
+
Scripts stay BLOCKED (no allow-scripts), so even though the report is now
|
|
309
|
+
same-origin it has no JS to touch the app page — static reports only. -->
|
|
310
|
+
<iframe id="report-frame" sandbox="allow-same-origin allow-popups allow-popups-to-escape-sandbox" title="Report"></iframe>
|
|
303
311
|
<div class="report-overlay" id="report-overlay" hidden></div>
|
|
304
312
|
</div>
|
|
305
313
|
</div>
|