@floless/app 0.54.0 → 0.55.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 +12 -3
- package/dist/skills/floless-app-steel-model/SKILL.md +7 -2
- package/dist/skills/floless-app-steel-takeoff/SKILL.md +5 -0
- package/dist/skills/reading-structural-drawings/SKILL.md +6 -1
- package/dist/skills/steel-drawing-abbreviations/SKILL.md +58 -0
- package/dist/skills/steel-drawing-abbreviations/references/standard-us-abbreviations.md +296 -0
- package/dist/web/app.css +146 -0
- package/dist/web/aware.js +226 -1
- package/dist/web/index.html +38 -0
- package/package.json +1 -1
package/dist/floless-server.cjs
CHANGED
|
@@ -52856,7 +52856,7 @@ function appVersion() {
|
|
|
52856
52856
|
return resolveVersion({
|
|
52857
52857
|
isSea: isSea2(),
|
|
52858
52858
|
sqVersionXml: readSqVersionXml(),
|
|
52859
|
-
define: true ? "0.
|
|
52859
|
+
define: true ? "0.55.0" : void 0,
|
|
52860
52860
|
pkgVersion: readPkgVersion()
|
|
52861
52861
|
});
|
|
52862
52862
|
}
|
|
@@ -52866,7 +52866,7 @@ function resolveChannel(s) {
|
|
|
52866
52866
|
return "dev";
|
|
52867
52867
|
}
|
|
52868
52868
|
function appChannel() {
|
|
52869
|
-
return resolveChannel({ isSea: isSea2(), define: true ? "0.
|
|
52869
|
+
return resolveChannel({ isSea: isSea2(), define: true ? "0.55.0" : void 0 });
|
|
52870
52870
|
}
|
|
52871
52871
|
|
|
52872
52872
|
// workflow-update.ts
|
|
@@ -60685,8 +60685,10 @@ var PRODUCT_SKILLS = [
|
|
|
60685
60685
|
// compose Custom Panels (~/.floless/ui/extensions.json) for the Dashboard
|
|
60686
60686
|
"floless-app-workflows",
|
|
60687
60687
|
// author/run .flo workflows
|
|
60688
|
-
"reading-structural-drawings"
|
|
60688
|
+
"reading-structural-drawings",
|
|
60689
60689
|
// the drawing-set reading discipline (non-namespace) the steel guide + reader lean on; shipped so users get it too
|
|
60690
|
+
"steel-drawing-abbreviations"
|
|
60691
|
+
// default US/AISC abbreviations+shapes legend (non-namespace) the steel reader falls back to when a set omits its own legend sheet
|
|
60690
60692
|
];
|
|
60691
60693
|
function selectShippedSkillNames(names) {
|
|
60692
60694
|
const present = new Set(names);
|
|
@@ -63874,6 +63876,13 @@ async function startServer() {
|
|
|
63874
63876
|
broadcast({ type: "request-added", request });
|
|
63875
63877
|
return { ok: true, request };
|
|
63876
63878
|
});
|
|
63879
|
+
app.post("/api/guide", async (req, reply) => {
|
|
63880
|
+
const { appId } = req.body ?? {};
|
|
63881
|
+
if (!appId) return reply.status(400).send({ ok: false, error: "appId required" });
|
|
63882
|
+
const request = addRequest({ type: "guide", appId });
|
|
63883
|
+
broadcast({ type: "request-added", request });
|
|
63884
|
+
return { ok: true, request };
|
|
63885
|
+
});
|
|
63877
63886
|
app.delete("/api/requests/:id", async (req, reply) => {
|
|
63878
63887
|
if (!deleteRequest(req.params.id)) return reply.status(404).send({ ok: false, error: "request not found" });
|
|
63879
63888
|
broadcast({ type: "requests-changed" });
|
|
@@ -26,13 +26,16 @@ the run path and no API key.
|
|
|
26
26
|
what was found and get an OK before spending effort on the next stage. Resume at any stage on
|
|
27
27
|
re-invoke ("we already set up the folder, provision the agents").
|
|
28
28
|
|
|
29
|
-
> Companion skills (
|
|
29
|
+
> Companion skills (all ship with floless.app — invoke them, don't duplicate them):
|
|
30
30
|
> - **`reading-structural-drawings`** — the reading *discipline* (massing + coordinate frame FIRST,
|
|
31
31
|
> members LAST, cross-validate every view). Follow it whenever skimming or classifying the set
|
|
32
32
|
> (stages 2 and 5) — it stops the classic failures (two buildings read as one, a sloped roof read
|
|
33
33
|
> as flat, grids conflated across sheets).
|
|
34
34
|
> - **`floless-app-steel-takeoff`** — reads one framing plan into the contract (the heavy vision
|
|
35
35
|
> pipeline, the filter pre-stage, the AISC/EN/BS lookup, the contract write + editor hand-off).
|
|
36
|
+
> - **`steel-drawing-abbreviations`** — the default US/AISC abbreviations + shape-designation legend.
|
|
37
|
+
> The fallback for note/profile codes (TYP, TOS, UON, `(E)`/`(N)`, HSS, WT) when the set omits its
|
|
38
|
+
> own abbreviations sheet (stage 2).
|
|
36
39
|
|
|
37
40
|
---
|
|
38
41
|
|
|
@@ -66,7 +69,9 @@ the project folder (a few files, **not** a framework):
|
|
|
66
69
|
|
|
67
70
|
Read the set with the terminal AI's own vision, following the **`reading-structural-drawings`**
|
|
68
71
|
discipline while skimming (massing + coordinate frame first — it's what makes the sheet
|
|
69
|
-
classification reliable).
|
|
72
|
+
classification reliable). If the set has **no abbreviations/legend sheet** (a partial set is common),
|
|
73
|
+
fall back to **`steel-drawing-abbreviations`** — the standard US/AISC abbreviations + shape legend —
|
|
74
|
+
for note/profile codes (TYP, TOS, UON, `(E)`/`(N)`, HSS, WT). The project's own legend always overrides.
|
|
70
75
|
|
|
71
76
|
**Gate:** show `set-index.md` (the sheet map) and confirm the classification looks right — the user
|
|
72
77
|
knows their set better than any read.
|
|
@@ -366,3 +366,8 @@ never committed). Clip title blocks from any embedded preview.
|
|
|
366
366
|
sizes or grid positions the drawing does not show.
|
|
367
367
|
- **Resolve the port** from the running floless.app the same way other floless-app skills do
|
|
368
368
|
(check `~/.floless/port` or the active server port).
|
|
369
|
+
- **Decode unfamiliar note/profile codes** with **`steel-drawing-abbreviations`** (the default
|
|
370
|
+
US/AISC abbreviations + shape legend) when the set lacks its own abbreviations sheet — for TYP
|
|
371
|
+
profiles, TOS/UON defaults, `(E)`/`(N)` scope, and shape designations. The project's own legend
|
|
372
|
+
always overrides; never expand a member *mark* (`MF`/`BF`/`D1`) from it — resolve those from the
|
|
373
|
+
schedules/elevations.
|
|
@@ -46,7 +46,9 @@ one with a curved roof — both obvious in the elevations, invisible in the plan
|
|
|
46
46
|
Read the **cover / drawing index** and **general notes** first. Build a sheet list classified by
|
|
47
47
|
type (index, notes, foundation plan, framing plans per level, frame elevations, sections, details,
|
|
48
48
|
**schedules**). Confirm the set is complete + current revision. Grab the legend, datum, material
|
|
49
|
-
defaults, and the **abbreviations** (FC/MF/BF/(E)/(N)/matchline symbols).
|
|
49
|
+
defaults, and the **abbreviations** (FC/MF/BF/(E)/(N)/matchline symbols). If the set ships **no
|
|
50
|
+
abbreviations/legend sheet** (or only partial sheets), fall back to the **`steel-drawing-abbreviations`**
|
|
51
|
+
default US/AISC legend for the standard codes. Trust PyMuPDF page_count over
|
|
50
52
|
any external count.
|
|
51
53
|
|
|
52
54
|
### Pass 1 — MASSING (how many buildings, what shape) — from elevations/sections, not plans
|
|
@@ -114,6 +116,9 @@ Only then build the scene / overlay / IFC. If unsure, present the **massing + gr
|
|
|
114
116
|
skill is the *reading discipline*; that one is the *reader*. The **`floless-app-steel-model`** guide
|
|
115
117
|
(the front-door orchestrator) wraps the whole flow and references this discipline at the
|
|
116
118
|
classify-and-verify gates rather than re-deriving it.
|
|
119
|
+
- **`steel-drawing-abbreviations`** — the default US/AISC abbreviations + shape-designation legend.
|
|
120
|
+
Consult it in Pass 0 when the set omits its own legend sheet, and whenever a note code (TYP, TOS,
|
|
121
|
+
UON, `(E)`/`(N)`) or profile designation needs decoding.
|
|
117
122
|
- There is no off-the-shelf OSS tool that does the whole drawing→model job — floorplan vectorisers
|
|
118
123
|
(e.g. CubiCasa5k) are residential-trained and won't read steel framing — which is why the order +
|
|
119
124
|
validation discipline above, not a single tool, is the method.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: steel-drawing-abbreviations
|
|
3
|
+
description: Default legend of standard US (AISC, imperial) structural-steel drawing abbreviations, note conventions, and shape designations — the fallback meaning when a drawing set omits or only partially supplies its own abbreviations/legend sheet. This skill should be used while reading a structural steel drawing whenever an abbreviation, note code, or profile designation needs expanding (TYP, TOS, UON, EQ, (E)/(N), SIM, HSS, WT, and so on), or when the steel reader (floless-app-steel-takeoff / floless-app-steel-model) meets a note or label whose meaning is not defined on the supplied sheets. The project's own legend always overrides these defaults.
|
|
4
|
+
metadata:
|
|
5
|
+
version: "0.1.0"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Steel drawing abbreviations — default legend
|
|
9
|
+
|
|
10
|
+
## What this is
|
|
11
|
+
|
|
12
|
+
A reusable **fallback legend** of the standard abbreviations, note conventions, and steel-shape
|
|
13
|
+
designations that appear on the General Notes / legend sheet (usually `S-001` / `S-100`) of most
|
|
14
|
+
**US (AISC, imperial)** structural sets. These are an industry convention — largely identical from
|
|
15
|
+
project to project — so they fill the gap when a set does not ship its own abbreviations sheet, or
|
|
16
|
+
when only some sheets are provided.
|
|
17
|
+
|
|
18
|
+
The full glossary lives in **`references/standard-us-abbreviations.md`** (abbreviations A–Z, the
|
|
19
|
+
structural-steel shape table, the high-value note conventions for takeoff, and a marks-vs-abbreviations
|
|
20
|
+
warning). Keep this SKILL.md lean; load the reference when an actual lookup is needed.
|
|
21
|
+
|
|
22
|
+
## When to consult it
|
|
23
|
+
|
|
24
|
+
- A structural plan, section, or **General Note** uses an abbreviation or code that is not spelled
|
|
25
|
+
out on the supplied sheets, and its meaning is needed to read geometry or metadata correctly
|
|
26
|
+
(e.g. a "T.O.S. = 16'-6" UON" note, a "TYP" profile, an `(E)` member).
|
|
27
|
+
- The **steel reader** (`floless-app-steel-takeoff`) or the **guide** (`floless-app-steel-model`) is
|
|
28
|
+
learning a set and the user did **not** provide the abbreviations/legend sheet — use this as the
|
|
29
|
+
default while still reading the rest of the set.
|
|
30
|
+
- A profile designation needs decoding (what `HSS6X6X3/8`, `WT`, `C`, `MC`, `PIPE-XX` mean and which
|
|
31
|
+
ones carry their weight in the name vs. need an AISC table).
|
|
32
|
+
|
|
33
|
+
## How to use it
|
|
34
|
+
|
|
35
|
+
1. **Read the project legend first.** If the set HAS an abbreviations sheet or a note that defines the
|
|
36
|
+
code, use *that* — this file is only the fallback. The project's own definition always wins.
|
|
37
|
+
2. **Look up the code** in `references/standard-us-abbreviations.md` (grep the abbreviation, e.g.
|
|
38
|
+
`grep -i 'TOS' references/standard-us-abbreviations.md`).
|
|
39
|
+
3. **Respect context over the table.** Several codes are context-dependent (`WT` = WEIGHT or a tee
|
|
40
|
+
shape; `PT` = POST-TENSIONED / PRESSURE-TREATED / POINT). Choose the structurally sensible meaning;
|
|
41
|
+
if it is genuinely ambiguous, flag it as an RFI rather than guess.
|
|
42
|
+
4. **Do not expand marks.** `MF` / `BF` / `D1` / `C1` / `B12` are project member/frame marks resolved
|
|
43
|
+
from the frame elevations and schedules — never from this glossary (see the reference's
|
|
44
|
+
"Marks vs abbreviations" section).
|
|
45
|
+
5. **Mind the region.** This is the US/AISC legend. For EU (EN) or UK (BS) sets, defer to the project
|
|
46
|
+
legend and the region-specific detailer agent (`steel-detailer-eu` / `steel-detailer-uk`); the
|
|
47
|
+
shape designations especially differ.
|
|
48
|
+
|
|
49
|
+
## Companion skills
|
|
50
|
+
|
|
51
|
+
- **`reading-structural-drawings`** — the reading discipline; its Pass 0 (inventory & legend) consults
|
|
52
|
+
this default legend when the set's own abbreviations sheet is missing.
|
|
53
|
+
- **`floless-app-steel-takeoff`** — the reader; uses this while reading notes/labels into the
|
|
54
|
+
`steel.takeoff/v1` contract (TYP profiles, TOS defaults, connection-type words, `(E)`/`(N)` scope).
|
|
55
|
+
- **`floless-app-steel-model`** — the front-door guide; references this at the "learn the set" stage so
|
|
56
|
+
a partial set without a legend sheet still reads correctly.
|
|
57
|
+
- **`steel-detailer-us` / `-eu` / `-uk`** (AWARE agents) — own the section *tables and weights*; this
|
|
58
|
+
skill only decodes the *labels and notes*.
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
# Standard US structural-steel drawing abbreviations & symbols (default legend)
|
|
2
|
+
|
|
3
|
+
These are the **de-facto standard abbreviations** that appear on the General Notes / legend sheet
|
|
4
|
+
(usually `S-001` / `S-100`) of most **US (AISC, imperial)** structural sets. They are an industry
|
|
5
|
+
convention, not any one firm's property, so they serve as a **fallback default** when a drawing set
|
|
6
|
+
does not include its own abbreviations/legend sheet (or only ships partial sheets).
|
|
7
|
+
|
|
8
|
+
**Rules of use (read before applying any expansion):**
|
|
9
|
+
- **The project's own legend ALWAYS wins.** If the set has an abbreviations sheet or a plan/general
|
|
10
|
+
note that defines a code, use *that* meaning — never override it with this list.
|
|
11
|
+
- **Context can override.** The same code means different things by context (e.g. `WT` = WEIGHT or a
|
|
12
|
+
structural-tee shape; `PT` = POST-TENSIONED or PRESSURE-TREATED or POINT). Pick the meaning that
|
|
13
|
+
fits the structural context; if genuinely ambiguous, flag it (RFI) rather than guess.
|
|
14
|
+
- **Region differs.** This is the US/AISC set. EU (EN) and UK (BS) sets use different shape
|
|
15
|
+
designations and some different abbreviations — defer those to the project legend and the
|
|
16
|
+
region-specific detailer agent (`steel-detailer-eu` / `steel-detailer-uk`).
|
|
17
|
+
- **Marks are NOT abbreviations.** Member/frame *marks* like `MF`, `BF`, `D1`, `C1`, `B12` are
|
|
18
|
+
project-specific labels resolved from the frame elevations / schedules — not from this glossary.
|
|
19
|
+
See "Marks vs abbreviations" below.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Abbreviations (A–Z)
|
|
24
|
+
|
|
25
|
+
| Abbr | Meaning |
|
|
26
|
+
|------|---------|
|
|
27
|
+
| `AB` | ANCHOR BOLT |
|
|
28
|
+
| `ACI` | AMERICAN CONCRETE INSTITUTE |
|
|
29
|
+
| `ADD'L` | ADDITIONAL |
|
|
30
|
+
| `ADJ` | ADJACENT / ADJUSTABLE |
|
|
31
|
+
| `AESS` | ARCHITECTURALLY EXPOSED STRUCTURAL STEEL |
|
|
32
|
+
| `AGGR` | AGGREGATE |
|
|
33
|
+
| `AISC` | AMERICAN INSTITUTE OF STEEL CONSTRUCTION |
|
|
34
|
+
| `AITC` | AMERICAN INSTITUTE OF TIMBER CONSTRUCTION |
|
|
35
|
+
| `ALT` | ALTERNATE |
|
|
36
|
+
| `ALUM` | ALUMINUM |
|
|
37
|
+
| `ANCH` | ANCHOR |
|
|
38
|
+
| `ANSI` | AMERICAN NATIONAL STANDARDS INSTITUTE |
|
|
39
|
+
| `APA` | AMERICAN PLYWOOD ASSOCIATION |
|
|
40
|
+
| `APPROX` | APPROXIMATE |
|
|
41
|
+
| `ARCH` | ARCHITECTURAL / ARCHITECT |
|
|
42
|
+
| `ASD` | ALLOWABLE STRENGTH DESIGN |
|
|
43
|
+
| `ASTM` | AMERICAN SOCIETY FOR TESTING AND MATERIALS |
|
|
44
|
+
| `AWPA` | AMERICAN WOOD PRESERVERS ASSOCIATION |
|
|
45
|
+
| `AWS` | AMERICAN WELDING SOCIETY |
|
|
46
|
+
| `AVG` | AVERAGE |
|
|
47
|
+
| `@` | AT |
|
|
48
|
+
| `&` | AND |
|
|
49
|
+
| `BLDG` | BUILDING |
|
|
50
|
+
| `BLK` | BLOCK |
|
|
51
|
+
| `BLKG` | BLOCKING |
|
|
52
|
+
| `BM` | BEAM |
|
|
53
|
+
| `BN` | BOUNDARY NAIL |
|
|
54
|
+
| `BNDRY` | BOUNDARY |
|
|
55
|
+
| `BOT` (or `B`) | BOTTOM |
|
|
56
|
+
| `BRC` | BRACE |
|
|
57
|
+
| `BRG` | BEARING |
|
|
58
|
+
| `BT` | BENT |
|
|
59
|
+
| `BTWN` | BETWEEN |
|
|
60
|
+
| `CANT` | CANTILEVER |
|
|
61
|
+
| `CAM` (or `C`) | CAMBER |
|
|
62
|
+
| `CG` | CENTER OF GRAVITY |
|
|
63
|
+
| `CIP` | CAST-IN-PLACE |
|
|
64
|
+
| `CJ` | CONSTRUCTION JOINT; CONTROL JOINT |
|
|
65
|
+
| `CJP` | COMPLETE JOINT PENETRATION (WELD) |
|
|
66
|
+
| `CL` (or `℄`) | CENTER LINE |
|
|
67
|
+
| `CLR` | CLEAR |
|
|
68
|
+
| `CMU` | CONCRETE MASONRY UNIT |
|
|
69
|
+
| `COL` | COLUMN |
|
|
70
|
+
| `COMP` | COMPRESSION / COMPOSITE |
|
|
71
|
+
| `CONC` | CONCRETE |
|
|
72
|
+
| `CONN` | CONNECTION |
|
|
73
|
+
| `CONST` / `CONSTR` | CONSTRUCTION |
|
|
74
|
+
| `CONT` | CONTINUOUS / CONTINUE |
|
|
75
|
+
| `CTR` | CENTER |
|
|
76
|
+
| `CTSK` | COUNTERSUNK |
|
|
77
|
+
| `CU FT` | CUBIC FOOT |
|
|
78
|
+
| `d` | PENNY (nail size, e.g. `16d`) |
|
|
79
|
+
| `DBL` | DOUBLE |
|
|
80
|
+
| `DEPT` | DEPARTMENT |
|
|
81
|
+
| `DET` | DETAIL |
|
|
82
|
+
| `DIA` (or `Ø`) | DIAMETER |
|
|
83
|
+
| `DIAG` | DIAGONAL |
|
|
84
|
+
| `DIAPH` | DIAPHRAGM |
|
|
85
|
+
| `DIM` | DIMENSION |
|
|
86
|
+
| `DN` | DOWN |
|
|
87
|
+
| `DO` | DITTO |
|
|
88
|
+
| `DWG` | DRAWING |
|
|
89
|
+
| `DWL` | DOWEL |
|
|
90
|
+
| `EA` | EACH |
|
|
91
|
+
| `EF` | EACH FACE |
|
|
92
|
+
| `EJ` | EXPANSION JOINT |
|
|
93
|
+
| `EL` / `ELEV` | ELEVATION |
|
|
94
|
+
| `ELEC` | ELECTRICAL |
|
|
95
|
+
| `EMBED` | EMBEDMENT |
|
|
96
|
+
| `EN` | EDGE NAIL |
|
|
97
|
+
| `EQ` | EQUAL / EQUALLY (SPACED) |
|
|
98
|
+
| `EQUIP` | EQUIPMENT |
|
|
99
|
+
| `ES` | EACH SIDE |
|
|
100
|
+
| `EW` | EACH WAY |
|
|
101
|
+
| `EXP` | EXPANSION |
|
|
102
|
+
| `EXT` | EXTERIOR |
|
|
103
|
+
| `(E)` | EXISTING (often out of new-work scope) |
|
|
104
|
+
| `FDN` | FOUNDATION |
|
|
105
|
+
| `FF` | FAR FACE |
|
|
106
|
+
| `FIN` | FINISH(ED) |
|
|
107
|
+
| `FLR` | FLOOR |
|
|
108
|
+
| `FN` | FIELD NAIL |
|
|
109
|
+
| `FOC` | FACE OF CONCRETE |
|
|
110
|
+
| `FOS` | FACE OF STUD / FACE OF SLAB |
|
|
111
|
+
| `FOW` | FACE OF WALL |
|
|
112
|
+
| `FP` | FIRE PROOFING |
|
|
113
|
+
| `FRMG` | FRAMING |
|
|
114
|
+
| `FS` | FAR SIDE / FULL SIZE |
|
|
115
|
+
| `FT` | FOOT / FEET |
|
|
116
|
+
| `FTG` | FOOTING |
|
|
117
|
+
| `GA` | GAUGE |
|
|
118
|
+
| `GALV` | GALVANIZED |
|
|
119
|
+
| `GB` | GRADE BEAM |
|
|
120
|
+
| `GLB` | GLUE-LAMINATED BEAM |
|
|
121
|
+
| `GR` | GRADE |
|
|
122
|
+
| `GRTG` | GRATING |
|
|
123
|
+
| `HDR` | HEADER |
|
|
124
|
+
| `HGR` | HANGER |
|
|
125
|
+
| `HGT` / `HT` | HEIGHT |
|
|
126
|
+
| `HK` | HOOK |
|
|
127
|
+
| `HORIZ` (or `H`) | HORIZONTAL |
|
|
128
|
+
| `HP` | HIGH POINT |
|
|
129
|
+
| `HS` | HIGH STRENGTH |
|
|
130
|
+
| `HSS` | HOLLOW STRUCTURAL SECTION |
|
|
131
|
+
| `ID` | INSIDE DIAMETER |
|
|
132
|
+
| `IF` | INSIDE FACE |
|
|
133
|
+
| `IN` | INCH |
|
|
134
|
+
| `INCL` | INCLUDE / INCLUDED |
|
|
135
|
+
| `INFO` | INFORMATION |
|
|
136
|
+
| `INSP` | INSPECTION |
|
|
137
|
+
| `INT` | INTERIOR |
|
|
138
|
+
| `JST` | JOIST |
|
|
139
|
+
| `JT` | JOINT |
|
|
140
|
+
| `K` | KIPS (1000 lb) |
|
|
141
|
+
| `KSI` | KIPS PER SQUARE INCH |
|
|
142
|
+
| `LAB` | LABORATORY |
|
|
143
|
+
| `LB(S)` (or `#`) | POUNDS |
|
|
144
|
+
| `LH` | LEFT HAND |
|
|
145
|
+
| `LLH` | LONG LEG HORIZONTAL |
|
|
146
|
+
| `LLV` | LONG LEG VERTICAL |
|
|
147
|
+
| `LP` | LOW POINT |
|
|
148
|
+
| `LT WT` | LIGHTWEIGHT |
|
|
149
|
+
| `MAS` | MASONRY |
|
|
150
|
+
| `MATL` | MATERIAL |
|
|
151
|
+
| `MAX` | MAXIMUM |
|
|
152
|
+
| `MB` | MACHINE BOLT |
|
|
153
|
+
| `MECH` | MECHANICAL |
|
|
154
|
+
| `MFR` | MANUFACTURER |
|
|
155
|
+
| `MIN` | MINIMUM |
|
|
156
|
+
| `MISC` | MISCELLANEOUS |
|
|
157
|
+
| `N` | NORTH |
|
|
158
|
+
| `(N)` | NEW |
|
|
159
|
+
| `NF` | NEAR FACE |
|
|
160
|
+
| `NIC` | NOT IN CONTRACT |
|
|
161
|
+
| `NOM` | NOMINAL |
|
|
162
|
+
| `NO` (or `#`) | NUMBER |
|
|
163
|
+
| `NS` | NEAR SIDE |
|
|
164
|
+
| `NTS` | NOT TO SCALE |
|
|
165
|
+
| `OC` | ON CENTER |
|
|
166
|
+
| `OD` | OUTSIDE DIAMETER |
|
|
167
|
+
| `OF` | OUTSIDE FACE |
|
|
168
|
+
| `OH` | OPPOSITE HAND |
|
|
169
|
+
| `OPNG` | OPENING |
|
|
170
|
+
| `OPP` | OPPOSITE |
|
|
171
|
+
| `OSB` | ORIENTED STRAND BOARD |
|
|
172
|
+
| `PARA` (or `∥`) | PARALLEL |
|
|
173
|
+
| `PERP` (or `⊥`) | PERPENDICULAR |
|
|
174
|
+
| `PJP` | PARTIAL JOINT PENETRATION (WELD) |
|
|
175
|
+
| `PL` | PLATE |
|
|
176
|
+
| `PLF` | POUNDS PER LINEAR FOOT |
|
|
177
|
+
| `PLYWD` | PLYWOOD |
|
|
178
|
+
| `PNL` | PANEL |
|
|
179
|
+
| `PSF` | POUNDS PER SQUARE FOOT |
|
|
180
|
+
| `PSI` | POUNDS PER SQUARE INCH |
|
|
181
|
+
| `PT` | POST-TENSIONED / PRESSURE-TREATED / POINT |
|
|
182
|
+
| `R` / `RAD` | RADIUS |
|
|
183
|
+
| `RD` | ROOF DRAIN |
|
|
184
|
+
| `REF` | REFERENCE |
|
|
185
|
+
| `REINF` | REINFORCING / REINFORCEMENT |
|
|
186
|
+
| `REQ'D` | REQUIRED |
|
|
187
|
+
| `RET` | RETAINING |
|
|
188
|
+
| `RM` | ROOM |
|
|
189
|
+
| `RO` | ROUGH OPENING |
|
|
190
|
+
| `SCHED` | SCHEDULE |
|
|
191
|
+
| `SECT` | SECTION |
|
|
192
|
+
| `SF` | SQUARE FEET |
|
|
193
|
+
| `SHT` | SHEET |
|
|
194
|
+
| `SHTHG` | SHEATHING |
|
|
195
|
+
| `SIM` | SIMILAR |
|
|
196
|
+
| `SLBB` | SHORT LEGS BACK-TO-BACK |
|
|
197
|
+
| `SOG` | SLAB ON GRADE |
|
|
198
|
+
| `SPCG` | SPACING |
|
|
199
|
+
| `SPECS` | SPECIFICATIONS |
|
|
200
|
+
| `SQ` | SQUARE |
|
|
201
|
+
| `SS` | SELECT STRUCTURAL / STAINLESS STEEL |
|
|
202
|
+
| `SSL` | SHORT SLOTTED HOLES |
|
|
203
|
+
| `STAGG` | STAGGERED |
|
|
204
|
+
| `STD` | STANDARD |
|
|
205
|
+
| `STIFF` | STIFFENER |
|
|
206
|
+
| `STIRR` | STIRRUP |
|
|
207
|
+
| `STL` | STEEL |
|
|
208
|
+
| `STRUCT` | STRUCTURAL |
|
|
209
|
+
| `SYM` | SYMMETRICAL |
|
|
210
|
+
| `T&B` | TOP AND BOTTOM |
|
|
211
|
+
| `T&G` | TONGUE AND GROOVE |
|
|
212
|
+
| `TB` | TIE BEAM |
|
|
213
|
+
| `TEMP` | TEMPORARY / TEMPERATURE |
|
|
214
|
+
| `THK` | THICK / THICKNESS |
|
|
215
|
+
| `THR` | THREADED |
|
|
216
|
+
| `THRU` | THROUGH |
|
|
217
|
+
| `TN` | TOE NAIL |
|
|
218
|
+
| `TOC` | TOP OF CONCRETE / TOP OF CURB |
|
|
219
|
+
| `TOF` | TOP OF FOOTING |
|
|
220
|
+
| `TOS` | TOP OF STEEL / TOP OF SLAB |
|
|
221
|
+
| `TOW` | TOP OF WALL |
|
|
222
|
+
| `TYP` | TYPICAL |
|
|
223
|
+
| `UON` / `UNO` | UNLESS OTHERWISE NOTED |
|
|
224
|
+
| `VERT` (or `V`) | VERTICAL |
|
|
225
|
+
| `VIF` | VERIFY IN FIELD |
|
|
226
|
+
| `W` | WEST |
|
|
227
|
+
| `W/` | WITH |
|
|
228
|
+
| `WD` | WOOD |
|
|
229
|
+
| `WP` | WORK POINT (also WATERPROOF) |
|
|
230
|
+
| `WT` | WEIGHT (also a structural-tee shape — see below) |
|
|
231
|
+
| `WWF` | WELDED WIRE FABRIC |
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Structural steel shape designations
|
|
236
|
+
|
|
237
|
+
How a profile label decodes (the label sits on/near the member centerline, usually mid-span):
|
|
238
|
+
|
|
239
|
+
| Designation | Shape |
|
|
240
|
+
|-------------|-------|
|
|
241
|
+
| `W` | Wide-flange (W-shape) — e.g. `W16X26` |
|
|
242
|
+
| `M` | Miscellaneous I-shape |
|
|
243
|
+
| `S` | American Standard (S-shape / "I-beam") |
|
|
244
|
+
| `HP` | Bearing pile (HP-shape) |
|
|
245
|
+
| `C` | American Standard Channel |
|
|
246
|
+
| `MC` | Miscellaneous Channel |
|
|
247
|
+
| `L` | Angle (equal/unequal leg) — e.g. `L4X4X1/4` |
|
|
248
|
+
| `WT` / `MT` / `ST` | Structural Tee (cut from W / M / S) |
|
|
249
|
+
| `HSS` | Hollow Structural Section (rectangular/square/round) — e.g. `HSS6X6X3/8` |
|
|
250
|
+
| `PIPE` | Standard pipe; `PIPE-X` extra-strong; `PIPE-XX` double-extra-strong |
|
|
251
|
+
| `TS` | Tube Steel (older designation for HSS) |
|
|
252
|
+
| `PL` | Plate — e.g. `PL1/2X8` |
|
|
253
|
+
|
|
254
|
+
**Reading the size numbers:**
|
|
255
|
+
- **W/M/S/HP/C/MC and WT/MT/ST:** the trailing number after the `X` IS the weight in **lb/ft (plf)** —
|
|
256
|
+
no table needed. `W16X26` = nominal depth **16"** × **26 lb/ft**; `WT6X25` (a tee cut from a W) =
|
|
257
|
+
**25 lb/ft**; `C12X20.7` = **20.7 lb/ft**. (Tees inherit weight-based naming from their parent
|
|
258
|
+
W/M/S shape.)
|
|
259
|
+
- **HSS / PIPE / L / PL:** the numbers are **dimensions**, not the weight (e.g. `HSS6X6X3/8` =
|
|
260
|
+
6"×6"×3/8" wall, `L4X4X1/4` = 4"×4"×1/4" leg) — look the weight up in an AISC shapes table
|
|
261
|
+
(or via the `steel-detailer-us` agent).
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Note conventions most useful to a takeoff
|
|
266
|
+
|
|
267
|
+
When reading the General Notes and plan notes, these drive member geometry/metadata directly:
|
|
268
|
+
|
|
269
|
+
- **`TYP`** — a value/detail shown once applies to all like conditions. A "TYP" profile or spacing in
|
|
270
|
+
the notes is the **default** for unlabeled members of that kind.
|
|
271
|
+
- **`TOS` / `TOC` / `TOF` / `BOS`** — top/bottom-of-steel/concrete/footing elevations. A plan default
|
|
272
|
+
TOS ("ALL STEEL T.O.S. = 16'-6" UON") sets every member's elevation unless a local callout overrides.
|
|
273
|
+
- **`UON` / `UNO`** — the stated value holds everywhere *unless* a local note says otherwise. Treat a
|
|
274
|
+
"TYP … UON" note as the fallback and watch for the exceptions.
|
|
275
|
+
- **`EQ`** — equally-spaced members between two references (no explicit dimension).
|
|
276
|
+
- **`SIM`** — a detail is repeated similarly elsewhere (mirror/rotate as needed).
|
|
277
|
+
- **`(N)` / `(E)`** — new vs existing. `(E)` members are frequently **out of the new-steel takeoff**;
|
|
278
|
+
confirm against line weight/scope before counting them.
|
|
279
|
+
- **Connection-type words in notes** — `MOMENT`, `DRAG`, `SHEAR`, `BRACED`, `PINNED`, `FIXED` name the
|
|
280
|
+
connection type at member ends; feed them into the member-end connection note + the connection
|
|
281
|
+
library (type → design detail# → platform component).
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Marks vs abbreviations (do not confuse)
|
|
286
|
+
|
|
287
|
+
A *mark* identifies a specific member or frame and is defined **by the project's schedules/elevations**,
|
|
288
|
+
not by this glossary:
|
|
289
|
+
|
|
290
|
+
- `MF` / `BF` — Moment Frame / Braced Frame member marks (resolved from the Frame Elevation sheets;
|
|
291
|
+
the AISC size comes from the moment-frame schedule, not from "MF").
|
|
292
|
+
- `D1`, `D2`, `BR1` — brace/diagonal marks.
|
|
293
|
+
- `C1`, `B12`, `G3` — column/beam/girder marks keyed to a schedule.
|
|
294
|
+
|
|
295
|
+
Never expand a mark using this abbreviation list. Resolve it from the frame elevations / column &
|
|
296
|
+
beam schedules, and if unresolved, leave it as an RFI.
|
package/dist/web/app.css
CHANGED
|
@@ -479,6 +479,152 @@
|
|
|
479
479
|
.onb-tour-btn:disabled { cursor: default; opacity: 0.85; box-shadow: none; }
|
|
480
480
|
.onb-tour-ico { font-size: 11px; opacity: 0.7; margin-left: 6px; }
|
|
481
481
|
|
|
482
|
+
/* ===== Per-workflow onboarding beacon ("Start here") ===== */
|
|
483
|
+
/* A header pill in the run cluster. Calm by default (accent-soft, like the
|
|
484
|
+
wf-update pill); pulses while the open workflow is new to the user. Accent —
|
|
485
|
+
NOT warn — because it's positive/new, and warn is already the drift colour. */
|
|
486
|
+
.guide-beacon {
|
|
487
|
+
display: inline-flex;
|
|
488
|
+
align-items: center;
|
|
489
|
+
gap: 5px;
|
|
490
|
+
border: 1px solid var(--accent-dim);
|
|
491
|
+
background: var(--accent-soft);
|
|
492
|
+
color: var(--accent-bright);
|
|
493
|
+
font-family: var(--ui);
|
|
494
|
+
font-size: 11px;
|
|
495
|
+
letter-spacing: 0.02em;
|
|
496
|
+
padding: 3px 11px;
|
|
497
|
+
border-radius: 999px;
|
|
498
|
+
cursor: pointer;
|
|
499
|
+
white-space: nowrap;
|
|
500
|
+
flex-shrink: 0; /* the workflow picker shrinks first; the beacon never squashes */
|
|
501
|
+
transition: border-color 0.15s, color 0.15s, background 0.15s;
|
|
502
|
+
}
|
|
503
|
+
.guide-beacon[hidden] { display: none; }
|
|
504
|
+
.guide-beacon:hover { border-color: var(--accent); color: var(--accent); }
|
|
505
|
+
.guide-beacon:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
506
|
+
.guide-beacon-ico { font-size: 11px; line-height: 1; }
|
|
507
|
+
/* "New to you": a slow box-shadow bloom (GPU-composited, no layout repaint) reading
|
|
508
|
+
as a calm invitation, not an alarm. Same motion dialect as the rest of the app. */
|
|
509
|
+
.guide-beacon.is-new {
|
|
510
|
+
border-color: var(--accent);
|
|
511
|
+
color: var(--accent-bright);
|
|
512
|
+
animation: guide-pulse 2.8s ease-in-out infinite;
|
|
513
|
+
}
|
|
514
|
+
@keyframes guide-pulse {
|
|
515
|
+
0%, 100% { box-shadow: 0 0 0 0 var(--accent-glow); }
|
|
516
|
+
50% { box-shadow: 0 0 0 6px rgba(74, 158, 255, 0); }
|
|
517
|
+
}
|
|
518
|
+
@media (prefers-reduced-motion: reduce) {
|
|
519
|
+
/* No pulse — a steady ring (shape + contrast, not colour alone) marks "new". */
|
|
520
|
+
.guide-beacon.is-new { animation: none; box-shadow: 0 0 0 1px var(--accent-dim); }
|
|
521
|
+
}
|
|
522
|
+
/* Spotlit (first-open): raised above the dim, opaque, ringed. The header creates no
|
|
523
|
+
stacking context, so z-index here lifts it above the fixed spotlight overlay. */
|
|
524
|
+
.guide-beacon.spotlit {
|
|
525
|
+
position: relative;
|
|
526
|
+
z-index: 95;
|
|
527
|
+
animation: none;
|
|
528
|
+
background: var(--surface-3);
|
|
529
|
+
border-color: var(--accent);
|
|
530
|
+
color: var(--accent-bright);
|
|
531
|
+
box-shadow: 0 0 0 3px var(--accent-glow), 0 0 20px 4px var(--accent-glow);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/* ===== First-open spotlight ===== */
|
|
535
|
+
.guide-spotlight {
|
|
536
|
+
position: fixed;
|
|
537
|
+
inset: 0;
|
|
538
|
+
z-index: 90; /* below modals (100), above the page; the beacon (.spotlit, 95) sits over it */
|
|
539
|
+
background: rgba(7, 10, 15, 0.72);
|
|
540
|
+
backdrop-filter: blur(2px);
|
|
541
|
+
-webkit-backdrop-filter: blur(2px);
|
|
542
|
+
display: none;
|
|
543
|
+
}
|
|
544
|
+
.guide-spotlight:not([hidden]) { display: block; animation: fade-in 0.18s ease-out; }
|
|
545
|
+
.guide-spotlight-bubble {
|
|
546
|
+
position: fixed;
|
|
547
|
+
z-index: 91;
|
|
548
|
+
max-width: 280px;
|
|
549
|
+
background: var(--surface);
|
|
550
|
+
border: 1px solid var(--accent-dim);
|
|
551
|
+
border-radius: 8px;
|
|
552
|
+
padding: 12px 14px;
|
|
553
|
+
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
|
|
554
|
+
}
|
|
555
|
+
.guide-spotlight-text { font-size: 12.5px; line-height: 1.5; color: var(--text); }
|
|
556
|
+
.guide-spotlight-text strong { color: var(--accent-bright); }
|
|
557
|
+
.guide-spotlight-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 11px; }
|
|
558
|
+
.guide-spotlight-actions button { padding: 5px 13px; font-size: 12px; }
|
|
559
|
+
.guide-spotlight-actions .ghost { background: var(--surface-2); color: var(--text-muted); }
|
|
560
|
+
.guide-spotlight-actions .ghost:hover { color: var(--text); border-color: var(--accent-dim); }
|
|
561
|
+
.guide-spotlight-actions .primary { background: var(--accent); color: #fff; border-color: var(--accent); font-weight: 600; }
|
|
562
|
+
.guide-spotlight-actions .primary:hover { background: var(--accent-bright); box-shadow: 0 0 14px var(--accent-glow); }
|
|
563
|
+
|
|
564
|
+
/* ===== Guide panel (modal) ===== */
|
|
565
|
+
.modal.guide {
|
|
566
|
+
width: 520px;
|
|
567
|
+
max-width: 92vw;
|
|
568
|
+
max-height: 82vh;
|
|
569
|
+
display: flex;
|
|
570
|
+
flex-direction: column;
|
|
571
|
+
}
|
|
572
|
+
.guide-eyebrow {
|
|
573
|
+
font-size: 10px;
|
|
574
|
+
text-transform: uppercase;
|
|
575
|
+
letter-spacing: 0.16em;
|
|
576
|
+
color: var(--accent-bright);
|
|
577
|
+
margin-bottom: 6px;
|
|
578
|
+
}
|
|
579
|
+
/* The body scrolls between the pinned title and the pinned actions; theme its
|
|
580
|
+
scrollbar so no native white bar leaks against the dark surface. */
|
|
581
|
+
#guide-body {
|
|
582
|
+
overflow-y: auto;
|
|
583
|
+
flex: 1 1 auto;
|
|
584
|
+
min-height: 0;
|
|
585
|
+
margin-top: 2px;
|
|
586
|
+
scrollbar-width: thin;
|
|
587
|
+
scrollbar-color: var(--border-strong) transparent;
|
|
588
|
+
}
|
|
589
|
+
#guide-body::-webkit-scrollbar { width: 7px; }
|
|
590
|
+
#guide-body::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 7px; }
|
|
591
|
+
#guide-body::-webkit-scrollbar-track { background: transparent; }
|
|
592
|
+
.guide-section-label {
|
|
593
|
+
font-size: 10px;
|
|
594
|
+
text-transform: uppercase;
|
|
595
|
+
letter-spacing: 0.14em;
|
|
596
|
+
color: var(--text-muted);
|
|
597
|
+
margin: 14px 0 7px;
|
|
598
|
+
}
|
|
599
|
+
.guide-section-label:first-child { margin-top: 2px; }
|
|
600
|
+
.guide-desc { font-size: 12.5px; line-height: 1.55; color: var(--text); }
|
|
601
|
+
.guide-desc p { margin: 0 0 8px; }
|
|
602
|
+
.guide-desc p:last-child { margin-bottom: 0; }
|
|
603
|
+
.guide-desc code { font-family: var(--mono); background: var(--surface-2); padding: 1px 5px; border-radius: 3px; color: var(--text-muted); font-size: 11px; }
|
|
604
|
+
.guide-desc-empty { color: var(--text-muted); font-style: italic; }
|
|
605
|
+
.guide-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 9px; }
|
|
606
|
+
.guide-step { display: flex; gap: 10px; align-items: baseline; }
|
|
607
|
+
.guide-box { font-family: var(--mono); font-size: 13px; min-width: 16px; color: var(--text-dim); line-height: 1.3; }
|
|
608
|
+
.guide-step-label { font-size: 12.5px; color: var(--text); }
|
|
609
|
+
.guide-step-note { font-size: 11px; color: var(--text-dim); font-style: italic; }
|
|
610
|
+
/* Done: ✓ in ok-green + struck/dimmed label, so the current step stands out on
|
|
611
|
+
shape + contrast, not colour alone. Current: → in accent + bold label. */
|
|
612
|
+
.guide-step.done .guide-box { color: var(--ok); }
|
|
613
|
+
.guide-step.done .guide-step-label { color: var(--text-dim); text-decoration: line-through; text-decoration-color: var(--text-dim); }
|
|
614
|
+
.guide-step.current .guide-box { color: var(--accent); }
|
|
615
|
+
.guide-step.current .guide-step-label { color: var(--text); font-weight: 600; }
|
|
616
|
+
.guide-walk-hint {
|
|
617
|
+
font-size: 11px;
|
|
618
|
+
line-height: 1.5;
|
|
619
|
+
color: var(--text-muted);
|
|
620
|
+
margin-top: 14px;
|
|
621
|
+
padding-top: 12px;
|
|
622
|
+
border-top: 1px solid var(--border);
|
|
623
|
+
}
|
|
624
|
+
.guide-walk-hint strong { color: var(--text); font-weight: 600; }
|
|
625
|
+
.modal.guide .modal-actions { margin-top: 16px; }
|
|
626
|
+
.modal.guide .modal-actions #guide-got-it { margin-right: auto; }
|
|
627
|
+
|
|
482
628
|
.agent-card {
|
|
483
629
|
background: var(--surface);
|
|
484
630
|
border: 1px solid var(--border-strong);
|
package/dist/web/aware.js
CHANGED
|
@@ -531,11 +531,193 @@
|
|
|
531
531
|
});
|
|
532
532
|
}
|
|
533
533
|
|
|
534
|
+
// ── Per-workflow onboarding beacon ──────────────────────────────────────────
|
|
535
|
+
// "New to you" = the user hasn't completed a first real Run of this workflow and
|
|
536
|
+
// hasn't dismissed its guide. Three bits per app id, persisted per origin; all
|
|
537
|
+
// default false, so every workflow starts new. Never touches the .flo or lock.
|
|
538
|
+
const LS_GUIDE = 'floless:guide';
|
|
539
|
+
function guideStateAll() {
|
|
540
|
+
try { const v = JSON.parse(localStorage.getItem(LS_GUIDE) || '{}'); return (v && typeof v === 'object') ? v : {}; }
|
|
541
|
+
catch { return {}; }
|
|
542
|
+
}
|
|
543
|
+
function guideFor(id) { return guideStateAll()[id] || {}; }
|
|
544
|
+
function setGuide(id, patch) {
|
|
545
|
+
if (!id) return;
|
|
546
|
+
try {
|
|
547
|
+
const all = guideStateAll();
|
|
548
|
+
all[id] = { ...(all[id] || {}), ...patch };
|
|
549
|
+
localStorage.setItem(LS_GUIDE, JSON.stringify(all));
|
|
550
|
+
} catch { /* private mode / quota */ }
|
|
551
|
+
}
|
|
552
|
+
// The beacon pulses while a workflow is new to the user.
|
|
553
|
+
function beaconIsNew(id) { const g = guideFor(id); return !g.firstRunDone && !g.dismissed; }
|
|
554
|
+
|
|
555
|
+
function paintBeacon(app) {
|
|
556
|
+
const $b = document.getElementById('guide-beacon');
|
|
557
|
+
if (!$b) return;
|
|
558
|
+
if (!app) { $b.hidden = true; $b.classList.remove('is-new', 'spotlit'); return; }
|
|
559
|
+
const isNew = beaconIsNew(app.id);
|
|
560
|
+
$b.hidden = false;
|
|
561
|
+
$b.classList.toggle('is-new', isNew);
|
|
562
|
+
const ico = $b.querySelector('.guide-beacon-ico');
|
|
563
|
+
const label = $b.querySelector('.guide-beacon-label');
|
|
564
|
+
// Monochrome dingbats (✦ / ?), not colour emoji — the locked baseline uses text
|
|
565
|
+
// glyphs (★, ▸); the pulse, not an emoji, is the "new" signal.
|
|
566
|
+
if (ico) ico.textContent = isNew ? '✦' : '?';
|
|
567
|
+
if (label) label.textContent = isNew ? 'Start here' : 'Guide';
|
|
568
|
+
$b.dataset.tip = isNew
|
|
569
|
+
? 'New here? See what this workflow does and how to run it.'
|
|
570
|
+
: 'Open the guide for this workflow.';
|
|
571
|
+
$b.setAttribute('aria-label', (isNew ? 'Start here — guide for ' : 'Guide for ') + (app.displayName || app.id));
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Record a completed real run → the workflow is no longer new; calm the beacon (and
|
|
575
|
+
// refresh an open guide so the Run step ticks). Idempotent.
|
|
576
|
+
function markFirstRunDone(id) {
|
|
577
|
+
if (!id || guideFor(id).firstRunDone) return;
|
|
578
|
+
setGuide(id, { firstRunDone: true });
|
|
579
|
+
if (id === currentId) {
|
|
580
|
+
const app = apps.get(id);
|
|
581
|
+
if (app) paintBeacon(app);
|
|
582
|
+
const $m = document.getElementById('guide-modal');
|
|
583
|
+
if ($m && $m.classList.contains('show')) renderGuide();
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// The guide panel's live checklist — each step ticks from real app state. The
|
|
588
|
+
// "Set inputs" row is omitted for a workflow with no declared inputs.
|
|
589
|
+
function guideSteps(app) {
|
|
590
|
+
const steps = [];
|
|
591
|
+
if (Array.isArray(app.inputs) && app.inputs.length) {
|
|
592
|
+
const vals = currentInputs();
|
|
593
|
+
const allSet = app.inputs.every((i) => {
|
|
594
|
+
const v = vals[i.name];
|
|
595
|
+
return v !== undefined && v !== null && String(v).trim() !== '';
|
|
596
|
+
});
|
|
597
|
+
steps.push({ label: 'Set inputs', note: 'the values this workflow runs with', done: allSet });
|
|
598
|
+
}
|
|
599
|
+
steps.push({ label: 'Compile', note: 'freeze the approved lock', done: app.runState === 'ready' });
|
|
600
|
+
steps.push({ label: 'Run', note: 'against the live host — results render here', done: !!guideFor(app.id).firstRunDone });
|
|
601
|
+
return steps;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function renderGuide() {
|
|
605
|
+
const app = currentId && apps.get(currentId);
|
|
606
|
+
const $body = document.getElementById('guide-body');
|
|
607
|
+
const $title = document.getElementById('guide-title');
|
|
608
|
+
const $eyebrow = document.getElementById('guide-eyebrow');
|
|
609
|
+
if (!app || !$body) return;
|
|
610
|
+
if ($title) $title.textContent = app.displayName || app.id;
|
|
611
|
+
if ($eyebrow) $eyebrow.textContent = beaconIsNew(app.id) ? '✦ Start here' : '✦ Workflow guide';
|
|
612
|
+
const desc = (app.description || '').trim();
|
|
613
|
+
const descHtml = desc
|
|
614
|
+
? `<div class="guide-desc">${desc.split(/\n{2,}/).map((p) => `<p>${mdInline(escapeHtml(p.trim()))}</p>`).join('')}</div>`
|
|
615
|
+
: `<div class="guide-desc guide-desc-empty"><p>A ${app.nodes.length}-step workflow. Set its inputs, Compile to freeze the approved lock, then Run it against your live host.</p></div>`;
|
|
616
|
+
const steps = guideSteps(app);
|
|
617
|
+
const curIdx = steps.findIndex((s) => !s.done);
|
|
618
|
+
const stepsHtml = `<ol class="guide-steps" role="list">${steps.map((s, i) => {
|
|
619
|
+
const state = s.done ? 'done' : i === curIdx ? 'current' : 'todo';
|
|
620
|
+
// Shape carries state, not colour alone: ✓ done · → current · ☐ to-do.
|
|
621
|
+
const box = s.done ? '✓' : i === curIdx ? '→' : '☐';
|
|
622
|
+
const cur = i === curIdx ? ' aria-current="step"' : '';
|
|
623
|
+
return `<li class="guide-step ${state}"${cur}><span class="guide-box" aria-hidden="true">${box}</span>` +
|
|
624
|
+
`<span class="guide-step-text"><span class="guide-step-label">${escapeHtml(s.label)}</span> ` +
|
|
625
|
+
`<span class="guide-step-note">— ${escapeHtml(s.note)}</span></span>` +
|
|
626
|
+
`<span class="sr-only">${s.done ? '(done)' : i === curIdx ? '(next step)' : '(to do)'}</span></li>`;
|
|
627
|
+
}).join('')}</ol>`;
|
|
628
|
+
const walkHint = `<div class="guide-walk-hint">A bigger workflow? <strong>Walk me through it</strong> hands off to your terminal AI for a step-by-step tour using this workflow's own skill.</div>`;
|
|
629
|
+
$body.innerHTML =
|
|
630
|
+
`<div class="guide-section-label">What this does</div>` + descHtml +
|
|
631
|
+
`<div class="guide-section-label">How to go through it</div>` + stepsHtml + walkHint;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function openGuide() {
|
|
635
|
+
if (!currentId) { showToast('open a workflow first', 'warn'); return; }
|
|
636
|
+
renderGuide();
|
|
637
|
+
const $m = document.getElementById('guide-modal');
|
|
638
|
+
showModal($m);
|
|
639
|
+
// Focus the dialog shell (not a button) so the checklist is read first; Tab then
|
|
640
|
+
// moves to Got it → Walk me through it. Focus returns to the beacon on close.
|
|
641
|
+
const shell = $m && $m.querySelector('.modal.guide');
|
|
642
|
+
if (shell) { try { shell.focus(); } catch { /* not focusable */ } }
|
|
643
|
+
}
|
|
644
|
+
// Closing the guide (Got it / backdrop / Esc) marks it seen → the beacon stops
|
|
645
|
+
// pulsing even before a first run (the user has looked; don't nag).
|
|
646
|
+
function closeGuide() {
|
|
647
|
+
if (currentId) {
|
|
648
|
+
setGuide(currentId, { dismissed: true });
|
|
649
|
+
const app = apps.get(currentId);
|
|
650
|
+
if (app) paintBeacon(app);
|
|
651
|
+
}
|
|
652
|
+
hideModal(document.getElementById('guide-modal'));
|
|
653
|
+
const $b = document.getElementById('guide-beacon');
|
|
654
|
+
if ($b && !$b.hidden) { try { $b.focus(); } catch { /* */ } }
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// "Walk me through it" → relay the ask to the terminal AI, the house way: queue a
|
|
658
|
+
// request AND copy the prompt, toast, light the footer requests pill, list it in the
|
|
659
|
+
// Requests window. The UI never guides itself; it hands intent to the assistant.
|
|
660
|
+
async function requestGuide() {
|
|
661
|
+
if (!currentId) { showToast('open a workflow first', 'warn'); return; }
|
|
662
|
+
try {
|
|
663
|
+
const { request } = await api('/api/guide', { method: 'POST', body: JSON.stringify({ appId: currentId }) });
|
|
664
|
+
const copied = await copyToClipboard(markedInstruction(request));
|
|
665
|
+
showToast(copied ? 'Sent to your assistant · copied to clipboard' : 'Sent to your assistant', 'ok');
|
|
666
|
+
appendNarration(`Asked your terminal AI to walk you through <code>${escapeHtml(currentId)}</code> — watch the glowing <strong>requests</strong> pill (bottom-right), or switch to your terminal.${copied ? ' The prompt is also on your clipboard.' : ''}`);
|
|
667
|
+
loadRequests();
|
|
668
|
+
closeGuide(); // engaging with the guidance also calms the beacon
|
|
669
|
+
} catch (e) { reportErr(e); }
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// ── First-open spotlight (one-time per workflow) ────────────────────────────
|
|
673
|
+
// Dims the canvas, raises the beacon, and points a small caption at it; Esc /
|
|
674
|
+
// click-away / either button dismiss it for good. Slice 2 of the onboarding beacon.
|
|
675
|
+
let _spotlightKey = null;
|
|
676
|
+
function maybeSpotlight(app) {
|
|
677
|
+
if (!app) return;
|
|
678
|
+
const g = guideFor(app.id);
|
|
679
|
+
if (g.spotlightShown || g.dismissed || g.firstRunDone) return;
|
|
680
|
+
if (document.querySelector('.modal-backdrop.show')) return; // don't pile on a dialog
|
|
681
|
+
if ($contractEditor && !$contractEditor.hidden) return;
|
|
682
|
+
setTimeout(() => {
|
|
683
|
+
if (currentId !== app.id) return; // switched away during the delay
|
|
684
|
+
if (guideFor(app.id).spotlightShown) return;
|
|
685
|
+
if (document.querySelector('.modal-backdrop.show')) return;
|
|
686
|
+
showSpotlight(app);
|
|
687
|
+
}, 700);
|
|
688
|
+
}
|
|
689
|
+
function showSpotlight(app) {
|
|
690
|
+
const $sp = document.getElementById('guide-spotlight');
|
|
691
|
+
const $b = document.getElementById('guide-beacon');
|
|
692
|
+
if (!$sp || !$b || $b.hidden) return;
|
|
693
|
+
setGuide(app.id, { spotlightShown: true });
|
|
694
|
+
$b.classList.add('spotlit');
|
|
695
|
+
$sp.hidden = false;
|
|
696
|
+
// Anchor the caption bubble just under the beacon, right-aligned to it.
|
|
697
|
+
const bubble = $sp.querySelector('.guide-spotlight-bubble');
|
|
698
|
+
if (bubble) {
|
|
699
|
+
const r = $b.getBoundingClientRect();
|
|
700
|
+
bubble.style.top = (r.bottom + 10) + 'px';
|
|
701
|
+
bubble.style.right = Math.max(8, window.innerWidth - r.right) + 'px';
|
|
702
|
+
}
|
|
703
|
+
_spotlightKey = (e) => { if (e.key === 'Escape') { e.preventDefault(); hideSpotlight(); } };
|
|
704
|
+
document.addEventListener('keydown', _spotlightKey);
|
|
705
|
+
if (bubble) { try { bubble.focus(); } catch { /* */ } }
|
|
706
|
+
}
|
|
707
|
+
function hideSpotlight() {
|
|
708
|
+
const $sp = document.getElementById('guide-spotlight');
|
|
709
|
+
const $b = document.getElementById('guide-beacon');
|
|
710
|
+
if ($sp) $sp.hidden = true;
|
|
711
|
+
if ($b) $b.classList.remove('spotlit');
|
|
712
|
+
if (_spotlightKey) { document.removeEventListener('keydown', _spotlightKey); _spotlightKey = null; }
|
|
713
|
+
}
|
|
714
|
+
|
|
534
715
|
// ── load / select / compile / run ───────────────────────────────────────────
|
|
535
716
|
|
|
536
717
|
let _wfUpdates = []; // workflow updates available, keyed by app id — polled by refreshWorkflowUpdates() below
|
|
537
718
|
|
|
538
719
|
async function loadApp(id) {
|
|
720
|
+
hideSpotlight(); // a spotlight pointing at the previous app's beacon must clear on switch
|
|
539
721
|
const { app } = await api(`/api/app/${encodeURIComponent(id)}`);
|
|
540
722
|
apps.set(id, app);
|
|
541
723
|
currentId = id;
|
|
@@ -555,8 +737,10 @@
|
|
|
555
737
|
seedAppInputs(app);
|
|
556
738
|
markSpecialNodes();
|
|
557
739
|
paintGate(app);
|
|
740
|
+
paintBeacon(app); // pulse "Start here" while this workflow is new to the user
|
|
558
741
|
refreshDirtyIndicator(); // clean app → dot off; switched back to a dirty one → dot on
|
|
559
742
|
renderWfUpdatePill(); // show the "↑ Update to vX" pill when THIS app has a newer published version
|
|
743
|
+
maybeSpotlight(app); // one-time first-open spotlight pointing at the beacon (slice 2)
|
|
560
744
|
}
|
|
561
745
|
|
|
562
746
|
async function loadApps() {
|
|
@@ -574,6 +758,8 @@
|
|
|
574
758
|
if ($menuBakeItem) $menuBakeItem.disabled = true;
|
|
575
759
|
if ($wfTrigger) $wfTrigger.disabled = true;
|
|
576
760
|
setComboTriggerLabel('no workflows installed');
|
|
761
|
+
paintBeacon(null); // no workflow open → hide the per-workflow beacon
|
|
762
|
+
hideSpotlight();
|
|
577
763
|
renderCanvasPlaceholder('empty');
|
|
578
764
|
return;
|
|
579
765
|
}
|
|
@@ -814,8 +1000,18 @@
|
|
|
814
1000
|
if (v != null) localStorage.setItem(lsInputsKey(toId), v);
|
|
815
1001
|
} catch { /* private mode / quota */ }
|
|
816
1002
|
}
|
|
1003
|
+
// A rename is the SAME workflow under a new id → carry its onboarding-seen state so it
|
|
1004
|
+
// doesn't re-pulse "Start here" / re-show the spotlight. (Not done on duplicate: a copy
|
|
1005
|
+
// is a genuinely new workflow that the user hasn't run, so it should start new.)
|
|
1006
|
+
function migrateGuideLS(fromId, toId) {
|
|
1007
|
+
try {
|
|
1008
|
+
const all = guideStateAll();
|
|
1009
|
+
if (all[fromId]) { all[toId] = all[fromId]; localStorage.setItem(LS_GUIDE, JSON.stringify(all)); }
|
|
1010
|
+
} catch { /* private mode / quota */ }
|
|
1011
|
+
}
|
|
817
1012
|
function dropClientAppState(id) {
|
|
818
1013
|
try { localStorage.removeItem(lsInputsKey(id)); } catch { /* ignore */ }
|
|
1014
|
+
try { const all = guideStateAll(); if (all[id]) { delete all[id]; localStorage.setItem(LS_GUIDE, JSON.stringify(all)); } } catch { /* ignore */ }
|
|
819
1015
|
appInputValues.delete(id); dirtyBaseline.delete(id); apps.delete(id);
|
|
820
1016
|
}
|
|
821
1017
|
|
|
@@ -843,6 +1039,7 @@
|
|
|
843
1039
|
// so a rename never silently drops the user's inputs (#85 review). loadApp() only seeds
|
|
844
1040
|
// inputs when the id is absent, so pre-setting them here preserves unsaved edits.
|
|
845
1041
|
migrateInputsLS(oldId, r.id);
|
|
1042
|
+
migrateGuideLS(oldId, r.id); // carry onboarding-seen state so the renamed workflow doesn't re-pulse
|
|
846
1043
|
if (appInputValues.has(oldId)) appInputValues.set(r.id, appInputValues.get(oldId));
|
|
847
1044
|
if (dirtyBaseline.has(oldId)) dirtyBaseline.set(r.id, dirtyBaseline.get(oldId));
|
|
848
1045
|
const wasOpen = currentId === oldId;
|
|
@@ -1767,6 +1964,7 @@
|
|
|
1767
1964
|
$reportSub.innerHTML = `Live 3D view from <code>${escapeHtml(nodeId)}</code>${inputBadge ? ' · ' + escapeHtml(inputBadge) : ''} — drag to orbit, scroll to zoom, click an element to inspect.`;
|
|
1768
1965
|
}
|
|
1769
1966
|
paintReport(html, interactive);
|
|
1967
|
+
markFirstRunDone(app.id); // a real run rendered → this workflow is no longer "new" (app captured pre-await; a mid-run switch must not mark the wrong workflow)
|
|
1770
1968
|
lastReportByApp.set(currentId, { nodeId, label: inputBadge, html, interactive });
|
|
1771
1969
|
$reportModal.dataset.html = '1';
|
|
1772
1970
|
appendNarration(`Rendered the ${interactive ? '3D view' : 'report'} from <strong>${escapeHtml(nodeId)}</strong>${inputBadge ? ' (' + escapeHtml(inputBadge) + ')' : ''} — live run against the model.`);
|
|
@@ -3215,6 +3413,7 @@
|
|
|
3215
3413
|
appendNarration(simulate
|
|
3216
3414
|
? `Simulated run complete · ${steps} node${steps === 1 ? '' : 's'} stubbed (no host needed) · see the <strong>Execution</strong> tab.`
|
|
3217
3415
|
: `Run complete · ${steps} node${steps === 1 ? '' : 's'} against the live model · see the <strong>Execution</strong> tab.`);
|
|
3416
|
+
if (!simulate) markFirstRunDone(app.id); // a real run completed → calm the beacon (app captured pre-await)
|
|
3218
3417
|
} catch (e) {
|
|
3219
3418
|
const msg = (e && e.message) ? e.message : String(e);
|
|
3220
3419
|
if (cancelRequested) {
|
|
@@ -4116,6 +4315,9 @@
|
|
|
4116
4315
|
: '';
|
|
4117
4316
|
return `In floless app "${req.appId}", re-read & re-bake${where} per the floless-app-rebake skill: ${req.instruction}${snaps}`;
|
|
4118
4317
|
}
|
|
4318
|
+
if (req.type === 'guide') {
|
|
4319
|
+
return `Walk me through the floless workflow "${req.appId}" — explain in plain English what it does and how to run it (set its inputs → Compile → ▶ Run). Use your floless-app-${req.appId} skill if one exists; otherwise read the workflow's own description and node notes from GET /api/app/${req.appId} and explain from those. This is a guided explanation — don't change anything.`;
|
|
4320
|
+
}
|
|
4119
4321
|
return '';
|
|
4120
4322
|
}
|
|
4121
4323
|
|
|
@@ -4126,6 +4328,9 @@
|
|
|
4126
4328
|
tweak: 'floless-app-workflows',
|
|
4127
4329
|
'ui-customize': 'floless-app-ui',
|
|
4128
4330
|
rebake: 'floless-app-rebake',
|
|
4331
|
+
// The guided-tour ask is picked up by onboarding; the instruction body names the
|
|
4332
|
+
// per-app floless-app-<appId> skill for depth when one exists.
|
|
4333
|
+
guide: 'floless-app-onboarding',
|
|
4129
4334
|
};
|
|
4130
4335
|
|
|
4131
4336
|
// The COPIED form of a request: instructionFor() prefixed with a self-identifying
|
|
@@ -4220,7 +4425,7 @@
|
|
|
4220
4425
|
return;
|
|
4221
4426
|
}
|
|
4222
4427
|
$list.innerHTML = pendingRequests.map((r) => {
|
|
4223
|
-
const label = r.type === 'use-template' ? 'template' : r.type === 'ui-customize' ? 'dashboard' : r.type === 'rebake' ? 're-bake' : 'tweak';
|
|
4428
|
+
const label = r.type === 'use-template' ? 'template' : r.type === 'ui-customize' ? 'dashboard' : r.type === 'rebake' ? 're-bake' : r.type === 'guide' ? 'guide' : 'tweak';
|
|
4224
4429
|
const badgeCls = r.type === 'tweak' || r.type === 'ui-customize' || r.type === 'rebake' ? 'req-type req-type-tweak' : 'req-type';
|
|
4225
4430
|
const target = r.type === 'tweak' && r.nodeId
|
|
4226
4431
|
? ` · node <code>${escapeHtml(r.nodeId)}</code>`
|
|
@@ -5034,6 +5239,7 @@
|
|
|
5034
5239
|
try {
|
|
5035
5240
|
const res = await api('/api/trigger-run', { method: 'POST', body: JSON.stringify({ id, inputs: currentInputs() }) });
|
|
5036
5241
|
foregroundTrigger = { appId: id, sessionId: res.sessionId, firedCount: 0 };
|
|
5242
|
+
markFirstRunDone(id); // a real live session started → a streaming workflow's "first run" is no longer new
|
|
5037
5243
|
syncRunControls(); // header ■ Stop run becomes reachable; Run disabled while live
|
|
5038
5244
|
paintForegroundListening();
|
|
5039
5245
|
appendNarration(`Live session started for <strong>${escapeHtml(id)}</strong> — the report updates on every event. Click <strong>■ Stop run</strong> (top right) to end it.`);
|
|
@@ -5718,6 +5924,25 @@
|
|
|
5718
5924
|
if (e.key === 'Escape' && $reqModal && $reqModal.classList.contains('show')) $reqModal.classList.remove('show');
|
|
5719
5925
|
});
|
|
5720
5926
|
|
|
5927
|
+
// Per-workflow onboarding beacon + guide panel + first-open spotlight.
|
|
5928
|
+
const $guideBeaconBtn = document.getElementById('guide-beacon');
|
|
5929
|
+
if ($guideBeaconBtn) $guideBeaconBtn.onclick = () => { hideSpotlight(); openGuide(); };
|
|
5930
|
+
const $guideModal = document.getElementById('guide-modal');
|
|
5931
|
+
const $guideGot = document.getElementById('guide-got-it');
|
|
5932
|
+
if ($guideGot) $guideGot.onclick = () => closeGuide();
|
|
5933
|
+
const $guideWalk = document.getElementById('guide-walk');
|
|
5934
|
+
if ($guideWalk) $guideWalk.onclick = () => requestGuide();
|
|
5935
|
+
if ($guideModal) onBackdropDismiss($guideModal, () => closeGuide());
|
|
5936
|
+
document.addEventListener('keydown', (e) => {
|
|
5937
|
+
if (e.key === 'Escape' && $guideModal && $guideModal.classList.contains('show')) closeGuide();
|
|
5938
|
+
});
|
|
5939
|
+
const $spotGot = document.getElementById('guide-spotlight-got');
|
|
5940
|
+
if ($spotGot) $spotGot.onclick = () => hideSpotlight();
|
|
5941
|
+
const $spotShow = document.getElementById('guide-spotlight-show');
|
|
5942
|
+
if ($spotShow) $spotShow.onclick = () => { hideSpotlight(); openGuide(); };
|
|
5943
|
+
const $spotlightEl = document.getElementById('guide-spotlight');
|
|
5944
|
+
if ($spotlightEl) $spotlightEl.onclick = (e) => { if (e.target === $spotlightEl) hideSpotlight(); };
|
|
5945
|
+
|
|
5721
5946
|
// Pull all workspace data (agents/templates/requests + the apps list). Factored
|
|
5722
5947
|
// out of bootWorkspace so the health poll can re-pull it after the server comes
|
|
5723
5948
|
// back from a boot-time outage (setServerStatus → recovery).
|
package/dist/web/index.html
CHANGED
|
@@ -58,6 +58,13 @@
|
|
|
58
58
|
<!-- Agents (⊞) and Routines (⏱) buttons moved into the ≡ menu in #149
|
|
59
59
|
(Ctrl+G / Ctrl+R); the toolbar keeps only the run-critical controls. -->
|
|
60
60
|
<span class="ctl-sep" aria-hidden="true"></span>
|
|
61
|
+
<!-- Per-workflow onboarding beacon. Pulses "✨ Start here" while this workflow is
|
|
62
|
+
new to the user (no first Run completed, not dismissed); calms to a quiet
|
|
63
|
+
"❔ Guide" after — always clickable for re-reference. Hidden until an app loads. -->
|
|
64
|
+
<button id="guide-beacon" class="guide-beacon" type="button" hidden aria-haspopup="dialog" data-tip="New here? See what this workflow does and how to run it.">
|
|
65
|
+
<span class="guide-beacon-ico" aria-hidden="true">✦</span>
|
|
66
|
+
<span class="guide-beacon-label">Start here</span>
|
|
67
|
+
</button>
|
|
61
68
|
<span class="run-state" id="run-state" role="status" aria-live="polite"></span>
|
|
62
69
|
<button id="compile-btn" data-tip="Compile + approve → freeze the .lock">⎙ Compile</button>
|
|
63
70
|
<button id="sim-btn" data-tip="Simulate: stub every node from its output schema — no live host is contacted. Validates the workflow's composition end-to-end, even when the real agents aren't connected yet.">Simulate</button>
|
|
@@ -511,6 +518,37 @@
|
|
|
511
518
|
</div>
|
|
512
519
|
</div>
|
|
513
520
|
|
|
521
|
+
<!-- Per-workflow onboarding "Start here" guide. Surfaces the workflow's plain-English
|
|
522
|
+
description + a live "Set inputs → Compile → Run" checklist (ticks from real state),
|
|
523
|
+
and relays a "walk me through it" ask to the terminal AI. A styled modal — never a
|
|
524
|
+
native dialog; body (#guide-body) is rendered per-open in aware.js. -->
|
|
525
|
+
<div class="modal-backdrop" id="guide-modal">
|
|
526
|
+
<div class="modal guide" role="dialog" aria-modal="true" aria-labelledby="guide-title" tabindex="-1">
|
|
527
|
+
<div class="guide-eyebrow" id="guide-eyebrow">✦ Start here</div>
|
|
528
|
+
<div class="modal-title" id="guide-title">Workflow</div>
|
|
529
|
+
<div class="modal-sub">What this workflow does, and how to go through it.</div>
|
|
530
|
+
<div id="guide-body"></div>
|
|
531
|
+
<div class="modal-actions">
|
|
532
|
+
<button id="guide-got-it">Got it</button>
|
|
533
|
+
<button id="guide-walk" class="primary">Walk me through it →</button>
|
|
534
|
+
</div>
|
|
535
|
+
</div>
|
|
536
|
+
</div>
|
|
537
|
+
|
|
538
|
+
<!-- One-time first-open spotlight: dims the canvas and points at the "Start here"
|
|
539
|
+
beacon, then collapses into it. Shown once per workflow; Esc / click-away / either
|
|
540
|
+
button dismiss it. The beacon gets .spotlit so it sits above the dim. The bubble is
|
|
541
|
+
positioned next to the beacon at open time (aware.js). -->
|
|
542
|
+
<div id="guide-spotlight" class="guide-spotlight" hidden>
|
|
543
|
+
<div class="guide-spotlight-bubble" role="dialog" aria-modal="false" aria-label="New workflow tip" tabindex="-1">
|
|
544
|
+
<div class="guide-spotlight-text">New to this workflow? <strong>Start here</strong> — see what it does and how to run it.</div>
|
|
545
|
+
<div class="guide-spotlight-actions">
|
|
546
|
+
<button type="button" id="guide-spotlight-got" class="ghost">Got it</button>
|
|
547
|
+
<button type="button" id="guide-spotlight-show" class="primary">Show me</button>
|
|
548
|
+
</div>
|
|
549
|
+
</div>
|
|
550
|
+
</div>
|
|
551
|
+
|
|
514
552
|
<!-- Routines — scheduled .flo runs. The list; the add/edit form is its own modal. -->
|
|
515
553
|
<div class="modal-backdrop" id="routines-modal">
|
|
516
554
|
<div class="modal routines">
|