@thatopen/services 0.7.0 → 0.9.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.
Files changed (77) hide show
  1. package/README.md +62 -62
  2. package/dist/built-in/index.d.ts +66 -41
  3. package/dist/cli/lib/revit-addin.d.ts +8 -0
  4. package/dist/cli.js +154 -13
  5. package/dist/core/client.d.ts +6 -0
  6. package/dist/core/platform-client.d.ts +139 -0
  7. package/dist/core/platform-client.live.test.d.ts +1 -0
  8. package/dist/core/platform-client.notifications.test.d.ts +1 -0
  9. package/dist/index.cjs.js +2 -2
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.es.js +635 -445
  12. package/dist/types/notifications.d.ts +54 -0
  13. package/dist/vite-env.d.ts +1 -1
  14. package/docs/ai-quickstart.md +120 -1
  15. package/docs/cli/create.md +29 -29
  16. package/docs/cli/local-server.md +38 -38
  17. package/docs/cli/login.md +32 -32
  18. package/docs/cli/publish.md +31 -31
  19. package/docs/cli/run.md +26 -26
  20. package/docs/cli/serve.md +43 -43
  21. package/docs/cli/swap.md +30 -30
  22. package/docs/flow-plugin-guide.md +26 -0
  23. package/docs/revit-collab-quickstart.md +26 -17
  24. package/docs/rhino-interop-quickstart.md +74 -7
  25. package/package.json +1 -1
  26. package/resources/AGENTS.md +40 -40
  27. package/src/built-in/CDEManager/example.ts +214 -214
  28. package/src/built-in/CDEManager/examples/FileEditing/example.ts +226 -226
  29. package/src/built-in/CDEManager/examples/FileMoves/example.ts +186 -186
  30. package/src/built-in/CDEManager/examples/FileOperations/example.ts +295 -295
  31. package/src/built-in/CDEManager/examples/FileTransfers/example.ts +210 -210
  32. package/src/built-in/CDEManager/examples/FolderManagement/example.ts +230 -230
  33. package/src/built-in/CDEManager/examples/MetadataSchema/example.ts +230 -230
  34. package/src/built-in/CDEManager/examples/MetadataViews/example.ts +166 -166
  35. package/src/built-in/CDEManager/examples/ProjectConfig/example.ts +177 -177
  36. package/src/built-in/UIManager/src/app/example.ts +97 -97
  37. package/src/built-in/UIManager/src/app/examples/AppState/example.ts +140 -140
  38. package/src/built-in/UIManager/src/app/examples/Banner/example.ts +159 -159
  39. package/src/built-in/UIManager/src/app/examples/Contexts/example.ts +123 -123
  40. package/src/built-in/UIManager/src/app/examples/FileDrop/example.ts +124 -124
  41. package/src/built-in/UIManager/src/app/examples/FloatingLayout/example.ts +90 -90
  42. package/src/built-in/UIManager/src/app/examples/Modal/example.ts +163 -163
  43. package/src/built-in/UIManager/src/app/examples/Notifications/example.ts +133 -133
  44. package/src/built-in/UIManager/src/app/examples/ProjectFileStorage/example.ts +150 -150
  45. package/src/built-in/UIManager/src/app/examples/Sidebar/example.ts +102 -102
  46. package/src/built-in/UIManager/src/clashes-panel/example.ts +183 -183
  47. package/src/built-in/UIManager/src/git-history-panel/example.ts +70 -70
  48. package/src/built-in/UIManager/src/models-list/example.ts +122 -122
  49. package/src/built-in/UIManager/src/topics-panel/example.ts +170 -170
  50. package/src/built-in/index.ts +66 -41
  51. package/src/cli/templates/app/index.html +35 -35
  52. package/src/cli/templates/app/package.json +32 -32
  53. package/src/cli/templates/app/src/bim-components/CloudRunner/index.ts +95 -95
  54. package/src/cli/templates/app/src/bim-components/CloudRunner/src/index.ts +1 -1
  55. package/src/cli/templates/app/src/bim-components/CloudRunner/src/types.ts +5 -5
  56. package/src/cli/templates/app/src/bim-components/index.ts +1 -1
  57. package/src/cli/templates/app/src/globals.ts +1 -1
  58. package/src/cli/templates/app/src/main.history.ts +191 -0
  59. package/src/cli/templates/app/src/ui-components/app-info-section/index.ts +26 -26
  60. package/src/cli/templates/app/src/ui-components/app-info-section/src/index.ts +1 -1
  61. package/src/cli/templates/app/src/ui-components/app-info-section/src/types.ts +15 -15
  62. package/src/cli/templates/app/src/ui-components/cloud-runner-section/index.ts +37 -37
  63. package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/index.ts +1 -1
  64. package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/types.ts +14 -14
  65. package/src/cli/templates/app/src/ui-components/index.ts +2 -2
  66. package/src/cli/templates/app/tsconfig.json +16 -16
  67. package/src/cli/templates/app/vite.config.js +57 -57
  68. package/src/cli/templates/cloud-component/README.md +205 -205
  69. package/src/cli/templates/cloud-component/_thatopen +5 -5
  70. package/src/cli/templates/cloud-component/declarations.json +4 -4
  71. package/src/cli/templates/cloud-component/package.json +21 -21
  72. package/src/cli/templates/cloud-component/src/main.ts +70 -70
  73. package/src/cli/templates/cloud-component/tsconfig.json +16 -16
  74. package/src/cli/templates/cloud-component/vite.config.js +46 -46
  75. package/src/cli/templates/shared/AGENTS.md +8 -8
  76. package/src/cli/templates/shared/CLAUDE.md +1 -1
  77. package/src/cli/templates/shared/_gitignore +5 -5
package/docs/cli/swap.md CHANGED
@@ -1,30 +1,30 @@
1
- ---
2
- description: "Toggle or explicitly set the engine library mode between stable public packages (@thatopen/*) and private beta packages (@thatopen-platform/*-beta). Updates package.json and .thatopen, then runs npm install. Source code never changes — aliases handle resolution."
3
- ---
4
-
5
- ## thatopen swap
6
-
7
- Switches the project between stable and beta engine libraries. Without flags, it toggles from the current state. Updates `package.json` dependencies and sets `beta` in `.thatopen` so `thatopen serve`, `thatopen local-server`, and `vite build` all pick up the change automatically.
8
-
9
- Source code stays unchanged — `@thatopen/components` imports resolve to the correct package via esbuild/vite aliases based on the `beta` flag in `.thatopen`.
10
-
11
- **Usage:**
12
- ```bash
13
- thatopen swap # toggle: stable → beta or beta → stable
14
- thatopen swap --beta # force beta
15
- thatopen swap --stable # force stable
16
- ```
17
-
18
- **Flags:**
19
-
20
- - `--beta` — Switch to beta libraries (`@thatopen-platform/*-beta`).
21
- - `--stable` — Switch back to stable public libraries (`@thatopen/*`).
22
-
23
- **Examples:**
24
- ```bash
25
- thatopen swap
26
- thatopen swap --beta
27
- thatopen swap --stable
28
- ```
29
-
30
- > Beta packages are private and the access must be explicitly granted by That Open Company.
1
+ ---
2
+ description: "Toggle or explicitly set the engine library mode between stable public packages (@thatopen/*) and private beta packages (@thatopen-platform/*-beta). Updates package.json and .thatopen, then runs npm install. Source code never changes — aliases handle resolution."
3
+ ---
4
+
5
+ ## thatopen swap
6
+
7
+ Switches the project between stable and beta engine libraries. Without flags, it toggles from the current state. Updates `package.json` dependencies and sets `beta` in `.thatopen` so `thatopen serve`, `thatopen local-server`, and `vite build` all pick up the change automatically.
8
+
9
+ Source code stays unchanged — `@thatopen/components` imports resolve to the correct package via esbuild/vite aliases based on the `beta` flag in `.thatopen`.
10
+
11
+ **Usage:**
12
+ ```bash
13
+ thatopen swap # toggle: stable → beta or beta → stable
14
+ thatopen swap --beta # force beta
15
+ thatopen swap --stable # force stable
16
+ ```
17
+
18
+ **Flags:**
19
+
20
+ - `--beta` — Switch to beta libraries (`@thatopen-platform/*-beta`).
21
+ - `--stable` — Switch back to stable public libraries (`@thatopen/*`).
22
+
23
+ **Examples:**
24
+ ```bash
25
+ thatopen swap
26
+ thatopen swap --beta
27
+ thatopen swap --stable
28
+ ```
29
+
30
+ > Beta packages are private and the access must be explicitly granted by That Open Company.
@@ -152,6 +152,32 @@ made every proposal declare its units, which looked careful and was a bug waitin
152
152
  an author state a unit while writing coordinates in an axis convention the target does not use, a
153
153
  mistake no declared unit can catch.
154
154
 
155
+ ### Who wrote it
156
+
157
+ `author` is free text for a timeline to print. `userId` is the one that has to be right: it is what
158
+ the platform's own member component resolves into a real name and face. Get it wrong and the
159
+ proposal shows up under somebody else's avatar, which is worse than showing none.
160
+
161
+ **In a browser app, take it from `getProjectData`.** It answers with `currentUser` already resolved
162
+ server-side, and the members it lists are keyed by exactly the id the history panel indexes:
163
+
164
+ ```ts
165
+ const { currentUser } = await client.getProjectData(projectId);
166
+ const userId = currentUser?.user._id; // what the avatar resolves against
167
+ const author = currentUser?.user.fullName; // what the timeline prints
168
+ ```
169
+
170
+ **Do not look for it on the client's context.** `client.context` is the four fields the platform
171
+ injects into the iframe: `appId`, `projectId`, `accessToken`, `apiUrl`. Reading `context.userId` or
172
+ `context.user.name` yields `undefined`, and both failures are silent: the envelope simply ships
173
+ without a `userId`, and the panel falls back to passing `author` as the email, so the member
174
+ component renders whatever it makes of a string that names nobody. A converter did exactly this, and
175
+ it looked fine until somebody noticed the proposal was wearing another person's face.
176
+
177
+ **In a native plug-in there is no such call**, so read the `u` claim out of the API key's JWT payload
178
+ (base64url, no signature check, since this only decides which name to display). That is what the
179
+ Revit add-in does.
180
+
155
181
  ### The verbs
156
182
 
157
183
  One per item, in the attribute `flow:op`:
@@ -46,6 +46,12 @@ the point.
46
46
 
47
47
  ## Step 0 — Ask what the user wants to do
48
48
 
49
+ **Only if they have not already told you.** "I want to start a new collaborative project in Revit
50
+ and here is my file" is (A), said plainly. Reading it back as a four-way menu costs the user a
51
+ whole turn to answer something they just said, and that is the single most common way this guide
52
+ wastes somebody's time. Work out which one it is, say which one you took it to be in one line, and
53
+ carry on. Ask only for what is genuinely missing.
54
+
49
55
  > "What would you like to do?
50
56
  > (A) **Share** a Revit model with my team for the first time.
51
57
  > (B) **Connect** to a shared central a teammate already uploaded.
@@ -164,27 +170,30 @@ Re-run `thatopen revit status` every ten seconds or so until `"loaded": true`.
164
170
 
165
171
  ## Step 3 — Do the action
166
172
 
167
- ### (A) Share a model — `inspect`, then `publish-central`
173
+ ### (A) Share a model — one command
168
174
 
169
175
  ```
170
- thatopen revit inspect --file "<FILE>"
176
+ thatopen revit share --file "<FILE>" --project <PROJECT>
171
177
  ```
172
178
 
173
- - **`"isCentral": true`** → already a central. Tell the user you will upload a copy and their
174
- original is untouched, then:
175
- ```
176
- thatopen revit publish-central --project <PROJECT> --doc <DOC> --file "<FILE>"
177
- ```
178
- - **`"isCentral": false`** → a plain model. **Ask for consent first:** *"This file isn't a shared
179
- central yet. To share it I need to turn it into one. I'll do that on a COPY, so your original
180
- stays exactly as it is. Shall I go ahead?"* Only then:
181
- ```
182
- thatopen revit publish-central --project <PROJECT> --doc <DOC> --file "<FILE>" --convert
183
- ```
184
-
185
- Publishing uploads the central and opens **the user's own local** in Revit. Success ends with
186
- `Published (…). Central: … (version N). Your original file was not modified.` Teammates connect with
187
- the folder id from the dashboard, or with `--project` and `--doc`.
179
+ That is the whole of it. `share` installs the add-in if nothing is listening, starts Revit and
180
+ waits for it, checks whether the file is already a central, publishes it, and opens the user's own
181
+ local. It prints each of the five steps as it reaches them, so a long silence is always a step you
182
+ can name.
183
+
184
+ - **`--doc <name>`** is optional. Left out, it comes from the file name, kebab-cased.
185
+ - **`--project`** takes the dashboard URL as happily as the id.
186
+ - **The original file is never modified.** Everything happens on a copy, which is why `share` does
187
+ not need to ask before converting: there is nothing to lose. Say so rather than asking.
188
+ - **It joins at the end on purpose.** Sharing a model and then carrying on in the original file is
189
+ the one reliable way to end up outside your own team.
190
+
191
+ Steps 1 and 2 (CLI, login, add-in) still have to have happened: `share` installs the add-in but it
192
+ cannot log in for somebody.
193
+
194
+ If you need the pieces separately — a central that already exists, a different convert decision,
195
+ scripting around it — they are still there: `thatopen revit inspect --file`, then
196
+ `thatopen revit publish-central --project --doc --file [--convert]`, then `thatopen revit join`.
188
197
 
189
198
  ### (B) Connect to a central — `join`
190
199
 
@@ -258,8 +258,25 @@ to `row.data(j)`.
258
258
 
259
259
  ### Units, and why the envelope has no units field
260
260
 
261
- A proposal's numbers are in the terms of **what the target published**. Read Revit's export metadata
262
- and it says so itself:
261
+ **Print both files' metadata BEFORE you write a line of the converter.** This is an ordering
262
+ instruction, not advice. The whole conversion rests on what those two files declare, and it takes
263
+ thirty seconds to find out:
264
+
265
+ ```ts
266
+ for (const id of [rhinoFileId, revitFileId]) {
267
+ const res = await client.downloadFile(id);
268
+ const m = FRAGS.EditUtils.getModelFromBuffer(new Uint8Array(await res.arrayBuffer()), false);
269
+ console.log(id, m.metadata());
270
+ }
271
+ ```
272
+
273
+ Measured on a live demo, 2026-08-05: a converter was written the other way round, four hundred lines
274
+ first and the check afterwards, and the target's metadata turned out to be `{}`. Everything above the
275
+ units gate was correct and none of it could run. Read the two files, then write the converter around
276
+ what is actually in them.
277
+
278
+ A proposal's numbers are in the terms of **what the target published**. When Revit's export declares
279
+ anything, it says so itself:
263
280
 
264
281
  ```json
265
282
  "data": { "units": "ft", "axes": "z-up", "note": "Revit's own terms, untouched. Operations are written in these." }
@@ -269,12 +286,21 @@ Rhino publishes its document units in the same place, `model.metadata()`, under
269
286
  sample project that is `Millimeters`. So the converter divides by **304.8**, and it is the converter's
270
287
  job precisely because it is the only piece that knows both sides.
271
288
 
272
- Check the conversion against something real instead of trusting the constant. An existing column in
273
- the Revit export sits at `-30.19028688187479` ft. Times 304.8 that is **-9202 mm**, which is exactly
274
- the bay edge measured in Rhino. That is a conversion proved, not assumed.
289
+ **When the target declares nothing.** Models published by add-in versions before 1.2.27 carry empty
290
+ metadata on their first commit, so `{}` is a state you will meet on any project created before then.
291
+ Revit's internal length unit is always decimal feet, so the factor is not actually in doubt; what is
292
+ missing is the file saying so. Take feet, say out loud that you are taking it, and then **prove it
293
+ from the data** rather than leaving it as an assumption:
294
+
295
+ - Read one existing column's `Position` out of the target, and compare it against the same column
296
+ measured in the source. On the sample project: `-30.19028688187479` ft times 304.8 is **-9202 mm**,
297
+ which is exactly the bay edge measured in Rhino.
298
+ - Keep a sanity check on every converted point: refuse anything landing more than about ten metres
299
+ from an existing element of the same category. That is what catches a factor-of-1000 error, and it
300
+ works whether or not the file declared anything.
275
301
 
276
- **Refuse unknown units.** Guessing a scale factor puts the columns a thousand times too far away, and
277
- nothing about the result says why.
302
+ **What you must never do is guess silently.** A wrong factor puts the columns a thousand times too
303
+ far away, and nothing about the result says why. Refusing is a worse demo and a better outcome.
278
304
 
279
305
  ### Building the payload
280
306
 
@@ -335,6 +361,38 @@ would have built a converter against a layer name nobody was using any more.
335
361
  The tables above are here so nobody has to do that. If you still want to see real bytes, publish
336
362
  something small and read it back inside the app.
337
363
 
364
+ ### What "it builds" does not tell you
365
+
366
+ The dev build does not typecheck. `npm run build` said **111 modules, built clean** on a converter
367
+ whose `tsc --noEmit` was red in four places, and the run that produced this guide read that line as
368
+ proof and moved on. Run the typechecker separately, and read what it says.
369
+
370
+ Its complaint was worth having. A file that does not exist yet is read with a fallback, and the
371
+ fallback's shape becomes the type:
372
+
373
+ ```ts
374
+ const history = await this._readJson(id, { commits: [] }); // commits: never[]
375
+ const head = history.commits.at(-1); // head: never
376
+ ```
377
+
378
+ Every field read off `head` is then an error, which is the typechecker pointing out that the empty
379
+ case was never really thought about. Type the fallback, and decide what an empty history means.
380
+
381
+ Three more that only fail once somebody presses the button:
382
+
383
+ - **The published file name is a parameter, not a constant.** `/publish` takes `name`. A converter
384
+ that hardcodes `rhino-columns.frag` answers "publish from Rhino first" when the file is right
385
+ there under another name. If you cannot find it, list what the folder actually holds and say so.
386
+ - **A loose folder match picks silently.** Falling back from an exact name to "any folder ending in
387
+ this" is fine with one project and wrong the first time there are two, with nothing on screen to
388
+ say which one it chose.
389
+ - **The platform round trip is not exercised by anything above.** The two failures this cost were
390
+ `parentFolderId` and the version tag, and both compiled, read fine, and only spoke on the first
391
+ real click. Press it once yourself, on something small, before showing it to anybody.
392
+
393
+ Not clicking it in front of the user is still the right call. Queuing a proposal writes into a
394
+ shared project, and that is theirs to trigger. Press it in a scratch document instead.
395
+
338
396
  ## 6. Accept it in Revit
339
397
 
340
398
  The Revit add-in's local API, `%APPDATA%\ThatOpen\revit-addin.json`, header `X-RevitFlow-Token`.
@@ -389,3 +447,12 @@ lands in the history, with the new columns in it, viewable in the app like any o
389
447
  - **Version tags are timestamps, not counts.** Numbering by how many entries there are collides on
390
448
  the very first update.
391
449
  - **Stray `.frag` files on the machine are not the current format.** They are an earlier session.
450
+ - **Read the two files' metadata before writing the converter, not after.** Everything the
451
+ conversion rests on is declared in them, or is not, and finding out costs thirty seconds at the
452
+ start and four hundred lines at the end.
453
+ - **Build only what was asked for.** An extra button and an extra layout are minutes each, and they
454
+ come out of the same hour as the thing that has to work.
455
+ - **A clean build is not a typecheck.** Vite does not run `tsc`. "Built clean" and "correct" are two
456
+ different sentences.
457
+ - **Read these guides verbatim.** A fetch that summarises gives you the shape of the thing and none
458
+ of the numbers, and every number here was paid for. Use the raw URL.
package/package.json CHANGED
@@ -15,7 +15,7 @@
15
15
  "access": "public"
16
16
  },
17
17
  "private": false,
18
- "version": "0.7.0",
18
+ "version": "0.9.0",
19
19
  "main": "dist/index.cjs.js",
20
20
  "module": "dist/index.es.js",
21
21
  "types": "dist/index.d.ts",
@@ -1,41 +1,41 @@
1
- # Building on the That Open Platform — Agent Guide
2
-
3
- You are an AI assistant helping a user build a **BIM app** or **cloud component** on the
4
- That Open Platform using **`@thatopen/services`** (the `thatopen` CLI + client library).
5
-
6
- **Read this file first, then immediately load all indexes listed below before doing anything else.**
7
-
8
- > This guide is tool-agnostic: it works with any AI (Claude, Codex, your own). All local docs it
9
- > references ship inside the `@thatopen/services` package under `docs/`.
10
-
11
- ---
12
-
13
- ## Session start — load all indexes in parallel
14
-
15
- Do this before answering any question or writing any code. These are compact description indexes:
16
-
17
- | What | Where |
18
- |---|---|
19
- | Platform built-ins | `node_modules/@thatopen/services/docs/builtin/paths.json` |
20
- | Platform client API | `node_modules/@thatopen/services/docs/client/paths.json` |
21
- | CLI commands | `node_modules/@thatopen/services/docs/cli/paths.json` |
22
- | Engine components (`OBC`, `OBF`) | `https://raw.githubusercontent.com/ThatOpen/engine_components/refs/heads/main/examples/paths.json` |
23
- | Fragments (`FRAGS`) | `https://raw.githubusercontent.com/ThatOpen/engine_fragment/refs/heads/main/examples/paths.json` |
24
- | UI components (`BUI`) | `https://raw.githubusercontent.com/ThatOpen/engine_ui-components/refs/heads/main/examples/paths.json` — **skip** entries whose path contains `packages/obc` or `bim-grid` |
25
-
26
- Once you have these, you know everything available on the platform. Only then fetch a specific example file when you need implementation details.
27
-
28
- > **Path resolution:** all `path` entries inside the local indexes (`docs/`, `src/`) are relative to the `@thatopen/services` package root — prepend `node_modules/@thatopen/services/` when reading any of those files.
29
-
30
- ---
31
-
32
- ## How to work
33
-
34
- - **The scaffold is already a complete, working viewer** (model loading, spatial tree, properties).
35
- **Run it first** (`npm run dev`) to see it work, *then* extend it — don't rebuild a viewer from scratch.
36
- - **Propose a short plan and get the user's OK** before changing files. If scope is unclear, ask.
37
- - If something already exists in the indexes, use it — don't reimplement it.
38
-
39
- ## Hard rules (always apply)
40
-
1
+ # Building on the That Open Platform — Agent Guide
2
+
3
+ You are an AI assistant helping a user build a **BIM app** or **cloud component** on the
4
+ That Open Platform using **`@thatopen/services`** (the `thatopen` CLI + client library).
5
+
6
+ **Read this file first, then immediately load all indexes listed below before doing anything else.**
7
+
8
+ > This guide is tool-agnostic: it works with any AI (Claude, Codex, your own). All local docs it
9
+ > references ship inside the `@thatopen/services` package under `docs/`.
10
+
11
+ ---
12
+
13
+ ## Session start — load all indexes in parallel
14
+
15
+ Do this before answering any question or writing any code. These are compact description indexes:
16
+
17
+ | What | Where |
18
+ |---|---|
19
+ | Platform built-ins | `node_modules/@thatopen/services/docs/builtin/paths.json` |
20
+ | Platform client API | `node_modules/@thatopen/services/docs/client/paths.json` |
21
+ | CLI commands | `node_modules/@thatopen/services/docs/cli/paths.json` |
22
+ | Engine components (`OBC`, `OBF`) | `https://raw.githubusercontent.com/ThatOpen/engine_components/refs/heads/main/examples/paths.json` |
23
+ | Fragments (`FRAGS`) | `https://raw.githubusercontent.com/ThatOpen/engine_fragment/refs/heads/main/examples/paths.json` |
24
+ | UI components (`BUI`) | `https://raw.githubusercontent.com/ThatOpen/engine_ui-components/refs/heads/main/examples/paths.json` — **skip** entries whose path contains `packages/obc` or `bim-grid` |
25
+
26
+ Once you have these, you know everything available on the platform. Only then fetch a specific example file when you need implementation details.
27
+
28
+ > **Path resolution:** all `path` entries inside the local indexes (`docs/`, `src/`) are relative to the `@thatopen/services` package root — prepend `node_modules/@thatopen/services/` when reading any of those files.
29
+
30
+ ---
31
+
32
+ ## How to work
33
+
34
+ - **The scaffold is already a complete, working viewer** (model loading, spatial tree, properties).
35
+ **Run it first** (`npm run dev`) to see it work, *then* extend it — don't rebuild a viewer from scratch.
36
+ - **Do the thing they asked for.** If the request already says what to build, build it. Stop and ask only when you are about to change files you did not create, when the request can be read two ways that mean different work, or when the next step is destructive.
37
+ - If something already exists in the indexes, use it — don't reimplement it.
38
+
39
+ ## Hard rules (always apply)
40
+
41
41
  1. **All UI must be built with Lit**, using the web components from `@thatopen/ui` (`BUI`) — `bim-button`, `bim-panel`, `bim-panel-section`, `bim-toolbar`, `bim-dropdown`, `bim-input`, and the rest of `packages/core`. Always consult the design system before writing any UI: `https://raw.githubusercontent.com/ThatOpen/engine_ui-components/refs/heads/main/DESIGN.md`.