@floless/app 0.22.1 → 0.23.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.
@@ -52729,7 +52729,7 @@ function appVersion() {
52729
52729
  return resolveVersion({
52730
52730
  isSea: isSea2(),
52731
52731
  sqVersionXml: readSqVersionXml(),
52732
- define: true ? "0.22.1" : void 0,
52732
+ define: true ? "0.23.1" : void 0,
52733
52733
  pkgVersion: readPkgVersion()
52734
52734
  });
52735
52735
  }
@@ -52739,7 +52739,7 @@ function resolveChannel(s) {
52739
52739
  return "dev";
52740
52740
  }
52741
52741
  function appChannel() {
52742
- return resolveChannel({ isSea: isSea2(), define: true ? "0.22.1" : void 0 });
52742
+ return resolveChannel({ isSea: isSea2(), define: true ? "0.23.1" : void 0 });
52743
52743
  }
52744
52744
 
52745
52745
  // oauth-presets.ts
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: floless-app-steel-from-drawings
3
- description: This skill should be used to turn a structural steel drawing into an interactive 3D model in floless.app — the "Steel from Drawings" reader. Triggers on a pending floless `rebake` request for the `steel-from-drawings` app (queued from its "Re-read & re-bake ▸" button), or asks like "read this framing plan into 3D", "turn this steel drawing into a model", "bake my drawing into steel-from-drawings". It teaches the host AI to READ a steel drawing (PDF/image) with its own vision, extract a schematic 3D scene (grids, members, sizes, takeoff) in the viewer-3d scene schema, bake it INLINE into the app's `view` node, recompile, and ask the user to approve — all at COMPOSE time (no model in the run path, no API key). Also bakes the same scene into the `steel-to-ifc` companion so the model exports to a universal IFC file (open in Tekla, SDS2, Revit, Navisworks). Triggers also on "export this to IFC", "bake to Tekla/SDS2/Revit".
3
+ description: This skill should be used to turn a structural steel drawing into an interactive 3D model in floless.app — the "Steel from Drawings" reader. Triggers on a pending floless `rebake` request for the `steel-from-drawings` app (queued from its "Re-read & re-bake ▸" button), or asks like "read this framing plan into 3D", "turn this steel drawing into a model", "bake my drawing into steel-from-drawings". It teaches the host AI to READ a steel drawing (PDF/image) with its own vision, extract a schematic 3D scene (grids, members, sizes, takeoff) in the viewer-3d scene schema, bake it INLINE into the app's `view` node, recompile, and ask the user to approve — all at COMPOSE time (no model in the run path, no API key). Also bakes the same scene into the `steel-to-ifc` companion (universal IFC file) and the `steel-to-tekla` companion (native Tekla parts via the Open API, needs Tekla open). Triggers also on "export this to IFC", "bake to Tekla/SDS2/Revit", "bake into Tekla".
4
4
  metadata:
5
5
  version: 0.1.0
6
6
  ---
@@ -17,11 +17,12 @@ and no AI API key**: the brain is you, in the terminal (the FloLess thin-UI cont
17
17
  and **for `steel-from-drawings` it supersedes the generic re-bake** — you bake the scene INLINE into
18
18
  the `.flo`'s `view` node, not into `config.yaml`).
19
19
 
20
- **Bake-out companion (`steel-to-ifc`).** A sibling app writes the SAME scene to a universal **IFC**
21
- file (IfcColumn/IfcBeam on the real grid) — open it in Tekla, SDS2, Revit or Navisworks; no Tekla
22
- needed to export. When you bake a scene, bake the *identical* scene into `steel-to-ifc` too (loop
23
- step 6) so the IFC matches the 3D the user confirmed. The whole loop: **read the drawing → see it in
24
- 3D (`steel-from-drawings`) bake it out to IFC (`steel-to-ifc`) finish in your detailing tool.**
20
+ **Bake-out companions.** Two sibling apps consume the SAME scene: **`steel-to-ifc`** writes a
21
+ universal **IFC** file (open in Tekla, SDS2, Revit, Navisworks; no Tekla needed), and
22
+ **`steel-to-tekla`** creates **native Tekla members** directly via the Open API (needs Tekla open).
23
+ When you bake a scene, bake the *identical* scene into whichever of these are installed (loop steps
24
+ 6–7) so every output matches the 3D the user confirmed. The whole loop: **read the drawing → see it
25
+ in 3D (`steel-from-drawings`) → bake it out (an IFC file and/or native Tekla parts) → finish in your tool.**
25
26
 
26
27
  > **Pasted a request?** If the user pastes a message beginning with a `[floless-request type=rebake
27
28
  > id=…]` marker, that's a request copied from the FloLess Dashboard — resolve it via
@@ -51,11 +52,19 @@ step 6) so the IFC matches the 3D the user confirmed. The whole loop: **read the
51
52
  present), then `aware app compile ~/.aware/apps/steel-to-ifc`. This keeps the exported IFC matching
52
53
  the 3D the user confirmed. (`steel-to-ifc` writes a real IFC file — IfcColumn/IfcBeam on the grid —
53
54
  openable in Tekla, SDS2, Revit or Navisworks; it never touches a Tekla model, so no Tekla needed.)
54
- 7. **Hand the approval to the user.** Tell them what you read (e.g. "read 6 columns + 8 beams on grids
55
+ 7. **Bake the SAME scene into `steel-to-tekla` (native Tekla parts), if it's installed.** Check
56
+ `~/.aware/apps/steel-to-tekla/`. If present, replace the `scene:` under that app's `bake-tekla` node
57
+ `config.args` with the IDENTICAL scene (installed + `demos/steel-to-tekla/`), set `config.version`
58
+ to the user's RUNNING Tekla version (e.g. "2026.0" — the Open API connects to a version-specific
59
+ channel, so a wrong version silently finds no model), then `aware app compile
60
+ ~/.aware/apps/steel-to-tekla`. It creates a real Tekla member per element via the Open API (mode:
61
+ write, snapshot — Undo reverts the whole bake); needs Tekla OPEN with a model. Profiles fall back
62
+ to a sized rectangular section when the environment lacks the drawing's catalogue profile.
63
+ 8. **Hand the approval to the user.** Tell them what you read (e.g. "read 6 columns + 8 beams on grids
55
64
  A–C / 1–2, heights assumed 4.5 m — review in Inspect → Code, then Compile/Approve, then ▶ Run to
56
- see the 3D; run **steel-to-ifc** to export the IFC"). The human eyeballing the read in 3D **is the
57
- safety gate**. Never auto-run.
58
- 8. **Clear the request.** `DELETE http://localhost:<port>/api/requests/<id>` once done (if from one).
65
+ see the 3D; run **steel-to-ifc** for an IFC file or **steel-to-tekla** for native Tekla parts").
66
+ The human eyeballing the read in 3D **is the safety gate**. Never auto-run.
67
+ 9. **Clear the request.** `DELETE http://localhost:<port>/api/requests/<id>` once done (if from one).
59
68
 
60
69
  ## Scene schema (the bake target — exactly what `viewer-3d` renders)
61
70
 
package/dist/web/app.css CHANGED
@@ -1799,6 +1799,9 @@
1799
1799
  `hidden` attribute, so it showed even when unsupported (dev/non-Windows). Restore
1800
1800
  the hide with a class+attribute selector (higher specificity than .menu-theme). */
1801
1801
  .menu-startup[hidden] { display: none; }
1802
+ /* Same footgun: .menu-item is display:flex, so JS-hiding the fullscreen row on
1803
+ browsers without the Fullscreen API needs an explicit hide to win over flex. */
1804
+ #menu-fullscreen[hidden] { display: none; }
1802
1805
  .theme-toggle {
1803
1806
  display: flex;
1804
1807
  gap: 2px;
@@ -1865,6 +1868,66 @@
1865
1868
  to { opacity: 1; transform: translateY(0); }
1866
1869
  }
1867
1870
 
1871
+ /* ========== FULLSCREEN NUDGE ==========
1872
+ One-time "need more space?" prompt, bottom-right. Deliberately shares the
1873
+ toast/whats-new notification language (surface card + 3px accent left rail)
1874
+ so it reads as a calm FloLess notice, not a marketing card — no glow, no
1875
+ gradient, no oversized shadow. */
1876
+ .fullscreen-nudge {
1877
+ position: fixed;
1878
+ right: 20px;
1879
+ bottom: 60px; /* clears the footer — matches the toast anchor */
1880
+ z-index: 210; /* above toasts (200): they share this corner */
1881
+ display: flex;
1882
+ align-items: flex-start;
1883
+ gap: 10px;
1884
+ width: 290px;
1885
+ background: var(--surface);
1886
+ border: 1px solid var(--border-strong);
1887
+ border-left: 3px solid var(--accent);
1888
+ border-radius: 6px;
1889
+ padding: 13px 12px 14px 15px;
1890
+ box-shadow: 0 12px 32px rgba(0,0,0,0.45);
1891
+ animation: fsn-in 0.16s ease-out;
1892
+ }
1893
+ /* .fullscreen-nudge is display:flex, which overrides the UA [hidden]{display:none}
1894
+ — restore the hide (same footgun the .menu-startup row hit). */
1895
+ .fullscreen-nudge[hidden] { display: none; }
1896
+ .fsn-text { flex: 1; min-width: 0; }
1897
+ .fsn-title { font-size: 13px; font-weight: 600; color: var(--text); line-height: 1.35; }
1898
+ .fsn-sub { font-size: 11.5px; color: var(--text-muted); line-height: 1.5; margin-top: 4px; }
1899
+ .fsn-go {
1900
+ margin-top: 11px;
1901
+ background: var(--accent);
1902
+ color: #fff;
1903
+ border: 1px solid var(--accent);
1904
+ border-radius: 4px;
1905
+ padding: 6px 12px;
1906
+ font-size: 11.5px;
1907
+ font-weight: 600;
1908
+ cursor: pointer;
1909
+ }
1910
+ .fsn-go:hover { background: var(--accent-bright); border-color: var(--accent-bright); box-shadow: 0 0 14px var(--accent-glow); }
1911
+ .fsn-dismiss {
1912
+ flex: none;
1913
+ background: transparent;
1914
+ border: none;
1915
+ color: var(--text-dim);
1916
+ font-size: 13px;
1917
+ line-height: 1;
1918
+ padding: 2px 4px;
1919
+ margin: -2px -2px 0 0;
1920
+ cursor: pointer;
1921
+ border-radius: 3px;
1922
+ }
1923
+ .fsn-dismiss:hover { color: var(--text); background: var(--surface-2); }
1924
+ @keyframes fsn-in {
1925
+ from { opacity: 0; transform: translateY(6px); }
1926
+ to { opacity: 1; transform: none; }
1927
+ }
1928
+ /* Calm, not flashy — honour reduced-motion by appearing without the fade/slide. */
1929
+ @media (prefers-reduced-motion: reduce) { .fullscreen-nudge { animation: none; } }
1930
+
1868
1931
  /* ========== FIND OVERLAY ========== */
1869
1932
  .find-overlay {
1870
1933
  position: absolute;
package/dist/web/app.js CHANGED
@@ -1042,6 +1042,7 @@ function handleMenuAction(action) {
1042
1042
  case 'integrations': openIntegrations(); break;
1043
1043
  case 'routines': openRoutines(); break;
1044
1044
  case 'report-issue': openReportIssue(); break;
1045
+ case 'fullscreen': toggleFullscreen(); break;
1045
1046
  }
1046
1047
  }
1047
1048
 
@@ -1186,6 +1187,96 @@ if ($startupToggle) {
1186
1187
  // e.g. the user disabling the task in Windows Settings).
1187
1188
  refreshStartup();
1188
1189
 
1190
+ /* ============= FULLSCREEN ============= */
1191
+ // Browser Fullscreen API on the root element — gives the 3-col workspace the whole
1192
+ // screen. The ≡ menu row and a one-time "more space?" nudge both call toggleFullscreen.
1193
+ // The row's icon/label are kept honest by `fullscreenchange` (covers Esc and other
1194
+ // out-of-band exits). If the API is unsupported we hide the row and never nudge —
1195
+ // the app's "hide, never show a dead control" rule (mirrors the startup row).
1196
+
1197
+ const FS_NUDGE_KEY = 'floless-fullscreen-nudge-v1';
1198
+ const $fsRow = document.getElementById('menu-fullscreen');
1199
+ const $fsIcon = document.getElementById('menu-fullscreen-icon');
1200
+ const $fsLabel = document.getElementById('menu-fullscreen-label');
1201
+ const $fsNudge = document.getElementById('fullscreen-nudge');
1202
+
1203
+ // Lucide maximize / minimize (same icon family as the other menu rows).
1204
+ const FS_ICON_ENTER = '<svg viewBox="0 0 24 24"><path d="M8 3H5a2 2 0 0 0-2 2v3"/><path d="M21 8V5a2 2 0 0 0-2-2h-3"/><path d="M3 16v3a2 2 0 0 0 2 2h3"/><path d="M16 21h3a2 2 0 0 0 2-2v-3"/></svg>';
1205
+ const FS_ICON_EXIT = '<svg viewBox="0 0 24 24"><path d="M8 3v3a2 2 0 0 1-2 2H3"/><path d="M21 8h-3a2 2 0 0 1-2-2V3"/><path d="M3 16h3a2 2 0 0 1 2 2v3"/><path d="M16 21v-3a2 2 0 0 1 2-2h3"/></svg>';
1206
+
1207
+ function fullscreenSupported() {
1208
+ return !!(document.fullscreenEnabled && document.documentElement.requestFullscreen);
1209
+ }
1210
+ function isFullscreen() { return !!document.fullscreenElement; }
1211
+
1212
+ function syncFullscreenUi() {
1213
+ const on = isFullscreen();
1214
+ if ($fsLabel) $fsLabel.textContent = on ? 'Exit full screen' : 'Enter full screen';
1215
+ if ($fsIcon) $fsIcon.innerHTML = on ? FS_ICON_EXIT : FS_ICON_ENTER;
1216
+ // Once the user IS fullscreen (by any means), the nudge has served its purpose —
1217
+ // retire it for good so it never reappears in a later session.
1218
+ if (on) dismissFsNudge(true);
1219
+ }
1220
+
1221
+ async function toggleFullscreen() {
1222
+ const wasOn = isFullscreen();
1223
+ try {
1224
+ if (wasOn) await document.exitFullscreen();
1225
+ else await document.documentElement.requestFullscreen();
1226
+ } catch {
1227
+ // The user can't act on a blocked request — a warn toast names what failed.
1228
+ showToast(wasOn ? "Couldn't exit fullscreen" : "Couldn't enter fullscreen", 'warn');
1229
+ }
1230
+ }
1231
+
1232
+ // Retire the nudge. `remember` persists the one-time flag so it never shows again.
1233
+ function dismissFsNudge(remember) {
1234
+ if ($fsNudge && $fsNudge.isConnected) {
1235
+ $fsNudge.hidden = true;
1236
+ $fsNudge.remove(); // keep the a11y tree clean once it's gone
1237
+ }
1238
+ if (remember) { try { localStorage.setItem(FS_NUDGE_KEY, '1'); } catch {} }
1239
+ }
1240
+
1241
+ function nudgeAlreadySeen() {
1242
+ try { return localStorage.getItem(FS_NUDGE_KEY) === '1'; } catch { return false; }
1243
+ }
1244
+
1245
+ // One-time nudge: only when the window is clearly under-using the screen, the app
1246
+ // isn't already fullscreen, no what's-new strip is competing for the corner, and it
1247
+ // was never dismissed/used. Delayed ~2s so the layout settles first.
1248
+ function maybeShowFsNudge() {
1249
+ if (nudgeAlreadySeen()) return;
1250
+ setTimeout(() => {
1251
+ if (!$fsNudge || !$fsNudge.isConnected || !$fsNudge.hidden) return;
1252
+ if (nudgeAlreadySeen() || isFullscreen()) return;
1253
+ const wn = document.getElementById('whats-new-panel');
1254
+ if (wn && !wn.hidden) return; // don't stack on the post-update what's-new strip
1255
+ const h = window.innerHeight;
1256
+ const avail = (window.screen && window.screen.availHeight) || h;
1257
+ if (h < 400) return; // genuinely cramped window — a card is just noise
1258
+ if (h > avail * 0.85) return; // window already uses most of the screen
1259
+ $fsNudge.hidden = false;
1260
+ }, 2000);
1261
+ }
1262
+
1263
+ if (!fullscreenSupported()) {
1264
+ if ($fsRow) $fsRow.hidden = true; // hide the control; never nudge on an unsupported browser
1265
+ } else {
1266
+ document.addEventListener('fullscreenchange', syncFullscreenUi);
1267
+ syncFullscreenUi();
1268
+ // Query the two buttons independently — a present #fullscreen-nudge doesn't by
1269
+ // itself guarantee its children survive a future HTML refactor.
1270
+ const $fsGo = document.getElementById('fsn-go');
1271
+ const $fsDismiss = document.getElementById('fsn-dismiss');
1272
+ if ($fsGo) $fsGo.addEventListener('click', () => {
1273
+ dismissFsNudge(true);
1274
+ toggleFullscreen(); // synchronous within the click → counts as a user gesture
1275
+ });
1276
+ if ($fsDismiss) $fsDismiss.addEventListener('click', () => dismissFsNudge(true));
1277
+ if ($fsNudge) maybeShowFsNudge();
1278
+ }
1279
+
1189
1280
  /* ============= TOAST ============= */
1190
1281
 
1191
1282
  function showToast(msg, type) {
@@ -168,6 +168,22 @@
168
168
  <div class="whats-new-detail"><div class="whats-new-detail-inner"></div></div>
169
169
  </div>
170
170
 
171
+ <!-- One-time "need more space?" nudge (mirrors Excel's prompt). Shown ~2s after
172
+ load ONLY when the window is clearly under-using the screen, the app isn't
173
+ already fullscreen, the post-update what's-new strip isn't already showing,
174
+ and it was never dismissed/used (localStorage). position:fixed, so it doesn't disturb the
175
+ .app grid; placed here so Tab order is main → nudge → footer. Calm
176
+ notification styling (shares the toast/whats-new accent rail) — never an ad;
177
+ one dismiss/use retires it for good. aria-live=polite (not alert): passive. -->
178
+ <aside id="fullscreen-nudge" class="fullscreen-nudge" aria-label="Fullscreen suggestion" aria-live="polite" hidden>
179
+ <div class="fsn-text">
180
+ <div class="fsn-title">Need more working space?</div>
181
+ <div class="fsn-sub">Go fullscreen to give FloLess the whole screen.</div>
182
+ <button type="button" class="fsn-go" id="fsn-go">Go fullscreen</button>
183
+ </div>
184
+ <button type="button" class="fsn-dismiss" id="fsn-dismiss" aria-label="Dismiss fullscreen suggestion">✕</button>
185
+ </aside>
186
+
171
187
  <footer>
172
188
  <div class="status">
173
189
  <span class="stat"><span class="dot"></span><span class="stat-val">runtime online</span></span>
@@ -269,6 +285,17 @@
269
285
  <span class="menu-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/><line x1="4" x2="4" y1="22" y2="15"/></svg></span>
270
286
  <span class="menu-label">Report an issue</span>
271
287
  </button>
288
+ <!-- Browser fullscreen toggle — gives the 3-col workspace the whole screen.
289
+ An action (not a stored pref: browsers won't restore fullscreen on load),
290
+ so it's a .menu-item button, not a switch. Icon + label flip with the live
291
+ fullscreen state via `fullscreenchange`. Hidden by JS when the Fullscreen
292
+ API is unavailable — hide, never show a dead control. No F11 kbd hint: F11
293
+ is the browser's own fullscreen, which is separate from the Fullscreen API
294
+ and wouldn't keep this control's state in sync. -->
295
+ <button class="menu-item" data-action="fullscreen" id="menu-fullscreen" role="menuitem">
296
+ <span class="menu-icon" aria-hidden="true" id="menu-fullscreen-icon"><svg viewBox="0 0 24 24"><path d="M8 3H5a2 2 0 0 0-2 2v3"/><path d="M21 8V5a2 2 0 0 0-2-2h-3"/><path d="M3 16v3a2 2 0 0 0 2 2h3"/><path d="M16 21h3a2 2 0 0 0 2-2v-3"/></svg></span>
297
+ <span class="menu-label" id="menu-fullscreen-label">Enter full screen</span>
298
+ </button>
272
299
  <!-- Start-on-login + Theme are sibling machine preferences (no divider between
273
300
  them — UX review 2026-05-31). Start-on-login toggles the per-user logon
274
301
  Scheduled Task that keeps the local server alive across logins; hidden unless
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floless/app",
3
- "version": "0.22.1",
3
+ "version": "0.23.1",
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": {