@floless/app 0.83.0 → 0.85.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.
@@ -6317,14 +6317,14 @@ var require_tools = __commonJS({
6317
6317
  }
6318
6318
  return point < 32 ? JSON.stringify(str2) : '"' + result + '"';
6319
6319
  }
6320
- function asJson(obj, msg, num3, time) {
6320
+ function asJson(obj, msg, num4, time) {
6321
6321
  if (asJsonChan.hasSubscribers === false) {
6322
- return _asJson.call(this, obj, msg, num3, time);
6322
+ return _asJson.call(this, obj, msg, num4, time);
6323
6323
  }
6324
6324
  const store = { instance: this, arguments };
6325
- return asJsonChan.traceSync(_asJson, store, this, obj, msg, num3, time);
6325
+ return asJsonChan.traceSync(_asJson, store, this, obj, msg, num4, time);
6326
6326
  }
6327
- function _asJson(obj, msg, num3, time) {
6327
+ function _asJson(obj, msg, num4, time) {
6328
6328
  const stringify2 = this[stringifySym];
6329
6329
  const stringifySafe = this[stringifySafeSym];
6330
6330
  const stringifiers = this[stringifiersSym];
@@ -6334,7 +6334,7 @@ var require_tools = __commonJS({
6334
6334
  const formatters = this[formattersSym];
6335
6335
  const messageKey = this[messageKeySym];
6336
6336
  const errorKey = this[errorKeySym];
6337
- let data = this[lsCacheSym][num3] + time;
6337
+ let data = this[lsCacheSym][num4] + time;
6338
6338
  data = data + chindings;
6339
6339
  let value;
6340
6340
  if (formatters.log) {
@@ -6959,7 +6959,7 @@ var require_proto = __commonJS({
6959
6959
  function defaultMixinMergeStrategy(mergeObject, mixinObject) {
6960
6960
  return Object.assign(mixinObject, mergeObject);
6961
6961
  }
6962
- function write(_obj, msg, num3) {
6962
+ function write(_obj, msg, num4) {
6963
6963
  const t = this[timeSym]();
6964
6964
  const mixin = this[mixinSym];
6965
6965
  const errorKey = this[errorKeySym];
@@ -6981,12 +6981,12 @@ var require_proto = __commonJS({
6981
6981
  }
6982
6982
  }
6983
6983
  if (mixin) {
6984
- obj = mixinMergeStrategy(obj, mixin(obj, num3, this));
6984
+ obj = mixinMergeStrategy(obj, mixin(obj, num4, this));
6985
6985
  }
6986
- const s = this[asJsonSym](obj, msg, num3, t);
6986
+ const s = this[asJsonSym](obj, msg, num4, t);
6987
6987
  const stream = this[streamSym];
6988
6988
  if (stream[needsMetadataGsym] === true) {
6989
- stream.lastLevel = num3;
6989
+ stream.lastLevel = num4;
6990
6990
  stream.lastObj = obj;
6991
6991
  stream.lastMsg = msg;
6992
6992
  stream.lastTime = t.slice(this[timeSliceIndexSym]);
@@ -8513,13 +8513,13 @@ var require_serializer = __commonJS({
8513
8513
  return "" + integer;
8514
8514
  }
8515
8515
  asNumber(i) {
8516
- const num3 = Number(i);
8517
- if (num3 !== num3) {
8516
+ const num4 = Number(i);
8517
+ if (num4 !== num4) {
8518
8518
  throw new Error(`The value "${i}" cannot be converted to a number.`);
8519
- } else if (num3 === Infinity || num3 === -Infinity) {
8519
+ } else if (num4 === Infinity || num4 === -Infinity) {
8520
8520
  return "null";
8521
8521
  } else {
8522
- return "" + num3;
8522
+ return "" + num4;
8523
8523
  }
8524
8524
  }
8525
8525
  asBoolean(bool) {
@@ -24673,9 +24673,9 @@ var require_semver = __commonJS({
24673
24673
  } else {
24674
24674
  this.prerelease = m[4].split(".").map((id) => {
24675
24675
  if (/^[0-9]+$/.test(id)) {
24676
- const num3 = +id;
24677
- if (num3 >= 0 && num3 < MAX_SAFE_INTEGER) {
24678
- return num3;
24676
+ const num4 = +id;
24677
+ if (num4 >= 0 && num4 < MAX_SAFE_INTEGER) {
24678
+ return num4;
24679
24679
  }
24680
24680
  }
24681
24681
  return id;
@@ -41036,24 +41036,24 @@ var require_dist5 = __commonJS({
41036
41036
  var DAY = HOUR * 24;
41037
41037
  var YEAR = DAY * 365.25;
41038
41038
  function parse(val) {
41039
- var num3, arr = val.toLowerCase().match(RGX);
41040
- if (arr != null && (num3 = parseFloat(arr[1]))) {
41041
- if (arr[3] != null) return num3 * SEC;
41042
- if (arr[4] != null) return num3 * MIN;
41043
- if (arr[5] != null) return num3 * HOUR;
41044
- if (arr[6] != null) return num3 * DAY;
41045
- if (arr[7] != null) return num3 * DAY * 7;
41046
- if (arr[8] != null) return num3 * YEAR;
41047
- return num3;
41039
+ var num4, arr = val.toLowerCase().match(RGX);
41040
+ if (arr != null && (num4 = parseFloat(arr[1]))) {
41041
+ if (arr[3] != null) return num4 * SEC;
41042
+ if (arr[4] != null) return num4 * MIN;
41043
+ if (arr[5] != null) return num4 * HOUR;
41044
+ if (arr[6] != null) return num4 * DAY;
41045
+ if (arr[7] != null) return num4 * DAY * 7;
41046
+ if (arr[8] != null) return num4 * YEAR;
41047
+ return num4;
41048
41048
  }
41049
41049
  }
41050
41050
  function fmt(val, pfx, str2, long) {
41051
- var num3 = (val | 0) === val ? val : ~~(val + 0.5);
41052
- return pfx + num3 + (long ? " " + str2 + (num3 != 1 ? "s" : "") : str2[0]);
41051
+ var num4 = (val | 0) === val ? val : ~~(val + 0.5);
41052
+ return pfx + num4 + (long ? " " + str2 + (num4 != 1 ? "s" : "") : str2[0]);
41053
41053
  }
41054
- function format(num3, long) {
41055
- var pfx = num3 < 0 ? "-" : "", abs = num3 < 0 ? -num3 : num3;
41056
- if (abs < SEC) return num3 + (long ? " ms" : "ms");
41054
+ function format(num4, long) {
41055
+ var pfx = num4 < 0 ? "-" : "", abs = num4 < 0 ? -num4 : num4;
41056
+ if (abs < SEC) return num4 + (long ? " ms" : "ms");
41057
41057
  if (abs < MIN) return fmt(abs / SEC, pfx, "second", long);
41058
41058
  if (abs < HOUR) return fmt(abs / MIN, pfx, "minute", long);
41059
41059
  if (abs < DAY) return fmt(abs / HOUR, pfx, "hour", long);
@@ -45784,9 +45784,9 @@ var require_stringifyNumber = __commonJS({
45784
45784
  function stringifyNumber({ format, minFractionDigits, tag, value }) {
45785
45785
  if (typeof value === "bigint")
45786
45786
  return String(value);
45787
- const num3 = typeof value === "number" ? value : Number(value);
45788
- if (!isFinite(num3))
45789
- return isNaN(num3) ? ".nan" : num3 < 0 ? "-.inf" : ".inf";
45787
+ const num4 = typeof value === "number" ? value : Number(value);
45788
+ if (!isFinite(num4))
45789
+ return isNaN(num4) ? ".nan" : num4 < 0 ? "-.inf" : ".inf";
45790
45790
  let n = Object.is(value, -0) ? "-0" : JSON.stringify(value);
45791
45791
  if (!format && minFractionDigits && (!tag || tag === "tag:yaml.org,2002:float") && /^-?\d/.test(n) && !n.includes("e")) {
45792
45792
  let i = n.indexOf(".");
@@ -45826,8 +45826,8 @@ var require_float = __commonJS({
45826
45826
  test: /^[-+]?(?:\.[0-9]+|[0-9]+(?:\.[0-9]*)?)[eE][-+]?[0-9]+$/,
45827
45827
  resolve: (str2) => parseFloat(str2),
45828
45828
  stringify(node) {
45829
- const num3 = Number(node.value);
45830
- return isFinite(num3) ? num3.toExponential() : stringifyNumber.stringifyNumber(node);
45829
+ const num4 = Number(node.value);
45830
+ return isFinite(num4) ? num4.toExponential() : stringifyNumber.stringifyNumber(node);
45831
45831
  }
45832
45832
  };
45833
45833
  var float = {
@@ -46266,8 +46266,8 @@ var require_float2 = __commonJS({
46266
46266
  test: /^[-+]?(?:[0-9][0-9_]*)?(?:\.[0-9_]*)?[eE][-+]?[0-9]+$/,
46267
46267
  resolve: (str2) => parseFloat(str2.replace(/_/g, "")),
46268
46268
  stringify(node) {
46269
- const num3 = Number(node.value);
46270
- return isFinite(num3) ? num3.toExponential() : stringifyNumber.stringifyNumber(node);
46269
+ const num4 = Number(node.value);
46270
+ return isFinite(num4) ? num4.toExponential() : stringifyNumber.stringifyNumber(node);
46271
46271
  }
46272
46272
  };
46273
46273
  var float = {
@@ -46469,23 +46469,23 @@ var require_timestamp2 = __commonJS({
46469
46469
  function parseSexagesimal(str2, asBigInt) {
46470
46470
  const sign = str2[0];
46471
46471
  const parts = sign === "-" || sign === "+" ? str2.substring(1) : str2;
46472
- const num3 = (n) => asBigInt ? BigInt(n) : Number(n);
46473
- const res = parts.replace(/_/g, "").split(":").reduce((res2, p) => res2 * num3(60) + num3(p), num3(0));
46474
- return sign === "-" ? num3(-1) * res : res;
46472
+ const num4 = (n) => asBigInt ? BigInt(n) : Number(n);
46473
+ const res = parts.replace(/_/g, "").split(":").reduce((res2, p) => res2 * num4(60) + num4(p), num4(0));
46474
+ return sign === "-" ? num4(-1) * res : res;
46475
46475
  }
46476
46476
  function stringifySexagesimal(node) {
46477
46477
  let { value } = node;
46478
- let num3 = (n) => n;
46478
+ let num4 = (n) => n;
46479
46479
  if (typeof value === "bigint")
46480
- num3 = (n) => BigInt(n);
46480
+ num4 = (n) => BigInt(n);
46481
46481
  else if (isNaN(value) || !isFinite(value))
46482
46482
  return stringifyNumber.stringifyNumber(node);
46483
46483
  let sign = "";
46484
46484
  if (value < 0) {
46485
46485
  sign = "-";
46486
- value *= num3(-1);
46486
+ value *= num4(-1);
46487
46487
  }
46488
- const _60 = num3(60);
46488
+ const _60 = num4(60);
46489
46489
  const parts = [value % _60];
46490
46490
  if (value < 60) {
46491
46491
  parts.unshift(0);
@@ -51176,6 +51176,17 @@ function appBaked(id) {
51176
51176
  return false;
51177
51177
  }
51178
51178
  }
51179
+ function appWorkspace(id) {
51180
+ try {
51181
+ const dir = appDir(id);
51182
+ const sourcePath = firstWithExt(dir, ".app") ?? firstWithExt(dir, ".flo");
51183
+ if (!sourcePath) return false;
51184
+ const src = asRecord((0, import_yaml.parse)((0, import_node_fs5.readFileSync)(sourcePath, "utf8")));
51185
+ return typeof src.floless === "object" && src.floless !== null && src.floless.workspace === true;
51186
+ } catch {
51187
+ return false;
51188
+ }
51189
+ }
51179
51190
  function readApp(id) {
51180
51191
  const dir = appDir(id);
51181
51192
  const sourcePath = firstWithExt(dir, ".app") ?? firstWithExt(dir, ".flo");
@@ -51279,6 +51290,8 @@ function readApp(id) {
51279
51290
  runnable: runState === "ready",
51280
51291
  triggerSource: detectTriggerSource(nodes, connections, readCommandSpec),
51281
51292
  baked: src["exposes-as-agent"] === true,
51293
+ // FloLess-namespaced `floless: { workspace: true }` — a FloLess flag AWARE ignores.
51294
+ workspace: typeof src.floless === "object" && src.floless !== null && src.floless.workspace === true,
51282
51295
  rebakeInput: findRebakeInput(inputs)
51283
51296
  };
51284
51297
  }
@@ -53093,7 +53106,7 @@ function appVersion() {
53093
53106
  return resolveVersion({
53094
53107
  isSea: isSea2(),
53095
53108
  sqVersionXml: readSqVersionXml(),
53096
- define: true ? "0.83.0" : void 0,
53109
+ define: true ? "0.85.0" : void 0,
53097
53110
  pkgVersion: readPkgVersion()
53098
53111
  });
53099
53112
  }
@@ -53103,7 +53116,7 @@ function resolveChannel(s) {
53103
53116
  return "dev";
53104
53117
  }
53105
53118
  function appChannel() {
53106
- return resolveChannel({ isSea: isSea2(), define: true ? "0.83.0" : void 0 });
53119
+ return resolveChannel({ isSea: isSea2(), define: true ? "0.85.0" : void 0 });
53107
53120
  }
53108
53121
 
53109
53122
  // workflow-update.ts
@@ -53400,7 +53413,7 @@ function extractReportHtml(events) {
53400
53413
  var import_node_child_process3 = require("node:child_process");
53401
53414
  var import_node_fs12 = require("node:fs");
53402
53415
  var import_node_path11 = require("node:path");
53403
- var OPEN_EXTS = /* @__PURE__ */ new Set([".xls", ".xlsx", ".html", ".htm", ".csv"]);
53416
+ var OPEN_EXTS = /* @__PURE__ */ new Set([".xls", ".xlsx", ".html", ".htm", ".csv", ".svg"]);
53404
53417
  var REVEAL_EXTS = /* @__PURE__ */ new Set([...OPEN_EXTS, ".ifc", ".nc1", ".dstv"]);
53405
53418
  function hasOpenableExt(path) {
53406
53419
  return OPEN_EXTS.has((0, import_node_path11.extname)(path).toLowerCase());
@@ -54055,16 +54068,16 @@ function sanitizeRecipe(raw) {
54055
54068
  if (!raw || typeof raw !== "object") return void 0;
54056
54069
  const r = raw;
54057
54070
  if (typeof r.kind !== "string" || !r.kind || !r.params || typeof r.params !== "object") return void 0;
54058
- const num3 = {};
54071
+ const num4 = {};
54059
54072
  for (const [k, v] of Object.entries(r.params)) {
54060
- if (typeof v === "number" && isFinite(v)) num3[k] = v;
54073
+ if (typeof v === "number" && isFinite(v)) num4[k] = v;
54061
54074
  }
54062
54075
  if (r.kind === "base-plate") {
54063
54076
  const params = {};
54064
54077
  for (const [k, [lo, hi]] of Object.entries(BASE_PLATE_PARAMS)) {
54065
- if (k in num3) {
54066
- if (!(num3[k] >= lo && num3[k] <= hi)) return void 0;
54067
- params[k] = num3[k];
54078
+ if (k in num4) {
54079
+ if (!(num4[k] >= lo && num4[k] <= hi)) return void 0;
54080
+ params[k] = num4[k];
54068
54081
  }
54069
54082
  }
54070
54083
  return Object.keys(params).length ? { kind: r.kind, params } : void 0;
@@ -54072,14 +54085,14 @@ function sanitizeRecipe(raw) {
54072
54085
  if (r.kind === "shear-plate") {
54073
54086
  const params = {};
54074
54087
  for (const [k, [lo, hi]] of Object.entries(SHEAR_PLATE_PARAMS)) {
54075
- if (k in num3) {
54076
- if (!(num3[k] >= lo && num3[k] <= hi)) return void 0;
54077
- params[k] = num3[k];
54088
+ if (k in num4) {
54089
+ if (!(num4[k] >= lo && num4[k] <= hi)) return void 0;
54090
+ params[k] = num4[k];
54078
54091
  }
54079
54092
  }
54080
54093
  return Object.keys(params).length ? { kind: r.kind, params } : void 0;
54081
54094
  }
54082
- return Object.keys(num3).length ? { kind: r.kind, params: num3 } : void 0;
54095
+ return Object.keys(num4).length ? { kind: r.kind, params: num4 } : void 0;
54083
54096
  }
54084
54097
  async function extractConnection(companionId, ifcPath, id) {
54085
54098
  await ensureConnectionReader();
@@ -54798,6 +54811,9 @@ function diffContracts(from, to) {
54798
54811
  }
54799
54812
 
54800
54813
  // version-diff.ts
54814
+ function appSupportsDiff(app) {
54815
+ return app === "steel-model";
54816
+ }
54801
54817
  function computeVersionDiff(id, n, base) {
54802
54818
  if (!Number.isInteger(n) || n < 1) return { code: 400, body: { ok: false, error: "n must be a positive integer version number" } };
54803
54819
  const head = listVersions(id)[0]?.n ?? 0;
@@ -54973,9 +54989,60 @@ function deriveLayers(elements, existing) {
54973
54989
  }
54974
54990
  return [...counts.entries()].map(([name, count]) => onByName.has(name) ? { name, count, on: onByName.get(name) } : { name, count }).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
54975
54991
  }
54992
+ function pickSheet(contract, sheetId) {
54993
+ const sheets = contract.sheets ?? [];
54994
+ if (sheetId) return sheets.find((s) => s.id === sheetId);
54995
+ const active = contract.active ?? 0;
54996
+ return sheets[active] ?? sheets[0];
54997
+ }
54998
+ function escAttr(s) {
54999
+ return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
55000
+ }
55001
+ function escText(s) {
55002
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
55003
+ }
55004
+ function num2(v, fallback) {
55005
+ const n = typeof v === "number" ? v : Number(v);
55006
+ return Number.isFinite(n) ? n : fallback;
55007
+ }
55008
+ function ptsToPath(pts) {
55009
+ if (!pts || pts.length < 2) return "";
55010
+ return pts.map((p, i) => `${i === 0 ? "M" : "L"}${num2(p[0], 0)} ${num2(p[1], 0)}`).join(" ");
55011
+ }
55012
+ function contractToSvg(contract, sheetId) {
55013
+ const sheet = pickSheet(contract, sheetId);
55014
+ if (!sheet) return '<svg xmlns="http://www.w3.org/2000/svg"></svg>';
55015
+ const w = num2(sheet.page?.w, 100);
55016
+ const h = num2(sheet.page?.h, 100);
55017
+ const parts = [];
55018
+ for (const el of sheet.elements) {
55019
+ if (el.kind === "text") {
55020
+ if (!el.text) continue;
55021
+ const bbox = el.bbox;
55022
+ const ox = num2(el.origin?.[0] ?? bbox?.[0], 0);
55023
+ const oy = num2(el.origin?.[1] ?? bbox?.[3] ?? bbox?.[1], 0);
55024
+ const fs2 = el.size != null ? num2(el.size, 4) : num2(el.w, 1) * 6;
55025
+ const font = el.font ? ` font-family="${escAttr(el.font)}"` : "";
55026
+ const ang = num2(el.angle, 0);
55027
+ const rot = ang ? ` transform="rotate(${ang} ${ox} ${oy})"` : "";
55028
+ parts.push(
55029
+ `<text x="${ox}" y="${oy}" font-size="${fs2}" fill="${escAttr(el.color ?? "#94a3b8")}"${font}${rot}>${escText(el.text)}</text>`
55030
+ );
55031
+ continue;
55032
+ }
55033
+ const d = el.d || ptsToPath(el.pts);
55034
+ if (!d) continue;
55035
+ const sw = num2(el.w, 1);
55036
+ const dash = el.dashed ? ` stroke-dasharray="${sw * 3} ${sw * 2}"` : "";
55037
+ parts.push(
55038
+ `<path d="${escAttr(d)}" fill="none" stroke="${escAttr(el.color ?? "#e2e8f0")}" stroke-width="${sw}"${dash}/>`
55039
+ );
55040
+ }
55041
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${w} ${h}" width="${w}" height="${h}">${parts.join("")}</svg>`;
55042
+ }
54976
55043
  function postProcess(contract, opts = {}) {
54977
55044
  const snapTol = opts.snapTol ?? 1;
54978
- const sheets = contract.sheets.map((sheet, i) => {
55045
+ const sheets = (contract.sheets ?? []).map((sheet, i) => {
54979
55046
  let elements = sheet.elements ?? [];
54980
55047
  elements = snapEndpoints(elements, snapTol);
54981
55048
  elements = dedupeElements(elements);
@@ -55226,16 +55293,16 @@ var GROUPS = {
55226
55293
  custom: { key: "custom", label: "Imported connections", color: "#8a97a8" }
55227
55294
  };
55228
55295
  var EMBED = 1;
55229
- function num2(params, key, def) {
55296
+ function num3(params, key, def) {
55230
55297
  const v = params?.[key];
55231
55298
  return typeof v === "number" && isFinite(v) ? v : def;
55232
55299
  }
55233
55300
  function pos(params, key, def) {
55234
- const v = num2(params, key, def);
55301
+ const v = num3(params, key, def);
55235
55302
  return v > 0 ? v : def;
55236
55303
  }
55237
55304
  function intMin1(params, key, def) {
55238
- return Math.max(1, Math.round(num2(params, key, def)));
55305
+ return Math.max(1, Math.round(num3(params, key, def)));
55239
55306
  }
55240
55307
  function finite3(a) {
55241
55308
  return Array.isArray(a) && a.length >= 3 && a.every((n) => typeof n === "number" && isFinite(n));
@@ -55290,18 +55357,18 @@ function expandBasePlate(joint, col) {
55290
55357
  const [bx, by, bz] = base;
55291
55358
  const { w, d } = col.dims;
55292
55359
  const thickness = pos(p, "thickness", 25);
55293
- const margin = Math.max(0, num2(p, "margin", 75));
55360
+ const margin = Math.max(0, num3(p, "margin", 75));
55294
55361
  const width = pos(p, "plateWidth", Math.max(w, d) + 2 * margin);
55295
55362
  const depth = pos(p, "plateDepth", Math.max(w, d) + 2 * margin);
55296
55363
  const boltDia = pos(p, "boltDia", 24);
55297
- const clearance = num2(p, "holeClearance", 6);
55298
- const edge = num2(p, "edgeDist", Math.max(1.5 * boltDia, 40));
55364
+ const clearance = num3(p, "holeClearance", 6);
55365
+ const edge = num3(p, "edgeDist", Math.max(1.5 * boltDia, 40));
55299
55366
  const cols = intMin1(p, "boltCols", 2);
55300
55367
  const rows = intMin1(p, "boltRows", 2);
55301
- const weldLeg = num2(p, "weldLeg", 8);
55302
- const projBelow = num2(p, "projBelow", 75);
55303
- const embedment = Math.max(0, num2(p, "embedment", 0));
55304
- const grout = Math.max(0, num2(p, "grout", 0));
55368
+ const weldLeg = num3(p, "weldLeg", 8);
55369
+ const projBelow = num3(p, "projBelow", 75);
55370
+ const embedment = Math.max(0, num3(p, "embedment", 0));
55371
+ const grout = Math.max(0, num3(p, "grout", 0));
55305
55372
  const rodBelow = embedment > 0 ? grout + embedment : projBelow;
55306
55373
  const nutH = pos(p, "nutHeight", 0.8 * boltDia);
55307
55374
  const nutAF = pos(p, "nutAcrossFlats", 1.6 * boltDia);
@@ -55411,14 +55478,14 @@ function beamCopes(beam, support, at, params) {
55411
55478
  if (!finite3(beam.from) || !finite3(beam.to) || !beam.dims || !(beam.dims.d > 0) || !(beam.dims.w > 0)) return {};
55412
55479
  if (profileKind(support.profile) !== "I") return {};
55413
55480
  const endPt = at === "end1" ? beam.to : beam.from;
55414
- const gap = Math.max(0, num2(params, "clearance", 12.7));
55481
+ const gap = Math.max(0, num3(params, "clearance", 12.7));
55415
55482
  const beamMidZ = endPt[2] + (typeof beam.posOffset === "number" && isFinite(beam.posOffset) ? beam.posOffset : 0);
55416
55483
  const beamTopZ = beamMidZ + beam.dims.d / 2, beamBotZ = beamMidZ - beam.dims.d / 2;
55417
55484
  const supMidZ = supportWorkZAt(support, endPt) + (typeof support.posOffset === "number" && isFinite(support.posOffset) ? support.posOffset : 0);
55418
55485
  const supTopZ = supMidZ + support.dims.d / 2, supBotZ = supMidZ - support.dims.d / 2;
55419
55486
  const tfS = flangeThk(support.profile, support.dims.d);
55420
- const kClear = Math.max(0, num2(params, "copeKClear", 12.7));
55421
- const minLen = Math.max(0, num2(params, "copeMinLength", 31.75));
55487
+ const kClear = Math.max(0, num3(params, "copeKClear", 12.7));
55488
+ const minLen = Math.max(0, num3(params, "copeMinLength", 31.75));
55422
55489
  const kBand = tfS + kClear;
55423
55490
  const webLo = supBotZ + kBand, webHi = supTopZ - kBand;
55424
55491
  if (!(webLo < webHi && beamTopZ > webLo && beamBotZ < webHi)) return {};
@@ -55461,14 +55528,14 @@ function expandShearPlate(joint, beam, support) {
55461
55528
  const cols = intMin1(p, "boltCols", 1);
55462
55529
  const rows = intMin1(p, "boltRows", 3);
55463
55530
  const pitch = pos(p, "boltPitch", 70);
55464
- const holeClr = num2(p, "holeClearance", 2);
55465
- const edge = Math.max(0, num2(p, "edgeDist", Math.max(1.5 * boltDia, 30)));
55466
- const weldLeg = num2(p, "weldLeg", 6);
55467
- const clearance = Math.max(0, num2(p, "clearance", 12.7));
55531
+ const holeClr = num3(p, "holeClearance", 2);
55532
+ const edge = Math.max(0, num3(p, "edgeDist", Math.max(1.5 * boltDia, 30)));
55533
+ const weldLeg = num3(p, "weldLeg", 6);
55534
+ const clearance = Math.max(0, num3(p, "clearance", 12.7));
55468
55535
  const stiffener = p?.["stiffener"] === true;
55469
55536
  const height = pos(p, "plateHeight", (rows - 1) * pitch + 2 * edge);
55470
55537
  const twHalf = webHalfThk(beam.profile, beam.dims.w);
55471
- const webSide = num2(p, "webSide", 1) >= 0 ? 1 : -1;
55538
+ const webSide = num3(p, "webSide", 1) >= 0 ? 1 : -1;
55472
55539
  const pOff = webSide * (twHalf + thickness / 2);
55473
55540
  const u0 = support ? webHalfThk(support.profile, support.dims.w) : 0;
55474
55541
  const edgeV = Math.min(edge, height * 0.4);
@@ -60348,16 +60415,16 @@ function memberTons(contract, m) {
60348
60415
  }
60349
60416
  function rollup(members, countWeighted = false) {
60350
60417
  const counts = emptyCounts();
60351
- let num3 = 0, den = 0, tons = 0;
60418
+ let num4 = 0, den = 0, tons = 0;
60352
60419
  for (const m of members) {
60353
60420
  counts[m.band]++;
60354
60421
  if (m.band === "rfi") continue;
60355
60422
  const w = countWeighted ? 1 : m.tons;
60356
- num3 += w * BAND_WEIGHT[m.band];
60423
+ num4 += w * BAND_WEIGHT[m.band];
60357
60424
  den += w;
60358
60425
  tons += m.tons;
60359
60426
  }
60360
- return { score: den > 0 ? Math.round(num3 / den * 100) : null, tons, counts };
60427
+ return { score: den > 0 ? Math.round(num4 / den * 100) : null, tons, counts };
60361
60428
  }
60362
60429
  function bandEnd(end, rows) {
60363
60430
  const row = end.conn ? rows.get(end.conn) : void 0;
@@ -60393,14 +60460,14 @@ function scoreConnections(contractInput) {
60393
60460
  }
60394
60461
  });
60395
60462
  const counts = emptyCounts();
60396
- let num3 = 0, den = 0;
60463
+ let num4 = 0, den = 0;
60397
60464
  for (const e of byEnd) {
60398
60465
  counts[e.band]++;
60399
60466
  if (e.band === "rfi") continue;
60400
- num3 += BAND_WEIGHT[e.band];
60467
+ num4 += BAND_WEIGHT[e.band];
60401
60468
  den += 1;
60402
60469
  }
60403
- const category = { score: den > 0 ? Math.round(num3 / den * 100) : null, tons: 0, counts };
60470
+ const category = { score: den > 0 ? Math.round(num4 / den * 100) : null, tons: 0, counts };
60404
60471
  return { byEnd, category };
60405
60472
  }
60406
60473
  function scoreContract(contractInput) {
@@ -60447,7 +60514,7 @@ function scoreContract(contractInput) {
60447
60514
  const columns = rollup(byMember.filter((m) => m.role === "column"));
60448
60515
  const details = rollupDetails(byDetail);
60449
60516
  const scored = byMember.filter((m) => m.band !== "rfi");
60450
- const num3 = scored.reduce((s, m) => s + m.tons * BAND_WEIGHT[m.band], 0);
60517
+ const num4 = scored.reduce((s, m) => s + m.tons * BAND_WEIGHT[m.band], 0);
60451
60518
  const den = scored.reduce((s, m) => s + m.tons, 0);
60452
60519
  return {
60453
60520
  byMember,
@@ -60465,7 +60532,7 @@ function scoreContract(contractInput) {
60465
60532
  note: "Approximate in v1 \u2014 precise per-segment binding is Slice 2."
60466
60533
  },
60467
60534
  overall: {
60468
- score: den > 0 ? Math.round(num3 / den * 100) : null,
60535
+ score: den > 0 ? Math.round(num4 / den * 100) : null,
60469
60536
  tons: den,
60470
60537
  rfiCount: byMember.filter((m) => m.band === "rfi").length
60471
60538
  }
@@ -60477,13 +60544,13 @@ function detailSheet(text) {
60477
60544
  }
60478
60545
  function rollupDetails(details) {
60479
60546
  const counts = emptyCounts();
60480
- let num3 = 0, den = 0;
60547
+ let num4 = 0, den = 0;
60481
60548
  for (const d of details) {
60482
60549
  counts[d.band]++;
60483
- num3 += BAND_WEIGHT[d.band];
60550
+ num4 += BAND_WEIGHT[d.band];
60484
60551
  den += 1;
60485
60552
  }
60486
- return { score: den > 0 ? Math.round(num3 / den * 100) : null, tons: 0, counts };
60553
+ return { score: den > 0 ? Math.round(num4 / den * 100) : null, tons: 0, counts };
60487
60554
  }
60488
60555
 
60489
60556
  // contract-score.ts
@@ -64755,7 +64822,7 @@ async function startServer() {
64755
64822
  });
64756
64823
  app.get("/api/apps", async () => {
64757
64824
  const apps = await aware.list();
64758
- return { ok: true, apps: apps.map((a) => ({ ...a, provider: appProvider(a.id), baked: appBaked(a.id), ...appOrigin(a.id) })) };
64825
+ return { ok: true, apps: apps.map((a) => ({ ...a, provider: appProvider(a.id), baked: appBaked(a.id), workspace: appWorkspace(a.id), ...appOrigin(a.id) })) };
64759
64826
  });
64760
64827
  app.get("/api/aisc-shapes", async () => ({ ok: true, shapes: Object.keys(aisc_shapes_default) }));
64761
64828
  app.get("/api/workflows/updates", async () => {
@@ -65023,6 +65090,9 @@ async function startServer() {
65023
65090
  async (req, reply) => {
65024
65091
  const project = getProject(req.params.id);
65025
65092
  if (!project) return reply.status(404).send({ ok: false, error: `unknown project: ${req.params.id}` });
65093
+ if (!appSupportsDiff(project.app)) {
65094
+ return reply.status(409).send({ ok: false, error: `the version diff is not supported for "${project.app}" projects` });
65095
+ }
65026
65096
  const n = Number(req.params.n);
65027
65097
  const base = req.query.base !== void 0 && req.query.base !== "" ? Number(req.query.base) : void 0;
65028
65098
  const { code, body } = computeVersionDiff(project.id, n, base);
@@ -65140,6 +65210,9 @@ async function startServer() {
65140
65210
  return null;
65141
65211
  }
65142
65212
  function projectExportFiles(appId) {
65213
+ if (appId === "vectorize") {
65214
+ return [{ kind: "svg", filename: `${appId}.svg` }];
65215
+ }
65143
65216
  return [
65144
65217
  { kind: "bom-csv", filename: `${appId}-bom.csv` },
65145
65218
  { kind: "bom-xlsx", filename: `${appId}-bom.xlsx` },
@@ -65150,7 +65223,7 @@ async function startServer() {
65150
65223
  const p = getProject(project);
65151
65224
  if (!p) return { status: 404, error: `unknown project: ${project}` };
65152
65225
  if (p.app !== appId) return { status: 404, error: `project "${project}" belongs to app "${p.app}", not "${appId}"` };
65153
- if (!p.approvedAt) return { status: 409, error: "Approve the model before exporting \u2014 exports go out only after sign-off." };
65226
+ if (!p.approvedAt) return { status: 409, error: "Approve this version before exporting \u2014 exports go out only after sign-off." };
65154
65227
  return null;
65155
65228
  }
65156
65229
  app.get("/api/contract/:appId", async (req, reply) => {
@@ -65216,7 +65289,7 @@ async function startServer() {
65216
65289
  broadcast({ type: "compiled", id: req.params.appId, lockPath: result.lockPath });
65217
65290
  let approvedAt;
65218
65291
  if (project) {
65219
- createVersion(project, doc, { author: "You", message: "Approved the model", gate: "model", kind: "approve" });
65292
+ createVersion(project, doc, { author: "You", message: "Approved this version", gate: "model", kind: "approve" });
65220
65293
  approvedAt = markApproved(project).approvedAt;
65221
65294
  }
65222
65295
  return { ok: true, result, approvedAt };
@@ -65508,6 +65581,32 @@ async function startServer() {
65508
65581
  return reply.status(500).send({ ok: false, error: `BOM ${format.toUpperCase()} export failed: ${e instanceof Error ? e.message : "write error"}` });
65509
65582
  }
65510
65583
  });
65584
+ app.post("/api/contract/:appId/export-svg", async (req, reply) => {
65585
+ const project = req.query.project || void 0;
65586
+ if (!/^[a-z0-9][a-z0-9._-]*$/i.test(req.params.appId)) {
65587
+ return reply.status(400).send({ ok: false, error: `invalid appId: ${req.params.appId}` });
65588
+ }
65589
+ if (project) {
65590
+ const g = projectExportGate(req.params.appId, project);
65591
+ if (g) return reply.status(g.status).send({ ok: false, error: g.error });
65592
+ }
65593
+ const doc = project ? readContract(req.params.appId, project) : readContractForApp(req.params.appId);
65594
+ if (doc == null) return reply.status(404).send({ ok: false, error: "no contract to export" });
65595
+ const v = validateContract(doc);
65596
+ if (!v.valid) return reply.status(400).send({ ok: false, error: "stored contract is invalid \u2014 re-save it in the editor" });
65597
+ const cleaned = postProcess(doc);
65598
+ const svg = contractToSvg(cleaned);
65599
+ const filename = `${req.params.appId}.svg`;
65600
+ const outPath = project ? (0, import_node_path33.join)(projectExportsDir(project), filename) : (0, import_node_path33.join)(appPath(req.params.appId), filename);
65601
+ try {
65602
+ (0, import_node_fs35.mkdirSync)((0, import_node_path33.dirname)(outPath), { recursive: true });
65603
+ (0, import_node_fs35.writeFileSync)(outPath, svg, "utf8");
65604
+ return { ok: true, filename, encoding: "utf8", content: svg, bytes: Buffer.byteLength(svg), savedTo: outPath };
65605
+ } catch (e) {
65606
+ app.log.error({ appId: req.params.appId, err: e instanceof Error ? e.message : e }, "export-svg: write failed");
65607
+ return reply.status(500).send({ ok: false, error: `SVG export failed: ${e instanceof Error ? e.message : "write error"}` });
65608
+ }
65609
+ });
65511
65610
  async function stopForegroundSession(id) {
65512
65611
  try {
65513
65612
  const sid = foregroundSessionId(id);
@@ -3,6 +3,12 @@ version: 0.3.0
3
3
  display-name: Steel Model
4
4
  publisher: floless
5
5
  module: steel-detailer
6
+ # FloLess-namespaced flag (AWARE ignores it — the .flo is FloLess's authoring format, not AWARE's).
7
+ # workspace:true makes this app show up in the app's Workspaces mode as project cards (drawings →
8
+ # model → exports → History). Steel previously relied on a hard-coded client constant; declaring the
9
+ # flag here is the source of truth. Presentation (which editor, which steps) is decided app-side.
10
+ floless:
11
+ workspace: true
6
12
  description: |
7
13
  Turn a structural steel drawing into a verifiable takeoff — overlay + properties + a confidence
8
14
  report you can judge — then an interactive 3D model (bake to IFC/Tekla) and a formatted BOM, all
@@ -2,6 +2,12 @@ app: vectorize
2
2
  version: 0.2.0
3
3
  display-name: Vectorize
4
4
  publisher: floless
5
+ # FloLess-namespaced flag (AWARE ignores it — the .flo is FloLess's authoring format, not AWARE's).
6
+ # workspace:true makes this app show up in the app's Workspaces mode as a free document workspace —
7
+ # project cards → the 2D vector editor → Approve → SVG export → version History — instead of only in
8
+ # the node-graph Workflows mode. Presentation (which editor, which steps) is decided app-side.
9
+ floless:
10
+ workspace: true
5
11
  description: |
6
12
  Turn any drawing — a sketch, a PDF, a photo — into clean, editable 2D vectors. Your terminal AI
7
13
  reads the drawing ONCE at compose time and bakes it into a drawing.vector contract: a vector PDF
@@ -0,0 +1,31 @@
1
+ /* ============================================================================
2
+ * analytics.js — a minimal product-analytics seam.
3
+ *
4
+ * The app has no analytics layer today. This is a REAL seam that stays a no-op
5
+ * until a PostHog client is configured on the page (globalThis.posthog with a
6
+ * .capture): wiring the full PostHog SDK pulls in a project key + consent/privacy
7
+ * handling for a desktop app — a dedicated later pass (spec §5.2). Until then,
8
+ * trackEvent() is defined and callable everywhere, and simply does nothing.
9
+ *
10
+ * Loaded as a classic <script> (like app.js/aware.js/panels.js/workspaces.js) and
11
+ * exposed on window — NOT an ESM export — to match the codebase's module pattern.
12
+ * Callers: workspaces.js emits the Slice-6 Workspaces events through window.trackEvent.
13
+ *
14
+ * Contract: analytics must NEVER break UX — every call is wrapped so a missing or
15
+ * throwing client can't surface an error to the user or interrupt a flow.
16
+ * ========================================================================== */
17
+ (() => {
18
+ // Emit a product-analytics event. No-op until a PostHog client is present.
19
+ // name — the event name (snake_case, e.g. 'workspace_project_approved')
20
+ // props — a flat properties object (e.g. { app: 'vectorize', kind: 'svg' })
21
+ function trackEvent(name, props = {}) {
22
+ try {
23
+ if (globalThis.posthog && typeof globalThis.posthog.capture === 'function') {
24
+ globalThis.posthog.capture(name, props);
25
+ }
26
+ } catch { /* never break UX for analytics */ }
27
+ }
28
+
29
+ // Expose the way the other web/*.js modules expose their API (window global, not ESM).
30
+ window.trackEvent = trackEvent;
31
+ })();