Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Building on the That Open Platform — Agent Guide

You are an AI assistant helping a user build a **BIM app** or **cloud component** on the
That Open Platform using **`@thatopen/services`** (the `thatopen` CLI + client library).

**Read this file first.** It states the rules you must follow, then routes you to the one
detailed doc for whatever you're doing. Open only the doc you need — don't load everything.

> This guide is tool-agnostic: it works with any AI (Claude, Codex, your own). All docs it
> references ship inside the `@thatopen/services` package under `docs/`.

---

## How to work

- **New app or component?** The **first step is always the CLI** (`thatopen create`) — never
hand-write the scaffold. See [docs/cli-setup.md](./docs/cli-setup.md) then
[docs/scaffolding.md](./docs/scaffolding.md).
- **Propose a short plan and get the user's OK** before changing files. If scope is unclear, ask.
- Prefer existing engine functionality over custom code (see rule 2).

## Hard rules (always apply)

1. **All business logic lives in a BIM component** (`src/bim-components/`).
`setups/` only wire; `ui-components/` only render; `main.ts` only boots. Logic that doesn't
fit an existing component → create a new one. Never put logic in a setup, template, or `main.ts`.
2. **Check engine components first** — `@thatopen/components` (`OBC`) and
`@thatopen/components-front` (`OBF`) — before building anything custom.
3. **Platform built-ins** (`AppManager`, `ViewportsManager`, `UIManager`, …) come from
`@thatopen/services` and are available after `client.setup()`. Don't reinvent them.

---

## What are you doing? → open the right doc

### Set up & ship
| Goal | Doc |
|---|---|
| Install the CLI + authenticate | [docs/cli-setup.md](./docs/cli-setup.md) |
| Scaffold a new app / component | [docs/scaffolding.md](./docs/scaffolding.md) |
| Preview the app inside the platform | [docs/previewing.md](./docs/previewing.md) |
| Publish an app or component | [docs/publishing.md](./docs/publishing.md) |

### Structure & wire an app
| Goal | Doc |
|---|---|
| Project structure, architecture rules, component tiers | [docs/app-architecture.md](./docs/app-architecture.md) |
| Boot / `app.ts` / `main.ts` / `client.setup()` | [docs/app-wiring.md](./docs/app-wiring.md) |
| Configure layout, add/reorganize grid sections | [docs/app-layout.md](./docs/app-layout.md) |
| Connect component logic to the UI | [docs/connect-logic-to-ui.md](./docs/connect-logic-to-ui.md) |
| Update a grid element's state at runtime | [docs/update-grid-elements.md](./docs/update-grid-elements.md) |
| Access the backend client / project data | [docs/access-backend-data.md](./docs/access-backend-data.md) |
| Register and use icons | [docs/using-icons.md](./docs/using-icons.md) |
| Declare and use colors | [docs/using-colors.md](./docs/using-colors.md) |

### Build a custom BIM component
Start at [docs/bim-components/overview.md](./docs/bim-components/overview.md) — conventions,
lifecycle (setup/cleanup), exposing events, observable/element collections, per-frame updates,
saving/restoring state, user-driven object creation.

### Build a UI component
Start at [docs/ui-components/overview.md](./docs/ui-components/overview.md) — rendering patterns,
section layout, data tables, inline forms, confirmation dialogs, display text, async actions.

### Cloud components & automations
[docs/cloud-components.md](./docs/cloud-components.md) — build / run locally / publish a cloud
component, the execution globals, and event-triggered automations.

---

## Reference (also shipped in this package)

- **Library API** — `PlatformClient` vs `EngineServicesClient`, the permissions contract, and the
full method surface → [CONTEXT.md](./CONTEXT.md)
- **Built-in components API** — config interfaces, method signatures, `@example` blocks →
`src/built-in/index.ts` (in the installed package, `node_modules/@thatopen/services/`)
8 changes: 4 additions & 4 deletions CONTEXT.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# thatopen-services
# @thatopen/services

Client library and CLI for the That Open Platform — a cloud platform for building BIM (Building Information Modeling) software.

Expand Down Expand Up @@ -81,7 +81,7 @@ every request, so Auth0's `getAccessTokenSilently()` and similar
refreshing sources Just Work:

```ts
import { PlatformClient } from 'thatopen-services';
import { PlatformClient } from '@thatopen/services';
const client = new PlatformClient(
() => auth0.getAccessTokenSilently(),
'https://api.thatopen.com',
Expand Down Expand Up @@ -142,7 +142,7 @@ yarn create-version # Build → changeset → version → publish
| **Item type** | `APP` | `TOOL` |
| **Entry point** | Side effects in `main.ts` (renders UI) | `export async function main()` |
| **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. |
| **Build output** | IIFE `dist/bundle.js` (all deps bundled) | IIFE `dist/bundle.js` (only `thatopen-services` externalized) |
| **Build output** | IIFE `dist/bundle.js` (all deps bundled) | IIFE `dist/bundle.js` (only `@thatopen/services` externalized) |
| **Template** | `bim`, `default`, or `test` | `cloud` or `cloud-test` |

### Authentication
Expand All @@ -156,7 +156,7 @@ Two modes, controlled by `useBearer` in the constructor:
Built-in components are platform-hosted UI modules fetched at runtime. Usage pattern:

```ts
import { AppManager, ViewportManager } from "thatopen-services";
import { AppManager, ViewportManager } from "@thatopen/services";

// Register all library globals once
client.setBuiltInGlobals({ OBC, OBF, BUI, CUI, THREE, FRAGS });
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# thatopen-services
# @thatopen/services

Client library and CLI for building BIM apps and cloud components on the [That Open Platform](https://platform.thatopen.com).

Expand All @@ -8,7 +8,7 @@ Client library and CLI for building BIM apps and cloud components on the [That O

```bash
# Install the services package globally
npm i thatopen-services -g
npm i @thatopen/services -g
```

Then, create a brand new app repository:
Expand Down Expand Up @@ -58,7 +58,7 @@ Use `npx thatopen create .` to scaffold in the current directory instead of crea
## Library usage

```typescript
import { EngineServicesClient } from 'thatopen-services';
import { EngineServicesClient } from '@thatopen/services';

const client = new EngineServicesClient(accessToken, apiUrl);

Expand Down Expand Up @@ -152,7 +152,7 @@ Cloud components export an `async function main()` that runs on the server. The
Platform-hosted UI components loaded at runtime:

```typescript
import { AppManager, ViewportManager } from "thatopen-services";
import { AppManager, ViewportManager } from "@thatopen/services";

// Register all library globals once
client.setBuiltInGlobals({ OBC, OBF, BUI, CUI, THREE, FRAGS });
Expand Down
19 changes: 19 additions & 0 deletions docs/access-backend-data.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Access Backend Data

## Accessing backend data via `AppManager`

After `init()`, `AppManager` exposes:

- **`app.client`** — `EngineServicesClient`: backend client for API calls
- **`app.projectData`** — `ProjectData`: current user info, role, project metadata

Both are resolved before any setup runs. Always access them through `getAppManager(components)`:

```ts
const app = getAppManager(components)
if (app.projectData.currentUser?.role.name === "Project Admin") {
// render admin-only controls
}

const files = await app.client.getProjectFiles(app.projectData.id)
```
94 changes: 94 additions & 0 deletions docs/app-architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# App Architecture and Composition

## Architecture rule: logic lives only in BIM components

**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.

| Layer | What belongs here | What does NOT belong here |
|---|---|---|
| `bim-components/` | State, events, async operations, data access, coordination logic | — |
| `setups/` | Event subscriptions, calling component methods, UI sync | Business logic, data processing, state |
| `ui-components/` | Rendering, layout, reading component state | Logic, API calls, mutations |
| `main.ts` | Boot sequence only | Any logic whatsoever |

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`.

```ts
// ✗ Forbidden — logic in a setup file
export const mySetup = (components: OBC.Components) => {
const fragments = components.get(OBC.FragmentsManager)
fragments.list.onItemSet.add(async (model) => {
const data = await fetchSomething(model.uuid) // ← logic here is wrong
processData(data)
})
}

// ✓ Correct — setup only wires; logic lives in the component
export const mySetup = (components: OBC.Components) => {
const uis = getUIManager(components)
const manager = components.get(MyManager)
manager.onDataReady.add(() => uis.custom.get("myPanel").updateInstances())
}
```

---

## Component tiers in a platform app

Every platform app works with three tiers of components, all accessible via `components.get()`:

- **Engine components** — public, from `@thatopen/components` (OBC) and `@thatopen/components-front` (OBF). Always check these first before building custom logic.
- **Platform built-in components** — private components from `@thatopen/services` (e.g. `AppManager`, `UIManager`, `ViewportsManager`). Available after `client.setup()`, already wired up.
- **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()`.

---

## Project Structure

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).

```
src/
├── bim-components/ → custom OBC domain components
├── ui-components/ → BUI templates, barrel only
├── setups/ → initialization and wiring
├── app.ts → typed app identity
├── globals.ts → global constants (icons, tooltips, colors, etc.)
└── main.ts → entry point
```

### `bim-components/`

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.

### `ui-components/`

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`.

### `setups/`

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).

### `app.ts` and `main.ts`

`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).

### `globals.ts`

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.

---

## See also

- [Connect logic to the UI](./connect-logic-to-ui.md) — register templates, sync views, etc.
- [Configure the app layout](./app-layout.md) — add or reorganize sections in the grid.
- [App wiring](./app-wiring.md) — boot the app, configure `app.ts`, `main.ts`, `client.setup()`, `componentSetups`.
- [Access backend data](./access-backend-data.md) — the backend client and project data (`app.client`, `app.projectData`).
- [Using icons](./using-icons.md) — register or use icons in the app.
- [Using colors](./using-colors.md) — declare or use global colors (highlighter, palette, etc.).
- [Update grid elements](./update-grid-elements.md) — update the state of a grid element at runtime.
- [Scaffolding a new app](./scaffolding.md) — scaffold a new app from scratch using the CLI.
- [Publishing an app](./publishing.md) — publish an app to the platform (login + publish).
- [CLI setup](./cli-setup.md) — install the CLI and authenticate with a platform token.
- [Previewing apps](./previewing.md) — preview the app during development inside the platform.
139 changes: 139 additions & 0 deletions docs/app-layout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Configure App Layout

## Grid element constraint

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.

**Correct:** one `grid.elements` entry = one `bim-panel-section`
**Wrong:** a UI component that returns a `<div>` wrapping multiple `<bui-panel-section>` elements used as a grid entry

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.

```ts
// ✗ Wrong — MyDashboard returns <div><bui-panel-section>...</bui-panel-section><bui-panel-section>...</bui-panel-section></div>
grid.elements = {
dashboard: { template: uis.custom.get("myDashboard").template, initialState: { components } },
}

// ✓ Correct — each section is its own grid element; grouping happens in the template
grid.elements = {
stats: { template: uis.custom.get("statsSection").template, initialState: { components } },
charts: { template: uis.custom.get("chartsSection").template, initialState: { components } },
}
// Then in the layout template:
// "panel:right(stats,charts) viewer" 1fr / 22rem 1fr
```

---

## Grid sync rules

Any change to the grid requires updates in multiple places. Never update one without the others.

**Adding or removing a layout:**
1. `app.ts` → first argument of `BUI.Grid<[layouts], ...>`
2. `main.ts` → `grid.layouts` (add/remove the definition) and `grid.layout` if it was the initial layout

**Adding or removing an element:**
1. `app.ts` → second argument of `BUI.Grid<..., [elements]>`
2. `main.ts` → `grid.elements`, `grid.areaGroups` (if applicable), and any layout templates that reference it

**Renaming an element** — the name originates in the component, so the change cascades outward:
1. Rename the folder in `ui-components/` (kebab-case of the new name)
2. Update all internal identifiers: types (`{ComponentName}State`, `{ComponentName}Component`, `{ComponentName}GridElement`), functions (`{camelCase}Template`, `on{PascalCase}Created`)
3. Update the `ui-components/index.ts` barrel to point to the renamed folder
4. `app.ts` → second argument of `BUI.Grid` reflects the new `{ComponentName}GridElement`
5. `main.ts` → `grid.elements` and all layout templates that use that area name

---

## Defining layouts

```ts
grid.layouts = {
Viewer: {
template: `"viewer" 1fr / 1fr`,
},
Quantities: {
icon: app.icons.QUANTITY,
template: `
"qtos viewer" 1fr
/40rem 1fr
`,
},
}
grid.layout = "Collider"
```

---

## Special area tokens

```
{groupType}:{areaName}(elementA, elementB)
```

There are two group types: `tabs` and `panel`.

### `tabs:` — one element at a time

Shows one element at a time with tab switchers. The user picks which section is visible. Supports nested sub-groups:

```
tabs:{areaName}({groupName}[elementA, elementB], elementC)
```

```ts
Collider: {
icon: appIcons.COLLISION,
template: `
"tabs:left(group[collider,qtos],collider) viewer" 1fr
/30rem 1fr
`,
}
```

`areaGroups` options for `tabs:`: `switchersCompact` or `switchersFull`.

### `panel:` — all elements stacked vertically

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):

```ts
app: {
template: `
"tabs:left viewport panel:right" 1fr
/ 22rem 1fr 20rem
`,
}
```

`areaGroups` options for `panel:`: `label` to give the panel a title.

### Choosing between `tabs:` and `panel:`

- Use `tabs:` when sections are **alternatives** — the user focuses on one at a time
- Use `panel:` when sections are **complementary** — the user needs all of them visible together

---

## `grid.elements` and `grid.areaGroups`

```ts
grid.elements = {
viewer: viewport,
qtos: {
template: uis.custom.get("qtosSection").template,
initialState: { components },
label: "Quantities",
},
}

grid.areaGroups = {
left: { switchersFull: true },
right: { label: "Inspector" },
group: { label: "Configuration", icon: appIcons.APPLY },
}
```

> **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.
Loading
Loading