@thatopen/services 0.0.1 → 0.0.2

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 (59) hide show
  1. package/AGENTS.md +76 -0
  2. package/CONTEXT.md +4 -4
  3. package/README.md +4 -4
  4. package/dist/cli.js +7 -5
  5. package/dist/core/client.d.ts +1 -1
  6. package/docs/access-backend-data.md +19 -0
  7. package/docs/app-architecture.md +94 -0
  8. package/docs/app-layout.md +139 -0
  9. package/docs/app-wiring.md +123 -0
  10. package/docs/bim-components/coding-conventions.md +128 -0
  11. package/docs/bim-components/element-collections.md +69 -0
  12. package/docs/bim-components/expose-events.md +84 -0
  13. package/docs/bim-components/library-examples.md +28 -0
  14. package/docs/bim-components/observable-collections.md +83 -0
  15. package/docs/bim-components/overview.md +160 -0
  16. package/docs/bim-components/per-frame-updates.md +20 -0
  17. package/docs/bim-components/save-and-restore-state.md +21 -0
  18. package/docs/bim-components/setup-and-cleanup.md +64 -0
  19. package/docs/bim-components/type-conventions.md +49 -0
  20. package/docs/bim-components/user-driven-object-creation.md +56 -0
  21. package/docs/cli-setup.md +54 -0
  22. package/docs/cloud-components.md +74 -0
  23. package/docs/connect-logic-to-ui.md +113 -0
  24. package/docs/previewing.md +45 -0
  25. package/docs/publishing.md +35 -0
  26. package/docs/scaffolding.md +54 -0
  27. package/docs/ui-components/async-actions.md +18 -0
  28. package/docs/ui-components/confirmation-dialog.md +39 -0
  29. package/docs/ui-components/data-table.md +185 -0
  30. package/docs/ui-components/display-text.md +39 -0
  31. package/docs/ui-components/inline-form.md +44 -0
  32. package/docs/ui-components/library-examples.md +24 -0
  33. package/docs/ui-components/overview.md +194 -0
  34. package/docs/ui-components/rendering-patterns.md +129 -0
  35. package/docs/ui-components/sections-layout.md +123 -0
  36. package/docs/update-grid-elements.md +46 -0
  37. package/docs/using-colors.md +32 -0
  38. package/docs/using-icons.md +37 -0
  39. package/package.json +3 -1
  40. package/src/cli/templates/bim/CONTEXT.md +3 -3
  41. package/src/cli/templates/bim/package.json +1 -1
  42. package/src/cli/templates/bim/src/app.ts +1 -1
  43. package/src/cli/templates/bim/src/bim-components/CloudRunner/index.ts +1 -1
  44. package/src/cli/templates/bim/src/main.ts +1 -1
  45. package/src/cli/templates/bim/src/setups/ui-manager.ts +1 -1
  46. package/src/cli/templates/bim/src/setups/viewports-manager.ts +1 -1
  47. package/src/cli/templates/cloud/CONTEXT.md +4 -4
  48. package/src/cli/templates/cloud/package.json +1 -1
  49. package/src/cli/templates/cloud/src/main.ts +1 -1
  50. package/src/cli/templates/cloud-test/package.json +1 -1
  51. package/src/cli/templates/cloud-test/src/main.ts +1 -1
  52. package/src/cli/templates/default/CONTEXT.md +4 -4
  53. package/src/cli/templates/default/package.json +3 -0
  54. package/src/cli/templates/default/src/main.ts +3 -3
  55. package/src/cli/templates/shared/AGENTS.md +23 -0
  56. package/src/cli/templates/shared/CLAUDE.md +1 -0
  57. package/src/cli/templates/shared/cloud/vite.config.js +3 -3
  58. package/src/cli/templates/test/package.json +1 -1
  59. package/src/cli/templates/test/src/main.ts +2 -2
package/AGENTS.md ADDED
@@ -0,0 +1,76 @@
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.** It states the rules you must follow, then routes you to the one
7
+ detailed doc for whatever you're doing. Open only the doc you need — don't load everything.
8
+
9
+ > This guide is tool-agnostic: it works with any AI (Claude, Codex, your own). All docs it
10
+ > references ship inside the `@thatopen/services` package under `docs/`.
11
+
12
+ ---
13
+
14
+ ## How to work
15
+
16
+ - **New app or component?** The **first step is always the CLI** (`thatopen create`) — never
17
+ hand-write the scaffold. See [docs/cli-setup.md](./docs/cli-setup.md) then
18
+ [docs/scaffolding.md](./docs/scaffolding.md).
19
+ - **Propose a short plan and get the user's OK** before changing files. If scope is unclear, ask.
20
+ - Prefer existing engine functionality over custom code (see rule 2).
21
+
22
+ ## Hard rules (always apply)
23
+
24
+ 1. **All business logic lives in a BIM component** (`src/bim-components/`).
25
+ `setups/` only wire; `ui-components/` only render; `main.ts` only boots. Logic that doesn't
26
+ fit an existing component → create a new one. Never put logic in a setup, template, or `main.ts`.
27
+ 2. **Check engine components first** — `@thatopen/components` (`OBC`) and
28
+ `@thatopen/components-front` (`OBF`) — before building anything custom.
29
+ 3. **Platform built-ins** (`AppManager`, `ViewportsManager`, `UIManager`, …) come from
30
+ `@thatopen/services` and are available after `client.setup()`. Don't reinvent them.
31
+
32
+ ---
33
+
34
+ ## What are you doing? → open the right doc
35
+
36
+ ### Set up & ship
37
+ | Goal | Doc |
38
+ |---|---|
39
+ | Install the CLI + authenticate | [docs/cli-setup.md](./docs/cli-setup.md) |
40
+ | Scaffold a new app / component | [docs/scaffolding.md](./docs/scaffolding.md) |
41
+ | Preview the app inside the platform | [docs/previewing.md](./docs/previewing.md) |
42
+ | Publish an app or component | [docs/publishing.md](./docs/publishing.md) |
43
+
44
+ ### Structure & wire an app
45
+ | Goal | Doc |
46
+ |---|---|
47
+ | Project structure, architecture rules, component tiers | [docs/app-architecture.md](./docs/app-architecture.md) |
48
+ | Boot / `app.ts` / `main.ts` / `client.setup()` | [docs/app-wiring.md](./docs/app-wiring.md) |
49
+ | Configure layout, add/reorganize grid sections | [docs/app-layout.md](./docs/app-layout.md) |
50
+ | Connect component logic to the UI | [docs/connect-logic-to-ui.md](./docs/connect-logic-to-ui.md) |
51
+ | Update a grid element's state at runtime | [docs/update-grid-elements.md](./docs/update-grid-elements.md) |
52
+ | Access the backend client / project data | [docs/access-backend-data.md](./docs/access-backend-data.md) |
53
+ | Register and use icons | [docs/using-icons.md](./docs/using-icons.md) |
54
+ | Declare and use colors | [docs/using-colors.md](./docs/using-colors.md) |
55
+
56
+ ### Build a custom BIM component
57
+ Start at [docs/bim-components/overview.md](./docs/bim-components/overview.md) — conventions,
58
+ lifecycle (setup/cleanup), exposing events, observable/element collections, per-frame updates,
59
+ saving/restoring state, user-driven object creation.
60
+
61
+ ### Build a UI component
62
+ Start at [docs/ui-components/overview.md](./docs/ui-components/overview.md) — rendering patterns,
63
+ section layout, data tables, inline forms, confirmation dialogs, display text, async actions.
64
+
65
+ ### Cloud components & automations
66
+ [docs/cloud-components.md](./docs/cloud-components.md) — build / run locally / publish a cloud
67
+ component, the execution globals, and event-triggered automations.
68
+
69
+ ---
70
+
71
+ ## Reference (also shipped in this package)
72
+
73
+ - **Library API** — `PlatformClient` vs `EngineServicesClient`, the permissions contract, and the
74
+ full method surface → [CONTEXT.md](./CONTEXT.md)
75
+ - **Built-in components API** — config interfaces, method signatures, `@example` blocks →
76
+ `src/built-in/index.ts` (in the installed package, `node_modules/@thatopen/services/`)
package/CONTEXT.md CHANGED
@@ -1,4 +1,4 @@
1
- # thatopen-services
1
+ # @thatopen/services
2
2
 
3
3
  Client library and CLI for the That Open Platform — a cloud platform for building BIM (Building Information Modeling) software.
4
4
 
@@ -81,7 +81,7 @@ every request, so Auth0's `getAccessTokenSilently()` and similar
81
81
  refreshing sources Just Work:
82
82
 
83
83
  ```ts
84
- import { PlatformClient } from 'thatopen-services';
84
+ import { PlatformClient } from '@thatopen/services';
85
85
  const client = new PlatformClient(
86
86
  () => auth0.getAccessTokenSilently(),
87
87
  'https://api.thatopen.com',
@@ -142,7 +142,7 @@ yarn create-version # Build → changeset → version → publish
142
142
  | **Item type** | `APP` | `TOOL` |
143
143
  | **Entry point** | Side effects in `main.ts` (renders UI) | `export async function main()` |
144
144
  | **Context** | `window.__THATOPEN_CONTEXT__` provides `{ appId, projectId, accessToken, apiUrl }` | Globals: `thatOpenServices`, `executionParams`, `executionContext` (`{ projectId?, executionId, toolId, toolVersion }`), `executionReporter` (`message/error/progress`). `OBC`, `THREE`, `web-ifc`, `fs` are NOT injected — import them and let the bundler include them. |
145
- | **Build output** | IIFE `dist/bundle.js` (all deps bundled) | IIFE `dist/bundle.js` (only `thatopen-services` externalized) |
145
+ | **Build output** | IIFE `dist/bundle.js` (all deps bundled) | IIFE `dist/bundle.js` (only `@thatopen/services` externalized) |
146
146
  | **Template** | `bim`, `default`, or `test` | `cloud` or `cloud-test` |
147
147
 
148
148
  ### Authentication
@@ -156,7 +156,7 @@ Two modes, controlled by `useBearer` in the constructor:
156
156
  Built-in components are platform-hosted UI modules fetched at runtime. Usage pattern:
157
157
 
158
158
  ```ts
159
- import { AppManager, ViewportManager } from "thatopen-services";
159
+ import { AppManager, ViewportManager } from "@thatopen/services";
160
160
 
161
161
  // Register all library globals once
162
162
  client.setBuiltInGlobals({ OBC, OBF, BUI, CUI, THREE, FRAGS });
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # thatopen-services
1
+ # @thatopen/services
2
2
 
3
3
  Client library and CLI for building BIM apps and cloud components on the [That Open Platform](https://platform.thatopen.com).
4
4
 
@@ -8,7 +8,7 @@ Client library and CLI for building BIM apps and cloud components on the [That O
8
8
 
9
9
  ```bash
10
10
  # Install the services package globally
11
- npm i thatopen-services -g
11
+ npm i @thatopen/services -g
12
12
  ```
13
13
 
14
14
  Then, create a brand new app repository:
@@ -58,7 +58,7 @@ Use `npx thatopen create .` to scaffold in the current directory instead of crea
58
58
  ## Library usage
59
59
 
60
60
  ```typescript
61
- import { EngineServicesClient } from 'thatopen-services';
61
+ import { EngineServicesClient } from '@thatopen/services';
62
62
 
63
63
  const client = new EngineServicesClient(accessToken, apiUrl);
64
64
 
@@ -152,7 +152,7 @@ Cloud components export an `async function main()` that runs on the server. The
152
152
  Platform-hosted UI components loaded at runtime:
153
153
 
154
154
  ```typescript
155
- import { AppManager, ViewportManager } from "thatopen-services";
155
+ import { AppManager, ViewportManager } from "@thatopen/services";
156
156
 
157
157
  // Register all library globals once
158
158
  client.setBuiltInGlobals({ OBC, OBF, BUI, CUI, THREE, FRAGS });
package/dist/cli.js CHANGED
@@ -8013,6 +8013,8 @@ const createCommand = new Command2("create").argument("<project-name>", 'Name of
8013
8013
  const sharedDir = require$$2.join(templatesDir, "shared");
8014
8014
  const sharedVariantDir = require$$2.join(sharedDir, isCloud ? "cloud" : "app");
8015
8015
  require$$3.copyFileSync(require$$2.join(sharedDir, "_gitignore"), require$$2.join(targetDir, ".gitignore"));
8016
+ require$$3.copyFileSync(require$$2.join(sharedDir, "AGENTS.md"), require$$2.join(targetDir, "AGENTS.md"));
8017
+ require$$3.copyFileSync(require$$2.join(sharedDir, "CLAUDE.md"), require$$2.join(targetDir, "CLAUDE.md"));
8016
8018
  require$$3.copyFileSync(require$$2.join(sharedVariantDir, "tsconfig.json"), require$$2.join(targetDir, "tsconfig.json"));
8017
8019
  require$$3.copyFileSync(require$$2.join(sharedVariantDir, "vite.config.js"), require$$2.join(targetDir, "vite.config.js"));
8018
8020
  if (!isCloud) {
@@ -8024,7 +8026,7 @@ const createCommand = new Command2("create").argument("<project-name>", 'Name of
8024
8026
  require$$3.renameSync(require$$2.join(targetDir, "_thatopen"), require$$2.join(targetDir, ".thatopen"));
8025
8027
  }
8026
8028
  const pkgPath = require$$2.join(targetDir, "package.json");
8027
- const pkg2 = require$$3.readFileSync(pkgPath, "utf-8").replace(/\{\{PROJECT_NAME\}\}/g, packageName).replace(/\{\{VERSION\}\}/g, libVersion).replace(/"thatopen-services": "file:[^"]*"/, `"thatopen-services": "^${libVersion}"`);
8029
+ const pkg2 = require$$3.readFileSync(pkgPath, "utf-8").replace(/\{\{PROJECT_NAME\}\}/g, packageName).replace(/\{\{VERSION\}\}/g, libVersion).replace(/"@thatopen\/services": "file:[^"]*"/, `"@thatopen/services": "^${libVersion}"`);
8028
8030
  require$$3.writeFileSync(pkgPath, pkg2);
8029
8031
  console.log("");
8030
8032
  console.log("Installing dependencies...");
@@ -10854,7 +10856,7 @@ function detectGlobalName(cwd) {
10854
10856
  }
10855
10857
  function buildEngineScript(bundleCode, accessToken, apiUrl, executionParams) {
10856
10858
  return `/* eslint-disable */
10857
- const { EngineServicesClient } = require('thatopen-services');
10859
+ const { EngineServicesClient } = require('@thatopen/services');
10858
10860
 
10859
10861
  const OBC = require('@thatopen/components');
10860
10862
  const THREE = require('three');
@@ -11280,7 +11282,7 @@ const localServerCommand = new Command2("local-server").description(
11280
11282
  ".webp": "dataurl"
11281
11283
  },
11282
11284
  external: [
11283
- "thatopen-services",
11285
+ "@thatopen/services",
11284
11286
  "@thatopen/components",
11285
11287
  "three",
11286
11288
  "web-ifc",
@@ -11539,13 +11541,13 @@ const pkg = JSON.parse(
11539
11541
  require$$3.readFileSync(require$$2.join(__dirname, "..", "package.json"), "utf-8")
11540
11542
  );
11541
11543
  let updateMessage;
11542
- fetch("https://registry.npmjs.org/thatopen-services/latest", {
11544
+ fetch("https://registry.npmjs.org/@thatopen/services/latest", {
11543
11545
  signal: AbortSignal.timeout(3e3)
11544
11546
  }).then((res) => res.ok && res.json()).then((data) => {
11545
11547
  if (data?.version && data.version !== pkg.version) {
11546
11548
  updateMessage = `
11547
11549
  ⚠ Update available: ${pkg.version} → ${data.version}
11548
- Run "npm install -g thatopen-services@latest" to update.
11550
+ Run "npm install -g @thatopen/services@latest" to update.
11549
11551
  `;
11550
11552
  }
11551
11553
  }).catch(() => {
@@ -113,7 +113,7 @@ export type EngineServicesClientProps = {
113
113
  *
114
114
  * @example
115
115
  * ```ts
116
- * import { EngineServicesClient } from 'thatopen-services';
116
+ * import { EngineServicesClient } from '@thatopen/services';
117
117
  *
118
118
  * const client = new EngineServicesClient('my-access-token', 'https://api.thatopen.com');
119
119
  * const files = await client.listFiles();
@@ -0,0 +1,19 @@
1
+ # Access Backend Data
2
+
3
+ ## Accessing backend data via `AppManager`
4
+
5
+ After `init()`, `AppManager` exposes:
6
+
7
+ - **`app.client`** — `EngineServicesClient`: backend client for API calls
8
+ - **`app.projectData`** — `ProjectData`: current user info, role, project metadata
9
+
10
+ Both are resolved before any setup runs. Always access them through `getAppManager(components)`:
11
+
12
+ ```ts
13
+ const app = getAppManager(components)
14
+ if (app.projectData.currentUser?.role.name === "Project Admin") {
15
+ // render admin-only controls
16
+ }
17
+
18
+ const files = await app.client.getProjectFiles(app.projectData.id)
19
+ ```
@@ -0,0 +1,94 @@
1
+ # App Architecture and Composition
2
+
3
+ ## Architecture rule: logic lives only in BIM components
4
+
5
+ **All business logic in a platform app must live inside a BIM component** (`src/bim-components/`). This is not a convention — it is the architectural constraint of the platform.
6
+
7
+ | Layer | What belongs here | What does NOT belong here |
8
+ |---|---|---|
9
+ | `bim-components/` | State, events, async operations, data access, coordination logic | — |
10
+ | `setups/` | Event subscriptions, calling component methods, UI sync | Business logic, data processing, state |
11
+ | `ui-components/` | Rendering, layout, reading component state | Logic, API calls, mutations |
12
+ | `main.ts` | Boot sequence only | Any logic whatsoever |
13
+
14
+ If logic doesn't fit inside an existing BIM component, the answer is always to create a new one or extend an existing one — never to write it in a setup file, a template, or `main.ts`.
15
+
16
+ ```ts
17
+ // ✗ Forbidden — logic in a setup file
18
+ export const mySetup = (components: OBC.Components) => {
19
+ const fragments = components.get(OBC.FragmentsManager)
20
+ fragments.list.onItemSet.add(async (model) => {
21
+ const data = await fetchSomething(model.uuid) // ← logic here is wrong
22
+ processData(data)
23
+ })
24
+ }
25
+
26
+ // ✓ Correct — setup only wires; logic lives in the component
27
+ export const mySetup = (components: OBC.Components) => {
28
+ const uis = getUIManager(components)
29
+ const manager = components.get(MyManager)
30
+ manager.onDataReady.add(() => uis.custom.get("myPanel").updateInstances())
31
+ }
32
+ ```
33
+
34
+ ---
35
+
36
+ ## Component tiers in a platform app
37
+
38
+ Every platform app works with three tiers of components, all accessible via `components.get()`:
39
+
40
+ - **Engine components** — public, from `@thatopen/components` (OBC) and `@thatopen/components-front` (OBF). Always check these first before building custom logic.
41
+ - **Platform built-in components** — private components from `@thatopen/services` (e.g. `AppManager`, `UIManager`, `ViewportsManager`). Available after `client.setup()`, already wired up.
42
+ - **Custom components** — see the BIM-component and UI-component guides in `docs/`. Live in `src/bim-components/`, self-register in their constructor via `components.add()`.
43
+
44
+ ---
45
+
46
+ ## Project Structure
47
+
48
+ Every app has this shape under `src/`. To configure the app layout or add and reorganize sections in the grid, see [./app-layout.md](./app-layout.md).
49
+
50
+ ```
51
+ src/
52
+ ├── bim-components/ → custom OBC domain components
53
+ ├── ui-components/ → BUI templates, barrel only
54
+ ├── setups/ → initialization and wiring
55
+ ├── app.ts → typed app identity
56
+ ├── globals.ts → global constants (icons, tooltips, colors, etc.)
57
+ └── main.ts → entry point
58
+ ```
59
+
60
+ ### `bim-components/`
61
+
62
+ Each custom OBC component lives in its own folder, built following the BIM-component guide in `docs/` (see [./bim-components/overview.md](./bim-components/overview.md)). The `index.ts` barrel re-exports every component class, its types, and its static `uuid` — this is the single import point for all custom domain logic throughout the app.
63
+
64
+ ### `ui-components/`
65
+
66
+ Templates created following the UI-component guide in `docs/` (see [./ui-components/overview.md](./ui-components/overview.md)) are re-exported from the barrel. The `index.ts` is a **pure barrel** — only re-exports, nothing else. Registration of templates in the UIManager happens in `setups/ui-manager.ts`.
67
+
68
+ ### `setups/`
69
+
70
+ One file per component being initialized. `ui-manager.ts` is always present as fixed boilerplate; all other files are custom wiring. See [./connect-logic-to-ui.md](./connect-logic-to-ui.md) for the full implementation reference. To update the state of a grid element at runtime from a setup or event handler, see [./update-grid-elements.md](./update-grid-elements.md).
71
+
72
+ ### `app.ts` and `main.ts`
73
+
74
+ `app.ts` defines the app's typed shape and the `getAppManager` accessor. `main.ts` boots the platform — no business logic belongs here. See [./app-wiring.md](./app-wiring.md) for structure, `client.setup()`, and `componentSetups`. To access the backend client or project data (`app.client`, `app.projectData`), see [./access-backend-data.md](./access-backend-data.md).
75
+
76
+ ### `globals.ts`
77
+
78
+ Contains the icon registry, color registry, and any other global constants. All icons must be declared here and accessed through `AppManager`. Colors are declared here and imported directly where needed. See [./using-icons.md](./using-icons.md) and [./using-colors.md](./using-colors.md) for details.
79
+
80
+ ---
81
+
82
+ ## See also
83
+
84
+ - [Connect logic to the UI](./connect-logic-to-ui.md) — register templates, sync views, etc.
85
+ - [Configure the app layout](./app-layout.md) — add or reorganize sections in the grid.
86
+ - [App wiring](./app-wiring.md) — boot the app, configure `app.ts`, `main.ts`, `client.setup()`, `componentSetups`.
87
+ - [Access backend data](./access-backend-data.md) — the backend client and project data (`app.client`, `app.projectData`).
88
+ - [Using icons](./using-icons.md) — register or use icons in the app.
89
+ - [Using colors](./using-colors.md) — declare or use global colors (highlighter, palette, etc.).
90
+ - [Update grid elements](./update-grid-elements.md) — update the state of a grid element at runtime.
91
+ - [Scaffolding a new app](./scaffolding.md) — scaffold a new app from scratch using the CLI.
92
+ - [Publishing an app](./publishing.md) — publish an app to the platform (login + publish).
93
+ - [CLI setup](./cli-setup.md) — install the CLI and authenticate with a platform token.
94
+ - [Previewing apps](./previewing.md) — preview the app during development inside the platform.
@@ -0,0 +1,139 @@
1
+ # Configure App Layout
2
+
3
+ ## Grid element constraint
4
+
5
+ Every entry in `grid.elements` must be a single `bim-panel-section`. This is a hard constraint of the platform — the grid only knows how to host panel sections, not arbitrary HTML containers.
6
+
7
+ **Correct:** one `grid.elements` entry = one `bim-panel-section`
8
+ **Wrong:** a UI component that returns a `<div>` wrapping multiple `<bui-panel-section>` elements used as a grid entry
9
+
10
+ If you need multiple sections in one area, register each section separately in `grid.elements` and group them using `panel:` or `tabs:` in the grid template — never wrap them in a container component.
11
+
12
+ ```ts
13
+ // ✗ Wrong — MyDashboard returns <div><bui-panel-section>...</bui-panel-section><bui-panel-section>...</bui-panel-section></div>
14
+ grid.elements = {
15
+ dashboard: { template: uis.custom.get("myDashboard").template, initialState: { components } },
16
+ }
17
+
18
+ // ✓ Correct — each section is its own grid element; grouping happens in the template
19
+ grid.elements = {
20
+ stats: { template: uis.custom.get("statsSection").template, initialState: { components } },
21
+ charts: { template: uis.custom.get("chartsSection").template, initialState: { components } },
22
+ }
23
+ // Then in the layout template:
24
+ // "panel:right(stats,charts) viewer" 1fr / 22rem 1fr
25
+ ```
26
+
27
+ ---
28
+
29
+ ## Grid sync rules
30
+
31
+ Any change to the grid requires updates in multiple places. Never update one without the others.
32
+
33
+ **Adding or removing a layout:**
34
+ 1. `app.ts` → first argument of `BUI.Grid<[layouts], ...>`
35
+ 2. `main.ts` → `grid.layouts` (add/remove the definition) and `grid.layout` if it was the initial layout
36
+
37
+ **Adding or removing an element:**
38
+ 1. `app.ts` → second argument of `BUI.Grid<..., [elements]>`
39
+ 2. `main.ts` → `grid.elements`, `grid.areaGroups` (if applicable), and any layout templates that reference it
40
+
41
+ **Renaming an element** — the name originates in the component, so the change cascades outward:
42
+ 1. Rename the folder in `ui-components/` (kebab-case of the new name)
43
+ 2. Update all internal identifiers: types (`{ComponentName}State`, `{ComponentName}Component`, `{ComponentName}GridElement`), functions (`{camelCase}Template`, `on{PascalCase}Created`)
44
+ 3. Update the `ui-components/index.ts` barrel to point to the renamed folder
45
+ 4. `app.ts` → second argument of `BUI.Grid` reflects the new `{ComponentName}GridElement`
46
+ 5. `main.ts` → `grid.elements` and all layout templates that use that area name
47
+
48
+ ---
49
+
50
+ ## Defining layouts
51
+
52
+ ```ts
53
+ grid.layouts = {
54
+ Viewer: {
55
+ template: `"viewer" 1fr / 1fr`,
56
+ },
57
+ Quantities: {
58
+ icon: app.icons.QUANTITY,
59
+ template: `
60
+ "qtos viewer" 1fr
61
+ /40rem 1fr
62
+ `,
63
+ },
64
+ }
65
+ grid.layout = "Collider"
66
+ ```
67
+
68
+ ---
69
+
70
+ ## Special area tokens
71
+
72
+ ```
73
+ {groupType}:{areaName}(elementA, elementB)
74
+ ```
75
+
76
+ There are two group types: `tabs` and `panel`.
77
+
78
+ ### `tabs:` — one element at a time
79
+
80
+ Shows one element at a time with tab switchers. The user picks which section is visible. Supports nested sub-groups:
81
+
82
+ ```
83
+ tabs:{areaName}({groupName}[elementA, elementB], elementC)
84
+ ```
85
+
86
+ ```ts
87
+ Collider: {
88
+ icon: appIcons.COLLISION,
89
+ template: `
90
+ "tabs:left(group[collider,qtos],collider) viewer" 1fr
91
+ /30rem 1fr
92
+ `,
93
+ }
94
+ ```
95
+
96
+ `areaGroups` options for `tabs:`: `switchersCompact` or `switchersFull`.
97
+
98
+ ### `panel:` — all elements stacked vertically
99
+
100
+ Stacks all elements and keeps them all visible simultaneously, scrollable as a single panel. Use when sections are related and should all be visible at once (e.g. an inspector with multiple info sections):
101
+
102
+ ```ts
103
+ app: {
104
+ template: `
105
+ "tabs:left viewport panel:right" 1fr
106
+ / 22rem 1fr 20rem
107
+ `,
108
+ }
109
+ ```
110
+
111
+ `areaGroups` options for `panel:`: `label` to give the panel a title.
112
+
113
+ ### Choosing between `tabs:` and `panel:`
114
+
115
+ - Use `tabs:` when sections are **alternatives** — the user focuses on one at a time
116
+ - Use `panel:` when sections are **complementary** — the user needs all of them visible together
117
+
118
+ ---
119
+
120
+ ## `grid.elements` and `grid.areaGroups`
121
+
122
+ ```ts
123
+ grid.elements = {
124
+ viewer: viewport,
125
+ qtos: {
126
+ template: uis.custom.get("qtosSection").template,
127
+ initialState: { components },
128
+ label: "Quantities",
129
+ },
130
+ }
131
+
132
+ grid.areaGroups = {
133
+ left: { switchersFull: true },
134
+ right: { label: "Inspector" },
135
+ group: { label: "Configuration", icon: appIcons.APPLY },
136
+ }
137
+ ```
138
+
139
+ > **Grouping sections always happens at the grid level.** To group, combine, or stack panel sections together, the answer is always `tabs:` or `panel:` in the grid template — never nesting one section inside another in the template code.
@@ -0,0 +1,123 @@
1
+ # App Wiring
2
+
3
+ ## `app.ts` — App Identity
4
+
5
+ Defines the app's typed shape and the typed accessor for `AppManager`. The `App` type uses `GridElement` types exported from `ui-components/`:
6
+
7
+ ```ts
8
+ import * as OBC from "@thatopen/components"
9
+ import * as BUI from "@thatopen/ui"
10
+ import { AppManager } from "@thatopen/services"
11
+ import { icons } from "./globals"
12
+ import { FilesSectionGridElement, QtosSectionGridElement } from "./ui-components"
13
+
14
+ export type App = {
15
+ icons: (keyof typeof icons)[]
16
+ grid: BUI.Grid<
17
+ ["Viewer", "Quantities", "Collider"],
18
+ [
19
+ "viewer",
20
+ FilesSectionGridElement,
21
+ QtosSectionGridElement,
22
+ ]
23
+ >
24
+ }
25
+
26
+ export const getAppManager = (components: OBC.Components) =>
27
+ components.get(AppManager<App>)
28
+ ```
29
+
30
+ `getAppManager` is the **only** valid way to access `AppManager` in an app. Never use `components.get(AppManager)` directly — it drops the `App` type parameter and loses all type information about the app's icons, grid layout, and grid elements.
31
+
32
+ ```ts
33
+ // ✗ Avoid — loses the App type
34
+ const app = components.get(AppManager)
35
+
36
+ // ✓ Correct — fully typed
37
+ const app = getAppManager(components)
38
+ ```
39
+
40
+ ### Grid element types
41
+
42
+ - Raw elements → typed as a string literal: `"viewer"`
43
+ - Template-based sections → typed as `{ComponentName}GridElement`, defined in the component's `src/types.ts` and exported through `ui-components/index.ts`
44
+
45
+ ```ts
46
+ // ui-components/files-section/src/types.ts
47
+ export type FilesSectionGridElement = {
48
+ name: "filesSection"
49
+ state: FilesSectionState
50
+ }
51
+ ```
52
+
53
+ ---
54
+
55
+ ## `main.ts` — Entry Point
56
+
57
+ `main.ts` boots the platform and calls `app.init()`. No business logic belongs here.
58
+
59
+ ```ts
60
+ async function main() {
61
+ const client = EngineServicesClient.fromPlatformContext()
62
+
63
+ const { components } = await client.setup(
64
+ { OBC, OBF, BUI, CUI, THREE, FRAGS },
65
+ { uuid: ViewportsManager.uuid },
66
+ { uuid: AppManager.uuid },
67
+ { uuid: UIManager.uuid },
68
+ ) as { components: OBC.Components }
69
+
70
+ await viewportsManager(components)
71
+ const viewports = components.get(ViewportsManager)
72
+ // @ts-ignore
73
+ const { element: viewport } = [...viewports._instances.values()][0]
74
+
75
+ const app = getAppManager(components)
76
+ await app.init({
77
+ client,
78
+ icons: appIcons,
79
+ componentSetups: { ... },
80
+ grid: (grid) => { ... },
81
+ })
82
+ }
83
+
84
+ main().catch(console.error)
85
+ ```
86
+
87
+ ### `client.setup()` — Platform initialization
88
+
89
+ - **First argument** — libraries object (`OBC`, `OBF`, `BUI`, `CUI`, `THREE`, `FRAGS`, etc.)
90
+ - **Subsequent arguments** — `{ uuid }` for each platform built-in component needed. Always present: `ViewportsManager`, `AppManager`, `UIManager`
91
+
92
+ Custom components (from `bim-components/`) do not need to be listed here — they self-register via `components.add()` in their constructor.
93
+
94
+ ---
95
+
96
+ ## `componentSetups` in `app.init()`
97
+
98
+ ```ts
99
+ componentSetups: {
100
+ core: [uiManager, fragmentsManager, highlighter, ifcLoader],
101
+ lazy: [
102
+ { uuid: MyComponent.uuid, fn: myComponentSetup },
103
+ ]
104
+ }
105
+ ```
106
+
107
+ **`core`** — runs before the grid mounts, in parallel. `uiManager` always goes here. Other engine component setups (fragments, highlighter, IFC loader) also typically go here.
108
+
109
+ **`lazy`** — runs the first time the corresponding component is instantiated. The `uuid` is imported from the `bim-components/` barrel. Use for custom components that can be loaded on demand.
110
+
111
+ The `uuid` of each custom component — a static property defined on the class — is imported from the `bim-components/` barrel when referencing it in `lazy`:
112
+
113
+ ```ts
114
+ import { MyComponent } from "../bim-components"
115
+
116
+ componentSetups: {
117
+ lazy: [
118
+ { uuid: MyComponent.uuid, fn: myComponentSetup },
119
+ ]
120
+ }
121
+ ```
122
+
123
+ When it's not obvious whether a custom component should be `core` or `lazy`, **decide based on whether the component is needed at boot or only on demand.**