@floless/app 0.12.3 → 0.14.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 +98 -3
- 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.14.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.14.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
|
@@ -1904,7 +1904,7 @@
|
|
|
1904
1904
|
openNotesPopover({ component: 'app', version, anchorEl: $appUpdate, onUpdate: applyAppUpdate });
|
|
1905
1905
|
};
|
|
1906
1906
|
refreshUpdate();
|
|
1907
|
-
setInterval(refreshUpdate,
|
|
1907
|
+
setInterval(refreshUpdate, 60 * 60 * 1000); // hourly backstop; the focus re-check (below) is the fast path
|
|
1908
1908
|
}
|
|
1909
1909
|
|
|
1910
1910
|
// Dev-override helper — the ONLY forced-visible path. Reveals a pill with a fixed
|
|
@@ -2002,7 +2002,34 @@
|
|
|
2002
2002
|
openNotesPopover({ component: 'aware', version, anchorEl: $awareUpdate, onUpdate: applyAwareUpdate });
|
|
2003
2003
|
};
|
|
2004
2004
|
refreshAwareUpdate();
|
|
2005
|
-
setInterval(refreshAwareUpdate,
|
|
2005
|
+
setInterval(refreshAwareUpdate, 60 * 60 * 1000); // hourly backstop; the focus re-check (below) is the fast path
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
// Re-check both update pills the instant the tab/window regains focus, so returning
|
|
2009
|
+
// to a long-open tab surfaces a freshly-released version immediately instead of waiting
|
|
2010
|
+
// out the hourly backstop poll above — the gap that used to make users hard-refresh (F5)
|
|
2011
|
+
// to see a new release. Guards: only when the tab is actually visible (never poll for a
|
|
2012
|
+
// backgrounded tab); throttled so rapid focus/blur — or the interval firing alongside —
|
|
2013
|
+
// can't storm the feed; and skipped while a pill is mid-apply (disabled, "Updating…") so
|
|
2014
|
+
// a focus event never stomps an in-flight update. Each refresh is already a no-op when its
|
|
2015
|
+
// pill is absent (dev hides them unless force-flagged), so this is safe to wire whenever
|
|
2016
|
+
// either pill exists.
|
|
2017
|
+
if ($appUpdate || $awareUpdate) {
|
|
2018
|
+
const UPDATE_FOCUS_THROTTLE_MS = 10 * 1000;
|
|
2019
|
+
let lastUpdateCheck = Date.now(); // the on-load checks above just ran — don't immediately re-fire
|
|
2020
|
+
const recheckUpdatesOnFocus = () => {
|
|
2021
|
+
if (document.visibilityState !== 'visible') return;
|
|
2022
|
+
// Offline: a failing fetch lands in refreshUpdate's catch and would briefly hide an
|
|
2023
|
+
// already-shown pill — skip and let the next focus/interval (back online) re-check.
|
|
2024
|
+
if (!navigator.onLine) return;
|
|
2025
|
+
const now = Date.now();
|
|
2026
|
+
if (now - lastUpdateCheck < UPDATE_FOCUS_THROTTLE_MS) return;
|
|
2027
|
+
lastUpdateCheck = now;
|
|
2028
|
+
if ($appUpdate && !$appUpdate.disabled) refreshUpdate();
|
|
2029
|
+
if ($awareUpdate && !$awareUpdate.disabled) refreshAwareUpdate();
|
|
2030
|
+
};
|
|
2031
|
+
document.addEventListener('visibilitychange', recheckUpdatesOnFocus);
|
|
2032
|
+
window.addEventListener('focus', recheckUpdatesOnFocus);
|
|
2006
2033
|
}
|
|
2007
2034
|
|
|
2008
2035
|
// The AWARE version label, once it's a notes affordance (post in-place upgrade),
|
|
@@ -2756,6 +2783,8 @@
|
|
|
2756
2783
|
if (window.flolessPanels) window.flolessPanels.refreshData(); // routine runs also refresh bindings
|
|
2757
2784
|
} else if (m.type === 'trigger-session-changed') {
|
|
2758
2785
|
applyTriggerSnapshot(m.id, m.snapshot);
|
|
2786
|
+
} else if (m.type === 'trigger-report') {
|
|
2787
|
+
applyTriggerReport(m);
|
|
2759
2788
|
} else if (m.type === 'connect-result') {
|
|
2760
2789
|
// A run's credential-reconnect card may be waiting on this integration —
|
|
2761
2790
|
// drive its success/fail state from the same SSE result.
|
|
@@ -3378,9 +3407,22 @@
|
|
|
3378
3407
|
if (!r) { loadRoutinesData(); return; } // unknown id (created in another tab) → resync
|
|
3379
3408
|
const prevFired = (r.session && r.session.firedCount) || 0;
|
|
3380
3409
|
r.session = snapshot;
|
|
3410
|
+
const fired = !!(snapshot && snapshot.firedCount > prevFired);
|
|
3411
|
+
// A new fire lands a beat BEFORE its report (the source node-output streams
|
|
3412
|
+
// first, then the exec runs ~1–3s and emits the report HTML). If the HTML
|
|
3413
|
+
// Viewer is open for this app, show a "collecting…" spinner over the last
|
|
3414
|
+
// report until applyTriggerReport paints the fresh one.
|
|
3415
|
+
const viewerOpenForApp = currentId === r.workflow && $reportModal.classList.contains('show') && !reportRunning;
|
|
3416
|
+
if (fired && viewerOpenForApp) {
|
|
3417
|
+
showTriggerCollecting();
|
|
3418
|
+
} else if (viewerOpenForApp && (snapshot.state === 'error' || snapshot.state === 'stopped') && $reportOverlay.querySelector('.spinner')) {
|
|
3419
|
+
// The session ended before this fire produced a report — don't spin forever.
|
|
3420
|
+
// Fall back to the last report if we have one, else an idle prompt.
|
|
3421
|
+
clearTriggerCollecting();
|
|
3422
|
+
}
|
|
3381
3423
|
if (!$routinesModal.classList.contains('show')) return;
|
|
3382
3424
|
renderRoutinesList();
|
|
3383
|
-
if (
|
|
3425
|
+
if (fired) flashRoutineRow(id);
|
|
3384
3426
|
}
|
|
3385
3427
|
|
|
3386
3428
|
// Briefly tint a row when it just fired. Re-applies on rapid repeats via a forced
|
|
@@ -3394,6 +3436,59 @@
|
|
|
3394
3436
|
el.classList.add('rtn-fired-flash');
|
|
3395
3437
|
}
|
|
3396
3438
|
|
|
3439
|
+
// A trigger routine (e.g. tekla-selection-report on each SelectionChange) just
|
|
3440
|
+
// produced a fresh report. Always cache it (so opening the viewer shows the
|
|
3441
|
+
// latest), and if the HTML Viewer is ALREADY open for THIS app, live-repaint it
|
|
3442
|
+
// — the user asked to watch the report update on each selection change. We never
|
|
3443
|
+
// auto-open the viewer, never hijack another app's open viewer, and never stomp
|
|
3444
|
+
// an in-flight manual run. (The live repaint is another srcdoc assignment, so it
|
|
3445
|
+
// depends on the report-frame `allow-same-origin` fix to repaint in Chrome.)
|
|
3446
|
+
function applyTriggerReport(m) {
|
|
3447
|
+
if (!m || !m.workflow || typeof m.html !== 'string') return;
|
|
3448
|
+
lastReportByApp.set(m.workflow, { nodeId: m.nodeId || null, label: 'live · on selection change', html: m.html });
|
|
3449
|
+
const open = $reportModal && $reportModal.classList.contains('show');
|
|
3450
|
+
if (!open || currentId !== m.workflow || reportRunning) return;
|
|
3451
|
+
paintReport(m.html); // unhides Share + clears the overlay
|
|
3452
|
+
const t = new Date().toLocaleTimeString();
|
|
3453
|
+
$reportSub.innerHTML =
|
|
3454
|
+
`<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.`;
|
|
3455
|
+
pulseReportFrame();
|
|
3456
|
+
}
|
|
3457
|
+
|
|
3458
|
+
// Brief edge-flash on the report stage when a live update lands, so the change
|
|
3459
|
+
// is noticeable. Forced reflow re-arms it on rapid repeats. CSS honors
|
|
3460
|
+
// prefers-reduced-motion.
|
|
3461
|
+
function pulseReportFrame() {
|
|
3462
|
+
const stage = document.getElementById('report-stage');
|
|
3463
|
+
if (!stage) return;
|
|
3464
|
+
stage.classList.remove('report-live-pulse');
|
|
3465
|
+
void stage.offsetWidth;
|
|
3466
|
+
stage.classList.add('report-live-pulse');
|
|
3467
|
+
}
|
|
3468
|
+
|
|
3469
|
+
// A trigger fired (new selection) but its report is still computing on the host.
|
|
3470
|
+
// Show a spinner over the viewer — the previous report dims behind the overlay —
|
|
3471
|
+
// until applyTriggerReport paints the fresh one. Built with DOM nodes (no
|
|
3472
|
+
// innerHTML); reuses the same .spinner the manual run overlay uses.
|
|
3473
|
+
function showTriggerCollecting() {
|
|
3474
|
+
$reportOverlay.hidden = false;
|
|
3475
|
+
$reportOverlay.replaceChildren();
|
|
3476
|
+
const sp = mkEl('div', 'spinner');
|
|
3477
|
+
const msg = mkEl('div', null, 'New selection — collecting report…');
|
|
3478
|
+
$reportOverlay.append(sp, msg);
|
|
3479
|
+
}
|
|
3480
|
+
|
|
3481
|
+
// The trigger session errored/stopped while a "collecting…" spinner was up (a
|
|
3482
|
+
// fire whose report never arrived) — clear it instead of spinning forever:
|
|
3483
|
+
// revert to the last cached report if there is one, else an idle prompt.
|
|
3484
|
+
function clearTriggerCollecting() {
|
|
3485
|
+
const cached = currentId && lastReportByApp.get(currentId);
|
|
3486
|
+
if (cached && cached.html) { paintReport(cached.html); return; }
|
|
3487
|
+
$reportOverlay.replaceChildren();
|
|
3488
|
+
$reportOverlay.hidden = false;
|
|
3489
|
+
$reportOverlay.append(mkEl('div', null, 'Listening for the next selection…'));
|
|
3490
|
+
}
|
|
3491
|
+
|
|
3397
3492
|
function renderRoutinesList() {
|
|
3398
3493
|
const $list = document.getElementById('routines-list');
|
|
3399
3494
|
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>
|