@floless/app 0.54.1 → 0.56.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 +598 -169
- package/dist/schemas/steel.takeoff.v1.schema.json +18 -0
- package/dist/skills/floless-app-new-workflow/SKILL.md +115 -0
- package/dist/skills/floless-app-new-workflow/references/build-and-verify.md +117 -0
- package/dist/skills/floless-app-onboarding/SKILL.md +2 -1
- package/dist/skills/floless-app-steel-model/SKILL.md +8 -4
- package/dist/skills/floless-app-steel-takeoff/SKILL.md +84 -1
- package/dist/web/app.css +155 -0
- package/dist/web/app.js +6 -1
- package/dist/web/aware.js +247 -1
- package/dist/web/index.html +42 -0
- package/dist/web/steel-3d-view.js +149 -9
- package/dist/web/steel-editor.html +163 -8
- package/dist/web/vendor/README.md +15 -4
- package/dist/web/vendor/three-bvh-csg.module.js +7619 -0
- package/package.json +1 -1
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"plans": { "type": "array", "items": { "$ref": "#/$defs/plan" } },
|
|
40
40
|
"filter": { "$ref": "#/$defs/filter", "description": "Layer/thickness/colour filter pre-stage. `sheets[]` holds per-framing-plan geometry; the facets + `mode` + on-flag selection are GLOBAL across sheets. The saved selection scopes which lines the reader binds profile labels to per sheet; replaces the length heuristic, fallback when absent. CONFIDENTIAL: each sheet's `page.bg_b64` is machine-local, never committed." },
|
|
41
41
|
"connections": { "type": "array", "items": { "$ref": "#/$defs/connection" }, "description": "Vendor-neutral connection library: each row maps a connection type → the firm's design detail# → per-platform component ids. Member ends/columns reference a row by id via `ends[].conn` / `col.conn` (legacy `note`/`detail` = fallback when `conn` is empty)." },
|
|
42
|
+
"joints": { "type": "array", "items": { "$ref": "#/$defs/joint" }, "description": "Placed connections (base plates, …) the connection engine expands into real 3D parts (plate/bolt/weld/cut) bound to their members. A joint may cite a `connections[]` row via `conn` for its type/detail identity; `params` carries the geometry settings (engine defaults when absent)." },
|
|
42
43
|
"dims3d": { "type": "array", "items": { "$ref": "#/$defs/dim3" }, "description": "Draft-only 3D dimensions (editor annotations, model-global). World-scene mm. NOT baked into the lock / 3D scene / IFC / BOM." }
|
|
43
44
|
},
|
|
44
45
|
"$defs": {
|
|
@@ -178,6 +179,23 @@
|
|
|
178
179
|
"source": { "enum": ["company-rules", "user", "drawing"], "description": "Where the row came from (drives the connection confidence band)." }
|
|
179
180
|
}
|
|
180
181
|
},
|
|
182
|
+
"joint": {
|
|
183
|
+
"type": "object",
|
|
184
|
+
"required": ["id", "kind", "main"],
|
|
185
|
+
"additionalProperties": true,
|
|
186
|
+
"description": "A placed connection — the geometry-bearing recipe the connection engine expands into real 3D parts bound to its member(s). Distinct from the `connections[]` library (vendor-neutral type/detail/targets identities a joint can cite). The engine derives all geometry from this recipe + the live member positions, so a joint moves/re-solves with its members. v1 kinds: base-plate, shear-plate.",
|
|
187
|
+
"properties": {
|
|
188
|
+
"id": { "type": "string", "description": "Stable joint id, e.g. j-bp-1 / sp-b3-e0." },
|
|
189
|
+
"kind": { "type": "string", "description": "Connection kind the engine knows how to expand: base-plate, shear-plate (v1). end-plate / cap-plate / … follow." },
|
|
190
|
+
"main": { "type": "string", "description": "Id of the host member this joint hangs off. For a base-plate this is the column; for a shear-plate this is the SUPPORTED BEAM (the joint sits at one of the beam's ends — see `at`)." },
|
|
191
|
+
"secondaries": { "type": "array", "items": { "type": "string" }, "description": "Ids of the other members this joint binds — e.g. the SUPPORT a shear-plate frames into. Optional and inferred by geometry; the shear-plate engine does not require it (the fin plate is placed off the beam end + beam axis). Empty for a base-plate." },
|
|
192
|
+
"at": { "type": "string", "description": "Where on the main member the joint sits (kind-dependent): base-plate = base; shear-plate = end0 | end1 — the beam end framing into the support (end0 ↔ ends[0]/start, end1 ↔ ends[1]/end)." },
|
|
193
|
+
"conn": { "type": "string", "description": "Optional `connections[]` row id this joint cites for its type/detail identity (keeps the connection library + confidence scoring in play)." },
|
|
194
|
+
"params": { "type": "object", "additionalProperties": true, "description": "Geometry settings (mm) with engine defaults; absent = all defaults. base-plate: plateWidth/plateDepth/thickness, boltCols/boltRows/boltDia/edgeDist, weldLeg, anchor kit, levelingNut. shear-plate: plateThickness, plateWidth (along the beam), plateHeight (vertical), boltCols/boltRows/boltDia/boltPitch/edgeDist, weldLeg, clearance (the erection gap so the beam clears the support, ~½–¾\"; pulls the beam box back), webSide (+1/-1 — which face of the beam web the plate laps), boltGrade (A325 | A490 — the AISC bolt length auto-sizes from the grip), stiffener (bool — a full-height stiffener on the far side of a girder web)." },
|
|
195
|
+
"source": { "enum": ["auto", "user"], "description": "auto = proposed by auto-detail from the takeoff; user = placed/edited by hand." },
|
|
196
|
+
"verified": { "type": "boolean", "description": "True = a human confirmed this joint." }
|
|
197
|
+
}
|
|
198
|
+
},
|
|
181
199
|
"point2": {
|
|
182
200
|
"type": "array",
|
|
183
201
|
"items": { "type": "number" },
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: floless-app-new-workflow
|
|
3
|
+
description: This skill should be used to build a brand-new FloLess .flo workflow from scratch, step by step, as the host terminal AI — interviewing the user about what they want, checking that the AWARE agents the workflow needs are installed (installing an available one on consent, or reporting a not-yet-existing agent as an idea), then authoring the .flo node by node and verifying each step with the real toolchain (plain-English descriptions, aware app validate, aware app compile, a real run) before installing it so it appears in the floless.app picker for Compile and Run. Use it on triggers like "build a new workflow", "create a workflow from scratch", "build me a workflow", "new workflow", the floless.app "New workflow…" menu item, a queued new-workflow request, or "/floless-app-new-workflow". It leans on floless-app-workflows for the exec contract and hands back a runnable, approved app.
|
|
4
|
+
metadata:
|
|
5
|
+
version: 0.1.0
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Build a new floless.app workflow from scratch
|
|
9
|
+
|
|
10
|
+
Guide the user from a blank slate to a **runnable, approved** `.flo` app, as the **host terminal
|
|
11
|
+
AI** they are talking to. floless.app is a **thin web UI**; the terminal AI (you) is the brain and
|
|
12
|
+
AWARE is the runtime. This skill makes you the builder — interview the user, confirm the agents the
|
|
13
|
+
workflow needs exist, then author the `.flo` **one node at a time, verifying each step with the real
|
|
14
|
+
toolchain before moving on**, and hand back an installed app the user can Compile and Run in the UI.
|
|
15
|
+
|
|
16
|
+
This is the doing-skill the onboarding tour points to ("want to build your first workflow?"). It is
|
|
17
|
+
the **create-from-scratch sibling** of the per-workflow guide (which explains an *existing* app).
|
|
18
|
+
|
|
19
|
+
The contract details (exec globals, return shape, templating, layout, gotchas) live in
|
|
20
|
+
**floless-app-workflows** — lean on it, do not re-derive it here. This skill owns the *interview →
|
|
21
|
+
preflight → author-and-verify → hand-back* flow; the verification mechanics live in
|
|
22
|
+
`references/build-and-verify.md` (read it when entering Step 3).
|
|
23
|
+
|
|
24
|
+
## Operating principles (apply throughout)
|
|
25
|
+
|
|
26
|
+
- **One step at a time, verified.** Author a node, then **run the real check that proves it** before
|
|
27
|
+
the next (description prose, `aware app validate`, `aware app compile`, a real `aware app run`).
|
|
28
|
+
Never stack three unverified nodes and hope. A failing check is the signal to fix *that* step.
|
|
29
|
+
- **Drive live, don't lecture.** Probe the user's real state (`/api/health`, `aware agent list`) and
|
|
30
|
+
build against it. A real install→compile→run beats prose.
|
|
31
|
+
- **Mutations need consent.** Installing an agent, writing the draft `.flo`, installing the app —
|
|
32
|
+
each is **offered, then done on an explicit yes**. Read-only probes run freely.
|
|
33
|
+
- **Never invent.** Don't name an agent, command, or node kind that isn't real — verify against
|
|
34
|
+
`aware agent list` / `--available` and floless-app-workflows. A made-up agent fails at validate.
|
|
35
|
+
- **Determinism is AWARE's.** The `.lock` is the approved contract; Run is gated on source-hash
|
|
36
|
+
freshness. Author the source; let `aware app compile` freeze it. Never teach around the gate.
|
|
37
|
+
- **Plain-English descriptions are mandatory.** Every node's leading `//` comment is its Description
|
|
38
|
+
(non-coders read it). Author it as prose as you write each node — it is also a verified step.
|
|
39
|
+
|
|
40
|
+
## Step 0 — Pick up the ask and orient
|
|
41
|
+
|
|
42
|
+
1. **If launched from the UI**, a `new-workflow` request is queued. Fetch the authoritative spec and
|
|
43
|
+
clear it when done (the house handshake):
|
|
44
|
+
```bash
|
|
45
|
+
curl -s http://127.0.0.1:4317/api/requests # find the {"type":"new-workflow", id} entry
|
|
46
|
+
# …do the work below…
|
|
47
|
+
curl -s -X DELETE http://127.0.0.1:4317/api/requests/<id> # only after the app is installed
|
|
48
|
+
```
|
|
49
|
+
If invoked directly ("/floless-app-new-workflow", "build me a workflow"), skip the queue.
|
|
50
|
+
2. **Confirm the ground** (read-only): `curl -s http://127.0.0.1:4317/api/health` →
|
|
51
|
+
`{"ok":true,"awareVersion":"<v>", ...}`. No response → the floless.app desktop app isn't running;
|
|
52
|
+
the user can still author from the terminal, but the final Compile/Run happens in the UI, so ask
|
|
53
|
+
them to start it. `license` not `valid` → the UI's Run sits behind a sign-in gate; mention it when
|
|
54
|
+
you reach Run, don't block authoring.
|
|
55
|
+
|
|
56
|
+
## Step 1 — Interview (what should it do?)
|
|
57
|
+
|
|
58
|
+
Pull the goal out in **one or two questions at a time**, plain English — never a wall:
|
|
59
|
+
- "What do you want this workflow to do?" (e.g. "list parts by phase", "export a BOM", "write a
|
|
60
|
+
greeting"). Get the *outcome* first.
|
|
61
|
+
- Then the shape: what **input(s)** does it take (a phase number? a file? nothing?), what **output**
|
|
62
|
+
the user wants to see (an HTML report in the Viewer? a value?), and which **host** (Tekla? none?).
|
|
63
|
+
|
|
64
|
+
Restate it in one sentence and get a nod before authoring. Keep it small — the first version should
|
|
65
|
+
**run**; extend it after. (Laziness: ship the one-node version that works, then grow it.)
|
|
66
|
+
|
|
67
|
+
## Step 2 — Agent preflight (before writing any node)
|
|
68
|
+
|
|
69
|
+
A node references an `agent` (e.g. `tekla`, `file`). **Confirm each one exists before authoring it** —
|
|
70
|
+
a workflow built on a missing agent fails at validate. Read `references/build-and-verify.md` →
|
|
71
|
+
"Agent preflight" and run the three-way check: **installed** (`aware agent list`) → use it;
|
|
72
|
+
**available but not installed** (`aware agent describe <id> --available`) → offer to install it
|
|
73
|
+
(consent); **truly absent from the catalogue** → it's a capability gap → offer to **report it as an
|
|
74
|
+
idea** (the reference spells out the report relay + the `aware` label). Never silently substitute or
|
|
75
|
+
invent an agent.
|
|
76
|
+
|
|
77
|
+
## Step 3 — Author the `.flo`, verifying each step
|
|
78
|
+
|
|
79
|
+
Read `references/build-and-verify.md` and follow the loop: write the draft under
|
|
80
|
+
`~/.floless/drafts/<id>/<id>.flo`, scaffold the **smallest valid shape**, then build up node by node —
|
|
81
|
+
**each node gets its plain-English `//` description and a real check before the next.** The reference
|
|
82
|
+
maps every step to the command that proves it (`validate` → `compile` → `install` → real `run`, read
|
|
83
|
+
the trace). For exec specifics (globals, `return`, no `System.Net`/Regex, no `{{ }}` in code
|
|
84
|
+
comments, `layout: linear` vs `dag`), defer to **floless-app-workflows** — don't reinvent them.
|
|
85
|
+
|
|
86
|
+
## Step 4 — Hand back to the UI (Compile → Run → approve)
|
|
87
|
+
|
|
88
|
+
Once the app **installs and a real run is green**, hand it to the user in floless.app:
|
|
89
|
+
1. It now appears in the **workflow picker** (top-right) — have them select it.
|
|
90
|
+
2. **Set inputs** on the input node (if any), then **Compile** (freezes the approved `.lock`; the Run
|
|
91
|
+
gate opens only when source-hash matches).
|
|
92
|
+
3. **▶ Run workflow** against the live host — the result renders in the canvas / HTML Viewer.
|
|
93
|
+
4. The user **approves** what they see. Done — they built a workflow from nothing.
|
|
94
|
+
|
|
95
|
+
Then DELETE the queued request (Step 0) if one launched this.
|
|
96
|
+
|
|
97
|
+
## Close
|
|
98
|
+
|
|
99
|
+
Recap in two lines what they built and how to grow it ("want a second node that exports a BOM?
|
|
100
|
+
re-run /floless-app-new-workflow, or just tell me"). Offer the obvious next step — bake it into a
|
|
101
|
+
reusable agent (**floless-app-rebake** / Bake), or automate it on a trigger (**floless-app-routines**).
|
|
102
|
+
|
|
103
|
+
## Guardrails
|
|
104
|
+
|
|
105
|
+
- **Verify every step — no "probably works".** A node isn't done until its check passes (description
|
|
106
|
+
prose, validate, compile, a real run with a green trace). `--simulate` does NOT count — it stubs
|
|
107
|
+
every node and emits no real output.
|
|
108
|
+
- **Read-only by default; mutations on consent.** Installing an agent, writing the draft, installing
|
|
109
|
+
the app — offer, then act on an explicit yes.
|
|
110
|
+
- **Never invent agents/nodes.** Verify against `aware agent list` / `--available`; report a real gap
|
|
111
|
+
as an idea, don't fake it.
|
|
112
|
+
- **Don't contradict the architecture.** The UI never composes the workflow or calls an LLM; you (the
|
|
113
|
+
terminal AI) author it, AWARE compiles/runs it, the `.lock` is the approved contract.
|
|
114
|
+
- **Lean on floless-app-workflows** for the exec contract and the run-trace details — this skill
|
|
115
|
+
orchestrates; it does not duplicate the contract reference.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Build-and-verify loop + agent preflight
|
|
2
|
+
|
|
3
|
+
The mechanics behind Steps 2–3 of `SKILL.md`. Every step here maps to a **real command that proves
|
|
4
|
+
it** — that is the whole point of this skill (build a workflow whose every step is checked, not
|
|
5
|
+
guessed). For the exec contract itself (globals, `return`, templating, layout, gotchas) read
|
|
6
|
+
**floless-app-workflows**; this file does not duplicate it.
|
|
7
|
+
|
|
8
|
+
All paths resolve at runtime — `~` is the user's home; never hardcode an absolute `C:\…` / `D:\…`.
|
|
9
|
+
|
|
10
|
+
## Where a new draft lives
|
|
11
|
+
|
|
12
|
+
A user's brand-new workflow is **not** a repo demo — author it in a user-owned scratch dir, one
|
|
13
|
+
directory per app (the install/compile/run verbs all take a *directory*, never a bare `.flo`):
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
~/.floless/drafts/<id>/<id>.flo
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`<id>` is the app id (lowercase, `a–z 0–9 - _`), the same string as the `.flo`'s top-level `app:`
|
|
20
|
+
field. Create the dir, write the `.flo` into it, and run every verb against `~/.floless/drafts/<id>`.
|
|
21
|
+
|
|
22
|
+
## The smallest valid shape (scaffold, then grow)
|
|
23
|
+
|
|
24
|
+
Start with the **one-node app that runs**, prove it, then add nodes. The minimal valid `.flo`:
|
|
25
|
+
|
|
26
|
+
```yaml
|
|
27
|
+
app: my-first # required — the id; matches the folder name
|
|
28
|
+
version: 0.1.0 # required — semver
|
|
29
|
+
display-name: My First # required for the picker label (validator doesn't enforce it)
|
|
30
|
+
description: | # required — plain-English paragraph for non-coders
|
|
31
|
+
What this workflow does, in one or two sentences.
|
|
32
|
+
requires: [] # conventional — list the preflighted agents (e.g. [tekla@0.1.x]); every demo carries one
|
|
33
|
+
layout: linear # linear (node-order chain) | dag (explicit connections)
|
|
34
|
+
nodes:
|
|
35
|
+
- id: hello
|
|
36
|
+
agent: tekla # a REAL agent (preflight first); model-free exec needs no open model
|
|
37
|
+
command: exec
|
|
38
|
+
mode: read
|
|
39
|
+
config:
|
|
40
|
+
version: "2025.0" # required for tekla/exec — the Tekla version
|
|
41
|
+
args: {}
|
|
42
|
+
code: |
|
|
43
|
+
// Plain-English description of what this node does — this IS the node's Description.
|
|
44
|
+
return new { ok = true };
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Grow by adding nodes one at a time. Inputs → `inputs:` block, templated `{{ inputs.<name> }}`;
|
|
48
|
+
cross-node data → `{{ <node-id>.result.<field> }}`. **Any branch (fan-out/fan-in) needs `layout: dag`
|
|
49
|
+
+ a `connections:` block** — a straight chain stays `linear`. (Details + footguns →
|
|
50
|
+
floless-app-workflows. Critical one: **never write `{{` or `}}` inside a `code:` block**, even in a
|
|
51
|
+
`//` comment — it is templated and a stray brace aborts the run.)
|
|
52
|
+
|
|
53
|
+
## The verify-each-step loop
|
|
54
|
+
|
|
55
|
+
Author a node (with its `//` description), then run the checks **in order** before the next node.
|
|
56
|
+
Each command is the proof for that step:
|
|
57
|
+
|
|
58
|
+
| Step | Command (run from anywhere; dir = the draft) | Proves |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| 1. Descriptions are plain English | eyeball the leading `//` of every node + the top-level `description:` — prose, ≥3 words, no raw code/markup. (The repo's `npm run verify:flo` enforces this for committed demos; a user draft lives outside `demos/`, so apply the rule yourself.) | non-coders can read the Inspect → Description tab |
|
|
61
|
+
| 2. Structural validity | `aware app validate ~/.floless/drafts/<id>` | YAML parses; required fields present; node topology sound; **every referenced agent exists** (else `E_APP_AGENT_UNAVAILABLE` — back to preflight) |
|
|
62
|
+
| 3. Install | `aware app install ~/.floless/drafts/<id>` (re-installing the same id? `aware app uninstall <id>` first — a same-id install errors `app <id> already installed`) | the app lands in `~/.aware/apps/<id>/` and shows in the floless.app picker |
|
|
63
|
+
| 4. Compile → lock | `aware app compile ~/.floless/drafts/<id>` | resolves agents, stamps `source-hash`, writes `<id>.lock` into the installed app dir — the approved, runnable contract |
|
|
64
|
+
| 5. Real run | `aware app run <id> --input k=v --json` | every node executes for real; trace at `~/.aware/logs/<id>/<instance>/<run>.jsonl` |
|
|
65
|
+
| 6. Read the trace | open that `.jsonl` | every node emits `node-output` with `ok:true` and it ends `run-end · status:ok` |
|
|
66
|
+
|
|
67
|
+
**`--simulate` is not a check** — it stubs every node, emits no real `result.<field>`, and renders
|
|
68
|
+
templates as `undefined`. A green simulate proves nothing. Always do a real run.
|
|
69
|
+
|
|
70
|
+
### No Tekla open? Still do a real run.
|
|
71
|
+
|
|
72
|
+
A `tekla/exec` node runs Roslyn in the host bridge `aware-tekla.exe` — it needs the **bridge**, not a
|
|
73
|
+
live model. A node that **never dereferences `model`** (builds a string/HTML, reads an arg, returns
|
|
74
|
+
data) runs with nothing open in Tekla. So a from-scratch first workflow that just returns a value or
|
|
75
|
+
builds an HTML report verifies end-to-end with no model. If the bridge is stale,
|
|
76
|
+
`aware sidecar install tekla` refreshes it. (A C# compile error surfaces only as an opaque
|
|
77
|
+
`agent tekla/exec failed (exit Some(2))` — isolate by checking your `code` compiles; no `System.Net`,
|
|
78
|
+
**no `System.Text.RegularExpressions`**, manual HTML-escape. See floless-app-workflows.)
|
|
79
|
+
|
|
80
|
+
When step 6 is green, the app is **installed, compiled, and proven** — hand back to the UI (SKILL.md
|
|
81
|
+
Step 4). If a step fails, fix **that** node/field and re-run from the failed step; don't push past it.
|
|
82
|
+
|
|
83
|
+
## Agent preflight (Step 2 in full)
|
|
84
|
+
|
|
85
|
+
Before authoring a node on an `agent`, confirm that agent exists. Three outcomes:
|
|
86
|
+
|
|
87
|
+
1. **Installed** — `aware agent list` shows it → use it. (`tekla` and `file` are the common ones.)
|
|
88
|
+
2. **Available but not installed** — not in `list`, but `aware agent describe <id> --available` finds
|
|
89
|
+
it in the registry catalogue → **offer to install it** (consent, it's a mutation):
|
|
90
|
+
- host bridges (tekla, rhino, sketchup, revit) → `aware sidecar install <host>`
|
|
91
|
+
- other catalogue agents → `aware agent install <id>`
|
|
92
|
+
Before assuming a gap, `aware agent search <term>` so a **misspelling or a differently-named
|
|
93
|
+
existing agent** isn't mistaken for "missing" (e.g. the user says "spreadsheet" but it's `file`).
|
|
94
|
+
3. **Truly absent** — not installed, and `aware agent describe <id> --available` reports not-found
|
|
95
|
+
(and `search` turns up nothing) → a genuine **AWARE capability gap**. Don't fake it, don't
|
|
96
|
+
substitute silently. Offer to **report it as an idea** (below), then either pick a different
|
|
97
|
+
approach with the agents that *do* exist, or pause that node until the agent ships.
|
|
98
|
+
|
|
99
|
+
### Reporting a missing agent as an idea
|
|
100
|
+
|
|
101
|
+
A not-yet-existing agent is a legitimate "missing-but-expected capability" — file it so it can be
|
|
102
|
+
built. Route it through **floless-app-report-issue** (it owns the floless.io relay) as an **idea**:
|
|
103
|
+
|
|
104
|
+
- `POST http://127.0.0.1:4317/api/report-issue` with `category: "idea"`, a title prefixed
|
|
105
|
+
`[AWARE agent] <id> —` and a body that states: the **capability needed** (what host/agent, what the
|
|
106
|
+
workflow would do with it), **why** (the user's real goal from Step 1), and the **proof it's
|
|
107
|
+
absent** (the `aware agent describe <id> --available` not-found / the `E_APP_AGENT_UNAVAILABLE`
|
|
108
|
+
from validate).
|
|
109
|
+
- The relay files it into the **floless.app** issue tracker, tagged `idea` + `user-report` — so it is
|
|
110
|
+
known to come from a real floless user via the licensed relay, not an anonymous open-source report
|
|
111
|
+
(and the public `aware-aeco/aware` tracker stays clean). The maintainer triages these under the
|
|
112
|
+
`aware` label and promotes the real ones into actual AWARE agents.
|
|
113
|
+
- This is **consent-gated and read-only to the user's machine** — it sends a report, it changes
|
|
114
|
+
nothing locally. Confirm the user wants it filed before sending.
|
|
115
|
+
|
|
116
|
+
Discriminator (don't file noise): only a *genuinely absent* agent is an idea. A typo, a wrong
|
|
117
|
+
capability guess, or a validate error caused by your own bad `code` is **not** a gap — fix the usage.
|
|
@@ -91,7 +91,8 @@ tell them how to come back:
|
|
|
91
91
|
> section ('the agents part', 'Bake & Graft', 'routines') and I'll jump straight there."
|
|
92
92
|
|
|
93
93
|
Then offer a concrete next step grounded in what they have — e.g. "want to build your first
|
|
94
|
-
workflow?" (hand off to **floless-app-
|
|
94
|
+
workflow?" (hand off to **floless-app-new-workflow**, the step-by-step from-scratch builder — or
|
|
95
|
+
**floless-app-workflows** for the raw authoring reference) or "install one of those agents?" (A4).
|
|
95
96
|
|
|
96
97
|
## Guardrails
|
|
97
98
|
|
|
@@ -62,10 +62,14 @@ the project folder (a few files, **not** a framework):
|
|
|
62
62
|
section / detail / schedule). This is the map the later stages classify against.
|
|
63
63
|
- **`general-notes.md`** — the General Notes that bind the whole job: design spec, default
|
|
64
64
|
connection type, bolt/weld standard, material grades, anything that overrides a single sheet.
|
|
65
|
-
- **`company-rules.md`** *(ask first)* — **ask whether the firm has company-specific standards
|
|
66
|
-
(e.g. "moment → detail 50S504 → Tekla
|
|
67
|
-
the connection-library pre-fill in stage 6 (the editor's Connections panel
|
|
68
|
-
`source:"company-rules"`)
|
|
65
|
+
- **`company-rules.md`** *(ask first)* — **ask whether the firm has company-specific standards.** Two
|
|
66
|
+
kinds, both captured here: (1) **connection-library mappings** (e.g. "moment → detail 50S504 → Tekla
|
|
67
|
+
component 146") that feed the connection-library pre-fill in stage 6 (the editor's Connections panel
|
|
68
|
+
reads these as `source:"company-rules"`); and (2) **firm DETAILING standards** — default bolt grade +
|
|
69
|
+
length-from-grip, usual gages, near/far-side, shear-tab shape/edge/clearance — that the Steel Takeoff
|
|
70
|
+
reader applies to a connection's `joints[].params` when detailing (see `floless-app-steel-takeoff`
|
|
71
|
+
§5.5b). The file is machine-local / the firm's private IP (it may live in the user's own synced Drive)
|
|
72
|
+
— **never committed.** If the firm has none, skip it — don't invent rules.
|
|
69
73
|
|
|
70
74
|
Read the set with the terminal AI's own vision, following the **`reading-structural-drawings`**
|
|
71
75
|
discipline while skimming (massing + coordinate frame first — it's what makes the sheet
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: floless-app-steel-takeoff
|
|
3
3
|
description: Read a structural steel drawing into an editable steel.takeoff/v1 contract in floless.app — the "Steel Takeoff" reader. Triggers on a pending floless `rebake` request for the `steel-model` app (queued from its "Re-read & re-bake ▸" button), or asks like "read this framing plan into a takeoff", "turn this structural drawing into a takeoff", "bake my drawing into steel-model", "extract members from this PDF". Teaches the host AI to READ a steel drawing (PDF/image) with its own vision at COMPOSE time, build a steel.takeoff/v1 contract (members + profiles + elevations + weights from AISC lookup), write it to the contract store, and hand control to the editor — no model in the run path, no API key.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 0.
|
|
5
|
+
version: 0.2.0
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Steel Takeoff — read a drawing into a steel.takeoff/v1 contract
|
|
@@ -293,10 +293,93 @@ Key field rules:
|
|
|
293
293
|
when `conn` is empty. `source` is `drawing` (read from a callout — best evidence), `company-rules`
|
|
294
294
|
(the firm's standard), or `user`. Prefer a `conn` reference over free text when the connection is
|
|
295
295
|
known — the editor's per-end picker and the Connections confidence card both key on it.
|
|
296
|
+
- **Base-plate connections (`joints[]`) — the geometry recipe.** Where the set has a **base-plate
|
|
297
|
+
detail + schedule** (column size → base-plate THK×"N"×"B", anchor Ø×embed, weld "A", washer plate),
|
|
298
|
+
emit a top-level `joints[]` entry per column so FloLess renders the real plate + anchor kit + weld
|
|
299
|
+
in 3D: `{ id, kind:"base-plate", main:"<column member id>", at:"base", source:"auto", params:{…} }`.
|
|
300
|
+
The joint binds to the column by id and may CITE a `connections[]` row via `conn` for its
|
|
301
|
+
type/detail identity (build on the library, don't fork it). Map the schedule straight into `params`:
|
|
302
|
+
|
|
303
|
+
| Schedule callout | `params` key(s) |
|
|
304
|
+
|---|---|
|
|
305
|
+
| base-plate THK · "N" · "B" | `thickness` · `plateWidth` · `plateDepth` |
|
|
306
|
+
| anchor bolt Ø · count | `boltDia` · `boltCols`×`boltRows` |
|
|
307
|
+
| weld size "A" | `weldLeg` |
|
|
308
|
+
| "provide 1/4"×3"×3" PL washer w/ std hole" | `washerPlateThickness` · `washerPlateSide` |
|
|
309
|
+
| "anchor holes oversized: A-bolt Ø + 5/16"" | `holeClearance` (the +amount) |
|
|
310
|
+
| edge distance · embed | `edgeDist` · `projBelow` |
|
|
311
|
+
|
|
312
|
+
⚠️ **UNITS: the *dimensional* `params` are MILLIMETRES** (the engine works in mm) — unlike the
|
|
313
|
+
contract's display/inch member coords — so convert the schedule's inches ×25.4. (`boltCols` /
|
|
314
|
+
`boltRows` are integer *counts*, not mm; `projBelow` is how far the rod renders below the plate — a
|
|
315
|
+
reasonable proxy for the embed.) Any omitted param falls back to a sensible default. Worked example
|
|
316
|
+
(a `W10x33 → 1"×18"×10", 1"Ø×12", 5/16"` schedule row):
|
|
317
|
+
`params: { thickness:25.4, plateWidth:457.2, plateDepth:254, boltDia:25.4, boltCols:2, boltRows:2, edgeDist:50.8, weldLeg:7.94, washerPlateThickness:6.35, washerPlateSide:76.2, holeClearance:7.94, projBelow:304.8 }`.
|
|
318
|
+
The weld automatically traces the column's **profile outline** (the I-shape for a W; a rectangle for
|
|
319
|
+
an HSS — square only when the section is square). *(The outline is engine-rendered: a wide-flange's
|
|
320
|
+
flange width is still approximate until a real AISC `bf` table lands, so a W renders a touch narrow —
|
|
321
|
+
the plate/anchor/weld sizes you set are exact.)*
|
|
322
|
+
- **Shear-plate (fin-plate) connections (`joints[]`) — the bolted beam-end recipe.** Where a beam end
|
|
323
|
+
reads as a **shear / simple / fin-plate / shear-tab** connection (the per-end `note`/`conn`), emit a
|
|
324
|
+
`joints[]` entry **per beam end** so FloLess renders the vertical fin plate (bolted to the web) + the
|
|
325
|
+
bolt group + the plate-to-support weld in 3D:
|
|
326
|
+
`{ id, kind:"shear-plate", main:"<SUPPORTED BEAM member id>", at:"end0"|"end1", source:"auto", params:{…} }`.
|
|
327
|
+
⚠️ **`main` is the BEAM** (not the support) — the joint sits at one of the beam's ends; `at:"end0"` =
|
|
328
|
+
the start end (`ends[0]`), `at:"end1"` = the far end (`ends[1]`). Optionally name the support in
|
|
329
|
+
`secondaries:["<column/girder id>"]` (metadata only — the geometry is placed off the beam end + beam
|
|
330
|
+
axis, so it's not required). May CITE a `connections[]` row via `conn` for its type/detail identity.
|
|
331
|
+
Map the schedule into `params`:
|
|
332
|
+
|
|
333
|
+
| Schedule callout | `params` key(s) |
|
|
334
|
+
|---|---|
|
|
335
|
+
| plate THK · width (along beam) · height | `plateThickness` · `plateWidth` · `plateHeight` |
|
|
336
|
+
| bolt Ø · rows×cols · pitch/gauge | `boltDia` · `boltRows`×`boltCols` · `boltPitch` |
|
|
337
|
+
| weld size | `weldLeg` |
|
|
338
|
+
| beam clearance / erection gap off the support (≈½–¾") | `clearance` |
|
|
339
|
+
| which face of the beam web the plate laps | `webSide` (+1 / -1) |
|
|
340
|
+
| bolt grade (AISC) — A325 or A490 | `boltGrade` ("A325" / "A490") |
|
|
341
|
+
| "stiffener / backing plate on the far side" | `stiffener: true` |
|
|
342
|
+
|
|
343
|
+
⚠️ **UNITS: the *dimensional* `params` are MILLIMETRES** (×25.4 from inches) — `boltCols`/`boltRows`
|
|
344
|
+
are integer counts; `stiffener` is a boolean; `webSide` is +1/-1. The fin plate is VERTICAL (in the
|
|
345
|
+
beam-web plane) and **laps one face of the web** (not centred — `webSide` picks the face), cantilevered
|
|
346
|
+
toward the beam from the support edge; the **`clearance` pulls the beam back** so it clears the support
|
|
347
|
+
(the cope/cut is a later slice). Omitted params fall back to sensible defaults (10 mm plate, 1×3 M20
|
|
348
|
+
bolts at 70 mm, 6 mm weld, ~13 mm clearance; A325 grade). The bolt **length is not a param** — it
|
|
349
|
+
auto-sizes from the grip (clamped plies) per the AISC A325/A490 chart and rides the bolt callout (the
|
|
350
|
+
3D part label); `boltGrade` only picks A325 vs A490. For a beam-to-beam joint, name the supporting girder in
|
|
351
|
+
`secondaries:["<girderId>"]` so `stiffener:true` draws a full-height stiffener on the girder web.
|
|
352
|
+
Worked example (a 3/8"×4"×8" tab, 3×3/4" bolts @ 3", far end of beam b3): `{ id:"sp-b3-e1",
|
|
353
|
+
kind:"shear-plate", main:"b3", at:"end1", source:"auto", params:{ plateThickness:9.5, plateWidth:101.6,
|
|
354
|
+
plateHeight:203.2, boltCols:1, boltRows:3, boltDia:19.05, boltPitch:76.2, weldLeg:6.35, clearance:12.7 } }`.
|
|
296
355
|
- **Company-rules pre-fill (§5.5).** If a firm-standard knowledge file is present (e.g.
|
|
297
356
|
`company-rules.md`: "moment → 50S504 → Tekla 146"), read it and emit those rows in `connections[]`
|
|
298
357
|
with `source: "company-rules"`, then PUT. The user reviews/edits them in the editor's
|
|
299
358
|
**Connections** library panel and assigns a row per member end. No new agent verb — it's contract data.
|
|
359
|
+
- **Firm DETAILING standards → shear-plate `joints[].params` (§5.5b).** The same `company-rules.md` is
|
|
360
|
+
also the home for the firm's **detailing** standards (the firm's IP / AISC-*Manual* reference data —
|
|
361
|
+
kept **machine-local, NEVER committed**, and out of the public `aware` AISC agent, which is
|
|
362
|
+
free-source only). When detailing a **shear plate**, read these and set the matching `joints[].params`
|
|
363
|
+
(and say you applied the firm rule), instead of leaning on the engine defaults:
|
|
364
|
+
- **Bolt grade + length** — the firm's default grade → `boltGrade` ("A325"/"A490"). The bolt length
|
|
365
|
+
auto-sizes from grip in the engine per the firm's A325/A490 bolt-length chart; just confirm the
|
|
366
|
+
grade. *(The firm's chart in `company-rules.md` mirrors AISC Manual Table 7-18 add-to-grip — facts
|
|
367
|
+
for the firm's own use; do not republish the table.)*
|
|
368
|
+
- **Usual gages** — the firm's bolt gauge `g` by flange width / leg (W-beams, angles) → `boltPitch`
|
|
369
|
+
and the bolt grid; e.g. a W-beam web gage or a standard 3" pitch. Read the gage table.
|
|
370
|
+
- **Near-side / far-side (NS-FS)** — the firm's default web face the shear plate laps → `webSide`
|
|
371
|
+
(+1 near / -1 far). Many shops default NS unless the drawing says FS.
|
|
372
|
+
- **Knife-plate / shear-tab shape + edge distance + clearance** — the firm's standard plate
|
|
373
|
+
proportions, edge distance, and erection clearance → `plateWidth`/`plateHeight`/`edgeDist`/
|
|
374
|
+
`clearance`. Use the firm's value when it differs from the engine default.
|
|
375
|
+
- **Sizing stays detailing-not-design.** Apply the firm's DEFAULTS + flag a drawing value that
|
|
376
|
+
conflicts; never invent a number. The drawing's explicit callout still wins over the firm default.
|
|
377
|
+
- *(Base plates have their OWN firm keys — `plateWidth`/`plateDepth`/`thickness`/`edgeDist`/
|
|
378
|
+
`boltCols`×`boltRows`/`boltDia`/`weldLeg`/`holeClearance`/`projBelow` — no grade/pitch/webSide;
|
|
379
|
+
apply the firm's base-plate schedule there the same way.)*
|
|
380
|
+
Format in `company-rules.md`: plain headed sections the AI can read (e.g. `## Bolts` with the grade
|
|
381
|
+
default + the length chart, `## Gages`, `## Shear tab (NS-FS, edge, clearance)`). The author keeps it
|
|
382
|
+
private; FloLess just reads + applies it.
|
|
300
383
|
- **Clickable callouts (`callouts[]`).** Harvest the cross-reference marks drawn ON the plan that
|
|
301
384
|
point to ANOTHER sheet — **section cuts** (e.g. a circle with `A` over `A` and a sheet ref),
|
|
302
385
|
**elevation/interior-elevation marks**, and **detail references** — as per-plan
|
package/dist/web/app.css
CHANGED
|
@@ -460,6 +460,15 @@
|
|
|
460
460
|
/* Secondary AI-native pointer (replaces the old raw `aware app install` chip — confusing for a
|
|
461
461
|
newcomer and off-message against "your terminal AI builds the workflows"). */
|
|
462
462
|
.onb-foot { font-size: 12px; color: var(--text-muted); margin-top: 16px; line-height: 1.5; }
|
|
463
|
+
/* Quiet secondary first-run action — an inline text button, NOT a second filled
|
|
464
|
+
button, so "Start the guided tour" stays the dominant primary. Existing tokens only. */
|
|
465
|
+
.onb-build-btn {
|
|
466
|
+
background: none; border: none; padding: 0; cursor: pointer;
|
|
467
|
+
font-family: var(--ui); font-size: 12px; font-weight: 600; color: var(--accent);
|
|
468
|
+
letter-spacing: 0.01em;
|
|
469
|
+
}
|
|
470
|
+
.onb-build-btn:hover { color: var(--accent-bright); text-decoration: underline; }
|
|
471
|
+
.onb-build-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 2px; }
|
|
463
472
|
/* "Start the guided tour" — the primary first-run action (copies the onboarding launch prompt).
|
|
464
473
|
Accent-soft region + filled-accent button mirror the .agent-card.selected / #run-btn primary
|
|
465
474
|
tier; all tokens existing (locked baseline — no new fonts/colors). */
|
|
@@ -479,6 +488,152 @@
|
|
|
479
488
|
.onb-tour-btn:disabled { cursor: default; opacity: 0.85; box-shadow: none; }
|
|
480
489
|
.onb-tour-ico { font-size: 11px; opacity: 0.7; margin-left: 6px; }
|
|
481
490
|
|
|
491
|
+
/* ===== Per-workflow onboarding beacon ("Start here") ===== */
|
|
492
|
+
/* A header pill in the run cluster. Calm by default (accent-soft, like the
|
|
493
|
+
wf-update pill); pulses while the open workflow is new to the user. Accent —
|
|
494
|
+
NOT warn — because it's positive/new, and warn is already the drift colour. */
|
|
495
|
+
.guide-beacon {
|
|
496
|
+
display: inline-flex;
|
|
497
|
+
align-items: center;
|
|
498
|
+
gap: 5px;
|
|
499
|
+
border: 1px solid var(--accent-dim);
|
|
500
|
+
background: var(--accent-soft);
|
|
501
|
+
color: var(--accent-bright);
|
|
502
|
+
font-family: var(--ui);
|
|
503
|
+
font-size: 11px;
|
|
504
|
+
letter-spacing: 0.02em;
|
|
505
|
+
padding: 3px 11px;
|
|
506
|
+
border-radius: 999px;
|
|
507
|
+
cursor: pointer;
|
|
508
|
+
white-space: nowrap;
|
|
509
|
+
flex-shrink: 0; /* the workflow picker shrinks first; the beacon never squashes */
|
|
510
|
+
transition: border-color 0.15s, color 0.15s, background 0.15s;
|
|
511
|
+
}
|
|
512
|
+
.guide-beacon[hidden] { display: none; }
|
|
513
|
+
.guide-beacon:hover { border-color: var(--accent); color: var(--accent); }
|
|
514
|
+
.guide-beacon:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
515
|
+
.guide-beacon-ico { font-size: 11px; line-height: 1; }
|
|
516
|
+
/* "New to you": a slow box-shadow bloom (GPU-composited, no layout repaint) reading
|
|
517
|
+
as a calm invitation, not an alarm. Same motion dialect as the rest of the app. */
|
|
518
|
+
.guide-beacon.is-new {
|
|
519
|
+
border-color: var(--accent);
|
|
520
|
+
color: var(--accent-bright);
|
|
521
|
+
animation: guide-pulse 2.8s ease-in-out infinite;
|
|
522
|
+
}
|
|
523
|
+
@keyframes guide-pulse {
|
|
524
|
+
0%, 100% { box-shadow: 0 0 0 0 var(--accent-glow); }
|
|
525
|
+
50% { box-shadow: 0 0 0 6px rgba(74, 158, 255, 0); }
|
|
526
|
+
}
|
|
527
|
+
@media (prefers-reduced-motion: reduce) {
|
|
528
|
+
/* No pulse — a steady ring (shape + contrast, not colour alone) marks "new". */
|
|
529
|
+
.guide-beacon.is-new { animation: none; box-shadow: 0 0 0 1px var(--accent-dim); }
|
|
530
|
+
}
|
|
531
|
+
/* Spotlit (first-open): raised above the dim, opaque, ringed. The header creates no
|
|
532
|
+
stacking context, so z-index here lifts it above the fixed spotlight overlay. */
|
|
533
|
+
.guide-beacon.spotlit {
|
|
534
|
+
position: relative;
|
|
535
|
+
z-index: 95;
|
|
536
|
+
animation: none;
|
|
537
|
+
background: var(--surface-3);
|
|
538
|
+
border-color: var(--accent);
|
|
539
|
+
color: var(--accent-bright);
|
|
540
|
+
box-shadow: 0 0 0 3px var(--accent-glow), 0 0 20px 4px var(--accent-glow);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/* ===== First-open spotlight ===== */
|
|
544
|
+
.guide-spotlight {
|
|
545
|
+
position: fixed;
|
|
546
|
+
inset: 0;
|
|
547
|
+
z-index: 90; /* below modals (100), above the page; the beacon (.spotlit, 95) sits over it */
|
|
548
|
+
background: rgba(7, 10, 15, 0.72);
|
|
549
|
+
backdrop-filter: blur(2px);
|
|
550
|
+
-webkit-backdrop-filter: blur(2px);
|
|
551
|
+
display: none;
|
|
552
|
+
}
|
|
553
|
+
.guide-spotlight:not([hidden]) { display: block; animation: fade-in 0.18s ease-out; }
|
|
554
|
+
.guide-spotlight-bubble {
|
|
555
|
+
position: fixed;
|
|
556
|
+
z-index: 91;
|
|
557
|
+
max-width: 280px;
|
|
558
|
+
background: var(--surface);
|
|
559
|
+
border: 1px solid var(--accent-dim);
|
|
560
|
+
border-radius: 8px;
|
|
561
|
+
padding: 12px 14px;
|
|
562
|
+
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
|
|
563
|
+
}
|
|
564
|
+
.guide-spotlight-text { font-size: 12.5px; line-height: 1.5; color: var(--text); }
|
|
565
|
+
.guide-spotlight-text strong { color: var(--accent-bright); }
|
|
566
|
+
.guide-spotlight-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 11px; }
|
|
567
|
+
.guide-spotlight-actions button { padding: 5px 13px; font-size: 12px; }
|
|
568
|
+
.guide-spotlight-actions .ghost { background: var(--surface-2); color: var(--text-muted); }
|
|
569
|
+
.guide-spotlight-actions .ghost:hover { color: var(--text); border-color: var(--accent-dim); }
|
|
570
|
+
.guide-spotlight-actions .primary { background: var(--accent); color: #fff; border-color: var(--accent); font-weight: 600; }
|
|
571
|
+
.guide-spotlight-actions .primary:hover { background: var(--accent-bright); box-shadow: 0 0 14px var(--accent-glow); }
|
|
572
|
+
|
|
573
|
+
/* ===== Guide panel (modal) ===== */
|
|
574
|
+
.modal.guide {
|
|
575
|
+
width: 520px;
|
|
576
|
+
max-width: 92vw;
|
|
577
|
+
max-height: 82vh;
|
|
578
|
+
display: flex;
|
|
579
|
+
flex-direction: column;
|
|
580
|
+
}
|
|
581
|
+
.guide-eyebrow {
|
|
582
|
+
font-size: 10px;
|
|
583
|
+
text-transform: uppercase;
|
|
584
|
+
letter-spacing: 0.16em;
|
|
585
|
+
color: var(--accent-bright);
|
|
586
|
+
margin-bottom: 6px;
|
|
587
|
+
}
|
|
588
|
+
/* The body scrolls between the pinned title and the pinned actions; theme its
|
|
589
|
+
scrollbar so no native white bar leaks against the dark surface. */
|
|
590
|
+
#guide-body {
|
|
591
|
+
overflow-y: auto;
|
|
592
|
+
flex: 1 1 auto;
|
|
593
|
+
min-height: 0;
|
|
594
|
+
margin-top: 2px;
|
|
595
|
+
scrollbar-width: thin;
|
|
596
|
+
scrollbar-color: var(--border-strong) transparent;
|
|
597
|
+
}
|
|
598
|
+
#guide-body::-webkit-scrollbar { width: 7px; }
|
|
599
|
+
#guide-body::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 7px; }
|
|
600
|
+
#guide-body::-webkit-scrollbar-track { background: transparent; }
|
|
601
|
+
.guide-section-label {
|
|
602
|
+
font-size: 10px;
|
|
603
|
+
text-transform: uppercase;
|
|
604
|
+
letter-spacing: 0.14em;
|
|
605
|
+
color: var(--text-muted);
|
|
606
|
+
margin: 14px 0 7px;
|
|
607
|
+
}
|
|
608
|
+
.guide-section-label:first-child { margin-top: 2px; }
|
|
609
|
+
.guide-desc { font-size: 12.5px; line-height: 1.55; color: var(--text); }
|
|
610
|
+
.guide-desc p { margin: 0 0 8px; }
|
|
611
|
+
.guide-desc p:last-child { margin-bottom: 0; }
|
|
612
|
+
.guide-desc code { font-family: var(--mono); background: var(--surface-2); padding: 1px 5px; border-radius: 3px; color: var(--text-muted); font-size: 11px; }
|
|
613
|
+
.guide-desc-empty { color: var(--text-muted); font-style: italic; }
|
|
614
|
+
.guide-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 9px; }
|
|
615
|
+
.guide-step { display: flex; gap: 10px; align-items: baseline; }
|
|
616
|
+
.guide-box { font-family: var(--mono); font-size: 13px; min-width: 16px; color: var(--text-dim); line-height: 1.3; }
|
|
617
|
+
.guide-step-label { font-size: 12.5px; color: var(--text); }
|
|
618
|
+
.guide-step-note { font-size: 11px; color: var(--text-dim); font-style: italic; }
|
|
619
|
+
/* Done: ✓ in ok-green + struck/dimmed label, so the current step stands out on
|
|
620
|
+
shape + contrast, not colour alone. Current: → in accent + bold label. */
|
|
621
|
+
.guide-step.done .guide-box { color: var(--ok); }
|
|
622
|
+
.guide-step.done .guide-step-label { color: var(--text-dim); text-decoration: line-through; text-decoration-color: var(--text-dim); }
|
|
623
|
+
.guide-step.current .guide-box { color: var(--accent); }
|
|
624
|
+
.guide-step.current .guide-step-label { color: var(--text); font-weight: 600; }
|
|
625
|
+
.guide-walk-hint {
|
|
626
|
+
font-size: 11px;
|
|
627
|
+
line-height: 1.5;
|
|
628
|
+
color: var(--text-muted);
|
|
629
|
+
margin-top: 14px;
|
|
630
|
+
padding-top: 12px;
|
|
631
|
+
border-top: 1px solid var(--border);
|
|
632
|
+
}
|
|
633
|
+
.guide-walk-hint strong { color: var(--text); font-weight: 600; }
|
|
634
|
+
.modal.guide .modal-actions { margin-top: 16px; }
|
|
635
|
+
.modal.guide .modal-actions #guide-got-it { margin-right: auto; }
|
|
636
|
+
|
|
482
637
|
.agent-card {
|
|
483
638
|
background: var(--surface);
|
|
484
639
|
border: 1px solid var(--border-strong);
|
package/dist/web/app.js
CHANGED
|
@@ -131,7 +131,7 @@ function onboardingHtml() {
|
|
|
131
131
|
step(3, 'Compile', '— freeze the approved lock') +
|
|
132
132
|
step(4, 'Run', '— against the live host; results render here') +
|
|
133
133
|
`</ol>` +
|
|
134
|
-
`<div class="onb-foot">No workflows yet?
|
|
134
|
+
`<div class="onb-foot">No workflows yet? <button class="onb-build-btn" type="button" aria-label="Build your first workflow — hands off to your terminal AI to build one from scratch">Build your first workflow →</button></div>` +
|
|
135
135
|
`</div>`
|
|
136
136
|
);
|
|
137
137
|
}
|
|
@@ -162,6 +162,11 @@ function wireOnboardingCopy() {
|
|
|
162
162
|
}
|
|
163
163
|
};
|
|
164
164
|
}
|
|
165
|
+
// Secondary first-run action: "Build your first workflow" — the empty canvas is the
|
|
166
|
+
// highest-intent moment to create one, so surface the same relay the ≡ menu's "New
|
|
167
|
+
// workflow…" fires (handleMenuAction is reassigned in aware.js → requestNewWorkflow).
|
|
168
|
+
const build = $topology.querySelector('.onb-build-btn');
|
|
169
|
+
if (build) build.onclick = () => handleMenuAction('new-workflow');
|
|
165
170
|
}
|
|
166
171
|
|
|
167
172
|
function nodeIds(p) {
|