@thatopen/services 0.8.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 (70) hide show
  1. package/README.md +62 -62
  2. package/dist/built-in/index.d.ts +66 -41
  3. package/dist/cli.js +31 -12
  4. package/dist/core/client.d.ts +6 -0
  5. package/dist/core/platform-client.d.ts +139 -0
  6. package/dist/core/platform-client.live.test.d.ts +1 -0
  7. package/dist/core/platform-client.notifications.test.d.ts +1 -0
  8. package/dist/index.cjs.js +2 -2
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.es.js +635 -445
  11. package/dist/types/notifications.d.ts +54 -0
  12. package/dist/vite-env.d.ts +1 -1
  13. package/docs/cli/create.md +29 -29
  14. package/docs/cli/local-server.md +38 -38
  15. package/docs/cli/login.md +32 -32
  16. package/docs/cli/publish.md +31 -31
  17. package/docs/cli/run.md +26 -26
  18. package/docs/cli/serve.md +43 -43
  19. package/docs/cli/swap.md +30 -30
  20. package/package.json +1 -1
  21. package/src/built-in/CDEManager/example.ts +214 -214
  22. package/src/built-in/CDEManager/examples/FileEditing/example.ts +226 -226
  23. package/src/built-in/CDEManager/examples/FileMoves/example.ts +186 -186
  24. package/src/built-in/CDEManager/examples/FileOperations/example.ts +295 -295
  25. package/src/built-in/CDEManager/examples/FileTransfers/example.ts +210 -210
  26. package/src/built-in/CDEManager/examples/FolderManagement/example.ts +230 -230
  27. package/src/built-in/CDEManager/examples/MetadataSchema/example.ts +230 -230
  28. package/src/built-in/CDEManager/examples/MetadataViews/example.ts +166 -166
  29. package/src/built-in/CDEManager/examples/ProjectConfig/example.ts +177 -177
  30. package/src/built-in/UIManager/src/app/example.ts +97 -97
  31. package/src/built-in/UIManager/src/app/examples/AppState/example.ts +140 -140
  32. package/src/built-in/UIManager/src/app/examples/Banner/example.ts +159 -159
  33. package/src/built-in/UIManager/src/app/examples/Contexts/example.ts +123 -123
  34. package/src/built-in/UIManager/src/app/examples/FileDrop/example.ts +124 -124
  35. package/src/built-in/UIManager/src/app/examples/FloatingLayout/example.ts +90 -90
  36. package/src/built-in/UIManager/src/app/examples/Modal/example.ts +163 -163
  37. package/src/built-in/UIManager/src/app/examples/Notifications/example.ts +133 -133
  38. package/src/built-in/UIManager/src/app/examples/ProjectFileStorage/example.ts +150 -150
  39. package/src/built-in/UIManager/src/app/examples/Sidebar/example.ts +102 -102
  40. package/src/built-in/UIManager/src/clashes-panel/example.ts +183 -183
  41. package/src/built-in/UIManager/src/git-history-panel/example.ts +70 -70
  42. package/src/built-in/UIManager/src/models-list/example.ts +122 -122
  43. package/src/built-in/UIManager/src/topics-panel/example.ts +170 -170
  44. package/src/built-in/index.ts +66 -41
  45. package/src/cli/templates/app/index.html +35 -35
  46. package/src/cli/templates/app/package.json +32 -32
  47. package/src/cli/templates/app/src/bim-components/CloudRunner/index.ts +95 -95
  48. package/src/cli/templates/app/src/bim-components/CloudRunner/src/index.ts +1 -1
  49. package/src/cli/templates/app/src/bim-components/CloudRunner/src/types.ts +5 -5
  50. package/src/cli/templates/app/src/bim-components/index.ts +1 -1
  51. package/src/cli/templates/app/src/globals.ts +1 -1
  52. package/src/cli/templates/app/src/ui-components/app-info-section/index.ts +26 -26
  53. package/src/cli/templates/app/src/ui-components/app-info-section/src/index.ts +1 -1
  54. package/src/cli/templates/app/src/ui-components/app-info-section/src/types.ts +15 -15
  55. package/src/cli/templates/app/src/ui-components/cloud-runner-section/index.ts +37 -37
  56. package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/index.ts +1 -1
  57. package/src/cli/templates/app/src/ui-components/cloud-runner-section/src/types.ts +14 -14
  58. package/src/cli/templates/app/src/ui-components/index.ts +2 -2
  59. package/src/cli/templates/app/tsconfig.json +16 -16
  60. package/src/cli/templates/app/vite.config.js +57 -57
  61. package/src/cli/templates/cloud-component/README.md +205 -205
  62. package/src/cli/templates/cloud-component/_thatopen +5 -5
  63. package/src/cli/templates/cloud-component/declarations.json +4 -4
  64. package/src/cli/templates/cloud-component/package.json +21 -21
  65. package/src/cli/templates/cloud-component/src/main.ts +70 -70
  66. package/src/cli/templates/cloud-component/tsconfig.json +16 -16
  67. package/src/cli/templates/cloud-component/vite.config.js +46 -46
  68. package/src/cli/templates/shared/AGENTS.md +8 -8
  69. package/src/cli/templates/shared/CLAUDE.md +1 -1
  70. package/src/cli/templates/shared/_gitignore +5 -5
@@ -1,102 +1,102 @@
1
- /* MD
2
- ## Sidebar navigation
3
- ---
4
- Product teams building BIM apps often need multiple distinct views — a 3D model
5
- viewer, a data browser, a settings screen — but wiring a custom nav bar and
6
- managing view transitions from scratch adds complexity before any real feature
7
- work begins.
8
-
9
- top-app supports named layouts with a built-in sidebar navigation bar that
10
- switches the active grid template on click, so teams get multi-view navigation
11
- without building the scaffolding themselves.
12
-
13
- This tutorial covers defining three named layouts with their own grid templates
14
- and icons; providing a dedicated element for each view; enabling the sidebar nav
15
- bar; and setting the initial active layout.
16
-
17
- By the end, you'll have a multi-view BIM app with a persistent sidebar that lets
18
- users switch between a 3D viewer, a data panel, and a settings screen.
19
- */
20
-
21
- import { html } from "lit";
22
- import * as THREE from "three";
23
- import * as OBC from "@thatopen/components";
24
- import * as OBF from "@thatopen/components-front";
25
- import * as FRAGS from "@thatopen/fragments";
26
- import * as BUI from "@thatopen/ui";
27
- import { PlatformClient, UIManager } from "@thatopen/services";
28
- import type { App } from "../../index";
29
-
30
- const client = PlatformClient.fromPlatformContext();
31
- const { components } = (await client.setup(
32
- { OBC, OBF, BUI, THREE, FRAGS },
33
- { uuid: UIManager.uuid },
34
- )) as { components: OBC.Components };
35
- components.get(UIManager).init();
36
-
37
- const app = document.createElement("top-app") as unknown as App;
38
-
39
- app.setup = (waitUntil) => {
40
- waitUntil(
41
- (async () => {
42
- const fragments = components.get(OBC.FragmentsManager);
43
- const workerUrl = await FRAGS.FragmentsModels.getWorker();
44
- fragments.init(await FRAGS.toClassicWorker(workerUrl), {
45
- classicWorker: true,
46
- });
47
- })(),
48
- "Fragments Core",
49
- );
50
- return { components, client };
51
- };
52
-
53
- app.elements = {
54
- viewer: () =>
55
- html`<top-viewer><top-viewer-tools></top-viewer-tools></top-viewer>`,
56
-
57
- data: () => html`
58
- <bim-panel label="Data">
59
- <bim-panel-section label="Properties" icon="solar:document-bold">
60
- <bim-label>Select an element in the viewer to inspect its properties.</bim-label>
61
- </bim-panel-section>
62
- </bim-panel>
63
- `,
64
-
65
- settings: () => html`
66
- <bim-panel label="Settings">
67
- <bim-panel-section label="Appearance" icon="solar:settings-bold">
68
- <bim-checkbox label="Show grid" checked></bim-checkbox>
69
- <bim-checkbox label="Show axes"></bim-checkbox>
70
- </bim-panel-section>
71
- </bim-panel>
72
- `,
73
- };
74
-
75
- app.layouts = {
76
- // Each layout defines its own CSS grid template. Only areas used in a template
77
- // are rendered for that layout — unused elements are not created.
78
- viewer: {
79
- label: "Viewer",
80
- icon: "solar:3d-square-bold",
81
- template: `"viewer" 1fr / 1fr`,
82
- },
83
- data: {
84
- label: "Data",
85
- icon: "solar:document-bold",
86
- template: `"data" 1fr / 1fr`,
87
- },
88
- settings: {
89
- label: "Settings",
90
- icon: "solar:settings-bold",
91
- template: `"settings" 1fr / 1fr`,
92
- },
93
- };
94
-
95
- // sidebar=true adds a vertical nav column on the left with one button per layout.
96
- // Each button shows the layout's label and icon and activates that layout on click.
97
- app.sidebar = true;
98
- app.layout = "viewer";
99
-
100
- const container = document.getElementById("that-open-app") ?? document.body;
101
- container.appendChild(app);
102
- document.body.style.margin = "0";
1
+ /* MD
2
+ ## Sidebar navigation
3
+ ---
4
+ Product teams building BIM apps often need multiple distinct views — a 3D model
5
+ viewer, a data browser, a settings screen — but wiring a custom nav bar and
6
+ managing view transitions from scratch adds complexity before any real feature
7
+ work begins.
8
+
9
+ top-app supports named layouts with a built-in sidebar navigation bar that
10
+ switches the active grid template on click, so teams get multi-view navigation
11
+ without building the scaffolding themselves.
12
+
13
+ This tutorial covers defining three named layouts with their own grid templates
14
+ and icons; providing a dedicated element for each view; enabling the sidebar nav
15
+ bar; and setting the initial active layout.
16
+
17
+ By the end, you'll have a multi-view BIM app with a persistent sidebar that lets
18
+ users switch between a 3D viewer, a data panel, and a settings screen.
19
+ */
20
+
21
+ import { html } from "lit";
22
+ import * as THREE from "three";
23
+ import * as OBC from "@thatopen/components";
24
+ import * as OBF from "@thatopen/components-front";
25
+ import * as FRAGS from "@thatopen/fragments";
26
+ import * as BUI from "@thatopen/ui";
27
+ import { PlatformClient, UIManager } from "@thatopen/services";
28
+ import type { App } from "../../index";
29
+
30
+ const client = PlatformClient.fromPlatformContext();
31
+ const { components } = (await client.setup(
32
+ { OBC, OBF, BUI, THREE, FRAGS },
33
+ { uuid: UIManager.uuid },
34
+ )) as { components: OBC.Components };
35
+ components.get(UIManager).init();
36
+
37
+ const app = document.createElement("top-app") as unknown as App;
38
+
39
+ app.setup = (waitUntil) => {
40
+ waitUntil(
41
+ (async () => {
42
+ const fragments = components.get(OBC.FragmentsManager);
43
+ const workerUrl = await FRAGS.FragmentsModels.getWorker();
44
+ fragments.init(await FRAGS.toClassicWorker(workerUrl), {
45
+ classicWorker: true,
46
+ });
47
+ })(),
48
+ "Fragments Core",
49
+ );
50
+ return { components, client };
51
+ };
52
+
53
+ app.elements = {
54
+ viewer: () =>
55
+ html`<top-viewer><top-viewer-tools></top-viewer-tools></top-viewer>`,
56
+
57
+ data: () => html`
58
+ <bim-panel label="Data">
59
+ <bim-panel-section label="Properties" icon="solar:document-bold">
60
+ <bim-label>Select an element in the viewer to inspect its properties.</bim-label>
61
+ </bim-panel-section>
62
+ </bim-panel>
63
+ `,
64
+
65
+ settings: () => html`
66
+ <bim-panel label="Settings">
67
+ <bim-panel-section label="Appearance" icon="solar:settings-bold">
68
+ <bim-checkbox label="Show grid" checked></bim-checkbox>
69
+ <bim-checkbox label="Show axes"></bim-checkbox>
70
+ </bim-panel-section>
71
+ </bim-panel>
72
+ `,
73
+ };
74
+
75
+ app.layouts = {
76
+ // Each layout defines its own CSS grid template. Only areas used in a template
77
+ // are rendered for that layout — unused elements are not created.
78
+ viewer: {
79
+ label: "Viewer",
80
+ icon: "solar:3d-square-bold",
81
+ template: `"viewer" 1fr / 1fr`,
82
+ },
83
+ data: {
84
+ label: "Data",
85
+ icon: "solar:document-bold",
86
+ template: `"data" 1fr / 1fr`,
87
+ },
88
+ settings: {
89
+ label: "Settings",
90
+ icon: "solar:settings-bold",
91
+ template: `"settings" 1fr / 1fr`,
92
+ },
93
+ };
94
+
95
+ // sidebar=true adds a vertical nav column on the left with one button per layout.
96
+ // Each button shows the layout's label and icon and activates that layout on click.
97
+ app.sidebar = true;
98
+ app.layout = "viewer";
99
+
100
+ const container = document.getElementById("that-open-app") ?? document.body;
101
+ container.appendChild(app);
102
+ document.body.style.margin = "0";
@@ -1,183 +1,183 @@
1
- /* MD
2
- ## clashes-panel
3
- ---
4
- BIM coordinators need a dedicated review environment where they can see all
5
- detected clashes, filter by status, navigate to each issue in the 3D viewer,
6
- and mark items as resolved — without writing any UI code themselves.
7
-
8
- `clashes-panel` is a ready-made web component that delivers exactly that. It
9
- connects to `ClashesManager` automatically and manages the entire review
10
- workflow: the detection matrix, the clash table with status filters, sphere
11
- markers and element highlights in the viewer, run controls with live progress,
12
- a loading skeleton while fresh data reloads after a run completes, and
13
- turning a cluster of clashes into a tracked BCF topic in one click.
14
-
15
- This tutorial covers the prerequisites before mounting the panel; dropping it
16
- into a `top-app` layout alongside a 3D viewer; and a detailed breakdown of
17
- everything the panel handles automatically so you know what you do not need to
18
- implement yourself.
19
-
20
- By the end, you'll have a fully working clash-review environment running in
21
- your application with a single line of markup.
22
- */
23
-
24
- import { html } from "lit";
25
- import * as THREE from "three";
26
- import * as OBC from "@thatopen/components";
27
- import * as OBF from "@thatopen/components-front";
28
- import * as FRAGS from "@thatopen/fragments";
29
- import * as BUI from "@thatopen/ui";
30
- import { PlatformClient, UIManager, ClashesManager, TopicsManager } from "@thatopen/services";
31
- import type { App } from "../app/index";
32
-
33
-
34
- /* MD
35
- ### ✅ Prerequisites
36
-
37
- Three conditions must be met before the panel mounts:
38
-
39
- 1. **`UIManager` must be in the setup call.** It registers all platform web
40
- components, including `<top-clashes-panel>` itself. Without it the element is
41
- unknown to the browser and renders as an empty box.
42
-
43
- 2. **`ClashesManager.init(client)` must have resolved.** The panel reads all
44
- its initial data from the manager in `connectedCallback`. If `init` has not
45
- finished, the panel mounts into an empty state and never catches up because
46
- it only subscribes to change events — it does not poll.
47
-
48
- 3. **`TopicsManager.init(client)` must have resolved too.** The panel's
49
- "Create topic" button (turns a selection of clashes into a tracked BCF
50
- topic) needs `Viewpoints.world` set to capture the camera, and `BCFTopics`
51
- configured with `version: "3"` for the clash↔topic link to survive a
52
- reload — `TopicsManager.init` is what sets both up. Skip it and the button
53
- still works, but the topic it creates is subtly broken.
54
-
55
- All three are naturally satisfied when you call both `init`s before appending
56
- `top-app` to the document, which is the pattern shown below.
57
- */
58
-
59
- const client = PlatformClient.fromPlatformContext();
60
-
61
- const { components } = (await client.setup(
62
- { OBC, OBF, BUI, THREE, FRAGS },
63
- { uuid: UIManager.uuid },
64
- { uuid: ClashesManager.uuid },
65
- { uuid: TopicsManager.uuid },
66
- )) as { components: OBC.Components };
67
-
68
- components.get(UIManager).init();
69
-
70
- // Both inits must resolve before top-app (and therefore clashes-panel) mounts.
71
- // Independent of each other — safe to run in parallel.
72
- await Promise.all([
73
- components.get(ClashesManager).init(client),
74
- components.get(TopicsManager).init(client),
75
- ]);
76
-
77
- /* MD
78
- ### 🖥️ Wiring the panel
79
-
80
- Add `clashes-panel` as a named area in `app.elements` and reference it in your
81
- layout template. A side-by-side layout with the 3D viewer is the most natural
82
- arrangement — the panel controls markers and highlights directly in the scene.
83
- */
84
-
85
- const app = document.createElement("top-app") as unknown as App;
86
-
87
- app.setup = (waitUntil) => {
88
- waitUntil(
89
- (async () => {
90
- const fragments = components.get(OBC.FragmentsManager);
91
- const workerUrl = await FRAGS.FragmentsModels.getWorker();
92
- fragments.init(await FRAGS.toClassicWorker(workerUrl), { classicWorker: true });
93
- })(),
94
- "Fragments Core",
95
- );
96
-
97
- waitUntil(
98
- (async () => {
99
- const loader = components.get(OBC.IfcLoader);
100
- await loader.setup();
101
- })(),
102
- "IFC Loader",
103
- );
104
-
105
- return { components, client };
106
- };
107
-
108
- app.elements = {
109
- viewer: () => html`<top-viewer><top-viewer-tools></top-viewer-tools></top-viewer>`,
110
- clashes: () => html`<top-clashes-panel></top-clashes-panel>`,
111
- };
112
-
113
- app.layouts = {
114
- main: {
115
- label: "Main",
116
- icon: "solar:3d-square-bold",
117
- template: `"viewer clashes" 1fr / 1fr 22rem`,
118
- },
119
- };
120
-
121
- app.layout = "main";
122
-
123
- const container = document.getElementById("that-open-app") ?? document.body;
124
- container.appendChild(app);
125
- document.body.style.margin = "0";
126
-
127
- /* MD
128
- ### 🤖 What the panel manages automatically
129
-
130
- Once mounted, `clashes-panel` takes ownership of the following without any
131
- further code from you:
132
-
133
- **Data synchronization** — the panel subscribes to every `ClashesManager`
134
- event (`onJobsChanged`, `onRunsChanged`, `onClashesChanged`,
135
- `onClashStatusChanged`, `onQueriesChanged`, `onRunningJobsChanged`) and keeps
136
- its tables in sync. Changes made programmatically through the manager API are
137
- reflected in the panel immediately.
138
-
139
- **Run lifecycle** — a progress callout appears as soon as a run starts
140
- (`onRunProgress`), updates its percentage and message in real time, and
141
- disappears when the run completes or errors. If a run was already in progress
142
- when the page loaded, the panel calls `reconnectIfRunning` automatically when
143
- the user opens that job.
144
-
145
- **Post-run reload skeleton** — after a successful run the manager reloads clash
146
- data from storage. During that window (`onReloadingChanged = true`) the panel
147
- replaces its entire content with an animated skeleton so the user is never left
148
- looking at stale results that appear final.
149
-
150
- **Sphere markers** — entering a job view places colored spheres in the 3D scene
151
- at each clash location, transformed from IFC world space to viewer space.
152
- Selecting a clash in the table zooms the camera, ghosts unrelated elements, and
153
- re-focuses the markers on the selection. Leaving the job view removes all
154
- markers and restores the full scene.
155
-
156
- **Element highlights** — element A is highlighted green and element B red for
157
- every new or active clash in the current job. Selecting a single clash refines
158
- the highlight to that pair only. Clearing the selection or changing status
159
- restores the full job highlight.
160
-
161
- **Model awareness** — the panel tracks which fragment models are currently
162
- loaded in the scene. Markers and highlights are activated only when the relevant
163
- models are present, and are cleaned up automatically if a model is disposed
164
- while the panel is open.
165
-
166
- **Create topic from clash** — selecting one or more clashes and clicking
167
- "Create topic" builds a BCF topic with an auto-generated title/description
168
- summarizing the pair(s) and a viewpoint capturing the same selection and
169
- red/green coloring shown in the table. The link back to the source clash(es)
170
- is recorded separately so it survives independently of the topic's own BCF
171
- fields. The button is disabled while the camera is still flying to a
172
- selection or no models are loaded yet, so it can't fire on an empty scene.
173
-
174
- **Save indicator** — every persisted change (clashes, queries, matrices,
175
- topics created from a selection) routes through `top-app`'s shared
176
- `projectStorageContext`, the same one `topics-panel` uses. A small indicator
177
- reflects the real debounce-then-upload cycle: just an icon while a save is
178
- pending, the full "Saving..." pill once the upload is actually in flight.
179
-
180
- **Disconnection cleanup** — when the panel is removed from the DOM it calls
181
- `hideMarkers` and `clearClashHighlight` on the manager, leaving the 3D scene
182
- clean regardless of what was selected at the time.
183
- */
1
+ /* MD
2
+ ## clashes-panel
3
+ ---
4
+ BIM coordinators need a dedicated review environment where they can see all
5
+ detected clashes, filter by status, navigate to each issue in the 3D viewer,
6
+ and mark items as resolved — without writing any UI code themselves.
7
+
8
+ `clashes-panel` is a ready-made web component that delivers exactly that. It
9
+ connects to `ClashesManager` automatically and manages the entire review
10
+ workflow: the detection matrix, the clash table with status filters, sphere
11
+ markers and element highlights in the viewer, run controls with live progress,
12
+ a loading skeleton while fresh data reloads after a run completes, and
13
+ turning a cluster of clashes into a tracked BCF topic in one click.
14
+
15
+ This tutorial covers the prerequisites before mounting the panel; dropping it
16
+ into a `top-app` layout alongside a 3D viewer; and a detailed breakdown of
17
+ everything the panel handles automatically so you know what you do not need to
18
+ implement yourself.
19
+
20
+ By the end, you'll have a fully working clash-review environment running in
21
+ your application with a single line of markup.
22
+ */
23
+
24
+ import { html } from "lit";
25
+ import * as THREE from "three";
26
+ import * as OBC from "@thatopen/components";
27
+ import * as OBF from "@thatopen/components-front";
28
+ import * as FRAGS from "@thatopen/fragments";
29
+ import * as BUI from "@thatopen/ui";
30
+ import { PlatformClient, UIManager, ClashesManager, TopicsManager } from "@thatopen/services";
31
+ import type { App } from "../app/index";
32
+
33
+
34
+ /* MD
35
+ ### ✅ Prerequisites
36
+
37
+ Three conditions must be met before the panel mounts:
38
+
39
+ 1. **`UIManager` must be in the setup call.** It registers all platform web
40
+ components, including `<top-clashes-panel>` itself. Without it the element is
41
+ unknown to the browser and renders as an empty box.
42
+
43
+ 2. **`ClashesManager.init(client)` must have resolved.** The panel reads all
44
+ its initial data from the manager in `connectedCallback`. If `init` has not
45
+ finished, the panel mounts into an empty state and never catches up because
46
+ it only subscribes to change events — it does not poll.
47
+
48
+ 3. **`TopicsManager.init(client)` must have resolved too.** The panel's
49
+ "Create topic" button (turns a selection of clashes into a tracked BCF
50
+ topic) needs `Viewpoints.world` set to capture the camera, and `BCFTopics`
51
+ configured with `version: "3"` for the clash↔topic link to survive a
52
+ reload — `TopicsManager.init` is what sets both up. Skip it and the button
53
+ still works, but the topic it creates is subtly broken.
54
+
55
+ All three are naturally satisfied when you call both `init`s before appending
56
+ `top-app` to the document, which is the pattern shown below.
57
+ */
58
+
59
+ const client = PlatformClient.fromPlatformContext();
60
+
61
+ const { components } = (await client.setup(
62
+ { OBC, OBF, BUI, THREE, FRAGS },
63
+ { uuid: UIManager.uuid },
64
+ { uuid: ClashesManager.uuid },
65
+ { uuid: TopicsManager.uuid },
66
+ )) as { components: OBC.Components };
67
+
68
+ components.get(UIManager).init();
69
+
70
+ // Both inits must resolve before top-app (and therefore clashes-panel) mounts.
71
+ // Independent of each other — safe to run in parallel.
72
+ await Promise.all([
73
+ components.get(ClashesManager).init(client),
74
+ components.get(TopicsManager).init(client),
75
+ ]);
76
+
77
+ /* MD
78
+ ### 🖥️ Wiring the panel
79
+
80
+ Add `clashes-panel` as a named area in `app.elements` and reference it in your
81
+ layout template. A side-by-side layout with the 3D viewer is the most natural
82
+ arrangement — the panel controls markers and highlights directly in the scene.
83
+ */
84
+
85
+ const app = document.createElement("top-app") as unknown as App;
86
+
87
+ app.setup = (waitUntil) => {
88
+ waitUntil(
89
+ (async () => {
90
+ const fragments = components.get(OBC.FragmentsManager);
91
+ const workerUrl = await FRAGS.FragmentsModels.getWorker();
92
+ fragments.init(await FRAGS.toClassicWorker(workerUrl), { classicWorker: true });
93
+ })(),
94
+ "Fragments Core",
95
+ );
96
+
97
+ waitUntil(
98
+ (async () => {
99
+ const loader = components.get(OBC.IfcLoader);
100
+ await loader.setup();
101
+ })(),
102
+ "IFC Loader",
103
+ );
104
+
105
+ return { components, client };
106
+ };
107
+
108
+ app.elements = {
109
+ viewer: () => html`<top-viewer><top-viewer-tools></top-viewer-tools></top-viewer>`,
110
+ clashes: () => html`<top-clashes-panel></top-clashes-panel>`,
111
+ };
112
+
113
+ app.layouts = {
114
+ main: {
115
+ label: "Main",
116
+ icon: "solar:3d-square-bold",
117
+ template: `"viewer clashes" 1fr / 1fr 22rem`,
118
+ },
119
+ };
120
+
121
+ app.layout = "main";
122
+
123
+ const container = document.getElementById("that-open-app") ?? document.body;
124
+ container.appendChild(app);
125
+ document.body.style.margin = "0";
126
+
127
+ /* MD
128
+ ### 🤖 What the panel manages automatically
129
+
130
+ Once mounted, `clashes-panel` takes ownership of the following without any
131
+ further code from you:
132
+
133
+ **Data synchronization** — the panel subscribes to every `ClashesManager`
134
+ event (`onJobsChanged`, `onRunsChanged`, `onClashesChanged`,
135
+ `onClashStatusChanged`, `onQueriesChanged`, `onRunningJobsChanged`) and keeps
136
+ its tables in sync. Changes made programmatically through the manager API are
137
+ reflected in the panel immediately.
138
+
139
+ **Run lifecycle** — a progress callout appears as soon as a run starts
140
+ (`onRunProgress`), updates its percentage and message in real time, and
141
+ disappears when the run completes or errors. If a run was already in progress
142
+ when the page loaded, the panel calls `reconnectIfRunning` automatically when
143
+ the user opens that job.
144
+
145
+ **Post-run reload skeleton** — after a successful run the manager reloads clash
146
+ data from storage. During that window (`onReloadingChanged = true`) the panel
147
+ replaces its entire content with an animated skeleton so the user is never left
148
+ looking at stale results that appear final.
149
+
150
+ **Sphere markers** — entering a job view places colored spheres in the 3D scene
151
+ at each clash location, transformed from IFC world space to viewer space.
152
+ Selecting a clash in the table zooms the camera, ghosts unrelated elements, and
153
+ re-focuses the markers on the selection. Leaving the job view removes all
154
+ markers and restores the full scene.
155
+
156
+ **Element highlights** — element A is highlighted green and element B red for
157
+ every new or active clash in the current job. Selecting a single clash refines
158
+ the highlight to that pair only. Clearing the selection or changing status
159
+ restores the full job highlight.
160
+
161
+ **Model awareness** — the panel tracks which fragment models are currently
162
+ loaded in the scene. Markers and highlights are activated only when the relevant
163
+ models are present, and are cleaned up automatically if a model is disposed
164
+ while the panel is open.
165
+
166
+ **Create topic from clash** — selecting one or more clashes and clicking
167
+ "Create topic" builds a BCF topic with an auto-generated title/description
168
+ summarizing the pair(s) and a viewpoint capturing the same selection and
169
+ red/green coloring shown in the table. The link back to the source clash(es)
170
+ is recorded separately so it survives independently of the topic's own BCF
171
+ fields. The button is disabled while the camera is still flying to a
172
+ selection or no models are loaded yet, so it can't fire on an empty scene.
173
+
174
+ **Save indicator** — every persisted change (clashes, queries, matrices,
175
+ topics created from a selection) routes through `top-app`'s shared
176
+ `projectStorageContext`, the same one `topics-panel` uses. A small indicator
177
+ reflects the real debounce-then-upload cycle: just an icon while a save is
178
+ pending, the full "Saving..." pill once the upload is actually in flight.
179
+
180
+ **Disconnection cleanup** — when the panel is removed from the DOM it calls
181
+ `hideMarkers` and `clearClashHighlight` on the manager, leaving the 3D scene
182
+ clean regardless of what was selected at the time.
183
+ */