@vived/component-pneumatic-panel 1.2.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +212 -0
- package/COMPONENT_KNOWLEDGE.md +642 -0
- package/LICENSE +15 -0
- package/README.md +62 -23
- package/dist/Domain/Adapters/pneumaticPanelPMAdapter.d.ts.map +1 -1
- package/dist/Domain/Controllers/applyPanelState.d.ts.map +1 -1
- package/dist/Domain/Controllers/getPanelState.d.ts.map +1 -1
- package/dist/Domain/Controllers/panelStateDefaults.d.ts +0 -1
- package/dist/Domain/Controllers/panelStateDefaults.d.ts.map +1 -1
- package/dist/Domain/Entities/PneumaticPanelEntity.d.ts +0 -4
- package/dist/Domain/Entities/PneumaticPanelEntity.d.ts.map +1 -1
- package/dist/Domain/Mocks/MockPneumaticPanelSelectUC.d.ts +0 -4
- package/dist/Domain/Mocks/MockPneumaticPanelSelectUC.d.ts.map +1 -1
- package/dist/Domain/PMs/PneumaticPanelPM.d.ts +0 -3
- package/dist/Domain/PMs/PneumaticPanelPM.d.ts.map +1 -1
- package/dist/Domain/UCs/PneumaticPanelCheckPressureUC.d.ts +4 -3
- package/dist/Domain/UCs/PneumaticPanelCheckPressureUC.d.ts.map +1 -1
- package/dist/Domain/UCs/PneumaticPanelSelectUC.d.ts +0 -14
- package/dist/Domain/UCs/PneumaticPanelSelectUC.d.ts.map +1 -1
- package/dist/Frameworks/Babylon/PneumaticPanelBabylonView.d.ts +9 -12
- package/dist/Frameworks/Babylon/PneumaticPanelBabylonView.d.ts.map +1 -1
- package/dist/Frameworks/Babylon/PneumaticPanelExhaustView.d.ts +2 -2
- package/dist/Frameworks/Babylon/PneumaticPanelExhaustView.d.ts.map +1 -1
- package/dist/Frameworks/Babylon/PneumaticPanelGaugeView.d.ts +2 -2
- package/dist/Frameworks/Babylon/PneumaticPanelGaugeView.d.ts.map +1 -1
- package/dist/Frameworks/Babylon/PneumaticPanelHitboxView.d.ts +5 -7
- package/dist/Frameworks/Babylon/PneumaticPanelHitboxView.d.ts.map +1 -1
- package/dist/Frameworks/Babylon/PneumaticPanelLockView.d.ts +2 -2
- package/dist/Frameworks/Babylon/PneumaticPanelLockView.d.ts.map +1 -1
- package/dist/PneumaticPanelFacade.d.ts +1 -13
- package/dist/PneumaticPanelFacade.d.ts.map +1 -1
- package/dist/index.d.ts +1 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1257 -51
- package/package.json +9 -2
- package/dist/.gitkeep +0 -0
- package/dist/Domain/Controllers/clearAllHighlights.d.ts +0 -9
- package/dist/Domain/Controllers/clearAllHighlights.d.ts.map +0 -1
- package/dist/Domain/Controllers/onPanelHoverEnter.d.ts +0 -7
- package/dist/Domain/Controllers/onPanelHoverEnter.d.ts.map +0 -1
- package/dist/Domain/Controllers/onPanelHoverExit.d.ts +0 -7
- package/dist/Domain/Controllers/onPanelHoverExit.d.ts.map +0 -1
- package/dist/Domain/Controllers/panelHitboxHoverEnter.d.ts +0 -7
- package/dist/Domain/Controllers/panelHitboxHoverEnter.d.ts.map +0 -1
- package/dist/Domain/Controllers/panelHitboxHoverExit.d.ts +0 -7
- package/dist/Domain/Controllers/panelHitboxHoverExit.d.ts.map +0 -1
- package/dist/Domain/Controllers/setHighlightedObject.d.ts +0 -11
- package/dist/Domain/Controllers/setHighlightedObject.d.ts.map +0 -1
- package/dist/Frameworks/Babylon/OutlinePulseController.d.ts +0 -18
- package/dist/Frameworks/Babylon/OutlinePulseController.d.ts.map +0 -1
- package/dist/Frameworks/Babylon/OverlayPulseController.d.ts +0 -17
- package/dist/Frameworks/Babylon/OverlayPulseController.d.ts.map +0 -1
- package/dist/Frameworks/Babylon/PanelOutlineController.d.ts +0 -32
- package/dist/Frameworks/Babylon/PanelOutlineController.d.ts.map +0 -1
- package/dist/Frameworks/Babylon/ScreenSpaceOutlinePulseController.d.ts +0 -17
- package/dist/Frameworks/Babylon/ScreenSpaceOutlinePulseController.d.ts.map +0 -1
- package/dist/clipPlaneFragment-BRmzi2tV.js +0 -55
- package/dist/clipPlaneFragment-CDdVuU0n.js +0 -55
- package/dist/clipPlaneVertex-CIEg9nQi.js +0 -358
- package/dist/clipPlaneVertex-CIarPzTc.js +0 -391
- package/dist/dumpTools-BUC-AUZt.js +0 -110
- package/dist/glowBlurPostProcess.fragment-DDhSDO38.js +0 -13
- package/dist/glowBlurPostProcess.fragment-fpk0xJVe.js +0 -13
- package/dist/glowMapGeneration.fragment-BBSNxu8K.js +0 -193
- package/dist/glowMapGeneration.fragment-Cl-xwsZp.js +0 -161
- package/dist/glowMapGeneration.vertex-CzQHgJdT.js +0 -85
- package/dist/glowMapGeneration.vertex-DErxE57o.js +0 -85
- package/dist/glowMapMerge.vertex-BQosy0g5.js +0 -14
- package/dist/glowMapMerge.vertex-tVYqLRGm.js +0 -13
- package/dist/index-CmNnQLDH.js +0 -36863
- package/dist/pass.fragment-D3pYn4R1.js +0 -10
- package/dist/pneumaticPanel.glb +0 -0
- package/dist/selection.fragment-CGZK1kVt.js +0 -45
- package/dist/selection.fragment-DjWRsu7R.js +0 -45
- package/dist/selection.vertex-DY_1B2lX.js +0 -84
- package/dist/selection.vertex-PhS5tOTA.js +0 -83
- package/dist/selectionOutline.fragment-Bp__J_pC.js +0 -53
- package/dist/selectionOutline.fragment-DmyQJACa.js +0 -54
- package/dist/studio.env +0 -0
- package/dist/thinEngine-dZnz-Owz.js +0 -4
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
# Pneumatic Panel — VIVED Smart Component
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
A 3D pneumatic control panel for VIVED slide apps featuring an exhaust knob, safety lock, and pressure gauge. Developers can toggle exhaust and lock states, check pressure, and snapshot/restore panel state for guided training or assessment interactions. Use it when you need a compact industrial interaction model with built-in lockout/tagout constraints and pressure behavior. The component is presentation-free (ADR-0009, ADR-0010): it owns geometry, domain, and identity, while the Host owns all hover/hint/selection feedback and every dialog — reading `view.highlightGroupsByObjectId` to outline the panel itself, and rendering the pressure readout from the VM.
|
|
6
|
+
|
|
7
|
+
The canonical host-facing surface is the **`PneumaticPanelFacade`** — a single typed object (conforming to the structural `SmartComponent` contract) that drives an instance through commands, typed events, lifecycle, and persisted state. The flat controllers and PM adapter remain available as the underlying mechanism the facade delegates to (and which the Babylon view boundary still calls directly).
|
|
8
|
+
|
|
9
|
+
- **Package**: @vived/component-pneumatic-panel
|
|
10
|
+
- **Version**: 3.0.0
|
|
11
|
+
- **Interface version**: 1
|
|
12
|
+
- **GitHub**: vivedlearning/component-pneumatic-panel
|
|
13
|
+
|
|
14
|
+
## Discovery
|
|
15
|
+
|
|
16
|
+
- **Category**: Industrial Equipment / Pneumatics
|
|
17
|
+
- **Tags**: pneumatic, panel, exhaust, valve, lock, lockout, tagout, pressure, gauge, PSI, industrial controls, safety training, assessment interaction, babylon, smart component
|
|
18
|
+
- **Visual description**: A compact pneumatic panel assembly with a rotatable exhaust knob, lock body, ghost-lock preview mesh, and circular pressure gauge. The lock and knob support click interactions, and visual guidance can be toggled for training or assessment.
|
|
19
|
+
- **Multi-instance**: Yes
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
1. **Install** — package and peer dependencies.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @vived/component-pneumatic-panel @vived/core @babylonjs/core @vived/app
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
2. **Register the feature factory** — wire the domain layer into your app.
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { makeAppObjectRepo, makeDomainFactoryRepo } from "@vived/core";
|
|
33
|
+
import { makePneumaticPanelFeatureFactory } from "@vived/component-pneumatic-panel";
|
|
34
|
+
|
|
35
|
+
const appObjects = makeAppObjectRepo();
|
|
36
|
+
const factoryRepo = makeDomainFactoryRepo(appObjects);
|
|
37
|
+
|
|
38
|
+
makePneumaticPanelFeatureFactory(appObjects);
|
|
39
|
+
factoryRepo.setupDomain();
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
3. **Create an instance and load the 3D model** — use the facade, the canonical host-facing surface.
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { PneumaticPanelFacade } from "@vived/component-pneumatic-panel";
|
|
46
|
+
|
|
47
|
+
const panel = new PneumaticPanelFacade("panel-1", appObjects); // creates the domain stack
|
|
48
|
+
await panel.load(); // attaches and loads the Babylon view
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
4. **Basic interaction** — drive the instance through facade methods and typed events.
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
panel.toggleExhaust();
|
|
55
|
+
panel.toggleLock();
|
|
56
|
+
panel.checkPressure();
|
|
57
|
+
|
|
58
|
+
const unsubscribe = panel.onEvent("exhaustOpened", () => {
|
|
59
|
+
console.log("exhaust opened");
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
> Need the raw `AppObject` (e.g. to parent the root node or register shadow casters)? Use the
|
|
64
|
+
> `createBabylonPneumaticPanel` composition entry point instead — it creates the same domain
|
|
65
|
+
> stack and view and returns the `AppObject`. See the mounting recipe below.
|
|
66
|
+
|
|
67
|
+
## Facade (host-facing surface)
|
|
68
|
+
|
|
69
|
+
The **`PneumaticPanelFacade`** is the one surface a host uses to drive an instance — commands, events, reactive view model, lifecycle, and persisted state through a single typed object. The flat controllers and PM adapter are the underlying mechanism it delegates to (documented under Internal / Advanced); host code should not call them directly.
|
|
70
|
+
|
|
71
|
+
### Structural contract (Contract v1)
|
|
72
|
+
|
|
73
|
+
Every VIVED facade implements the same eight mandatory members with this exact shape, so a host can hold mixed component types behind one uniform `SmartComponent` reference. This facade's **`interfaceVersion` is `1`**.
|
|
74
|
+
|
|
75
|
+
| Member | Signature | Notes |
|
|
76
|
+
| --- | --- | --- |
|
|
77
|
+
| `id` | `readonly string` | The instance identifier (matches the AppObject id). |
|
|
78
|
+
| `interfaceVersion` | `readonly number` | The `SmartComponent` contract version this facade implements (`1`). |
|
|
79
|
+
| `onEvent` | `<K extends keyof PneumaticPanelEvents>(event: K, cb: PneumaticPanelEvents[K]) => () => void` | Subscribe to a typed event. Returns an unsubscribe function — there is no separate `off`. |
|
|
80
|
+
| `onViewModel` | `(cb: (vm: PneumaticPanelVM) => void) => () => void` | Subscribe to the reactive VM; emits the current VM immediately, then on every change. Returns an unsubscribe function. |
|
|
81
|
+
| `load` | `(variant?: string) => Promise<void>` | The only async member; the only place view/asset work happens. |
|
|
82
|
+
| `destroy` | `() => void` | Tear down the component and release all resources. |
|
|
83
|
+
| `getState` | `() => PneumaticPanelState` | Versioned snapshot of authored configuration. |
|
|
84
|
+
| `applyState` | `(state: PneumaticPanelState) => void` | Restore authored configuration from a snapshot. |
|
|
85
|
+
|
|
86
|
+
### Lifecycle (two-phase)
|
|
87
|
+
|
|
88
|
+
- **Construction is synchronous and idempotent.** `new PneumaticPanelFacade(id, appObjects)` builds the entire domain stack (Entity, Use Cases, PM, adapter) with no Babylon dependency — it is headless-testable. It uses `getOrCreate` semantics: constructing a facade for an existing `id` returns a handle to the same instance, never a duplicate.
|
|
89
|
+
- **`load(variant?)` is the only async phase** and the only step that touches the Babylon view / GLB asset.
|
|
90
|
+
- **Everything else works before `load()`.** All command methods, `onEvent`, `onViewModel`, `getState`, and `applyState` are fully functional the moment the facade is constructed — `load()` is required only for the 3D model to render.
|
|
91
|
+
|
|
92
|
+
### Command methods
|
|
93
|
+
|
|
94
|
+
Return-value convention: a command a domain rule can **block** returns `boolean` (`true` = took effect, `false` = blocked); a fire-and-forget command returns `void`. Commands never return domain data — read state via `getState()` or `onViewModel`.
|
|
95
|
+
|
|
96
|
+
| Command | Signature | Description |
|
|
97
|
+
| --- | --- | --- |
|
|
98
|
+
| `toggleExhaust` | `() => boolean` | Toggle exhaust open/closed. `false` if blocked by lock state. |
|
|
99
|
+
| `toggleLock` | `() => boolean` | Toggle lock. Can only engage when exhaust is open; `false` if exhaust is closed. Always succeeds when unlocking. |
|
|
100
|
+
| `checkPressure` | `() => void` | Register a pressure check. Fires `pressureChecked`; renders nothing — the Host shows any dialog, reading `pressure` from the VM (ADR-0010). |
|
|
101
|
+
| `setShowGhostLockHint` | `(show: boolean) => void` | Enable/disable always-visible ghost-lock hint behavior. |
|
|
102
|
+
| `enableHitbox` | `() => void` | Enable whole-panel selection mode. Sub-component clicks are disabled while active. |
|
|
103
|
+
| `disableHitbox` | `() => void` | Disable whole-panel selection mode. Restore sub-component interactivity. |
|
|
104
|
+
|
|
105
|
+
### Event catalog (`PneumaticPanelEvents`)
|
|
106
|
+
|
|
107
|
+
All event callbacks are payload-free `() => void`; the host reacts by reading `getState()` or the VM. `onEvent` returns an unsubscribe function.
|
|
108
|
+
|
|
109
|
+
| Event | Fires when |
|
|
110
|
+
| --- | --- |
|
|
111
|
+
| `exhaustOpened` | exhaust valve transitioned to open |
|
|
112
|
+
| `exhaustClosed` | exhaust valve transitioned to closed |
|
|
113
|
+
| `exhaustCloseBlockedByLock` | a close attempt was blocked because the panel is locked |
|
|
114
|
+
| `panelLocked` | panel transitioned to locked |
|
|
115
|
+
| `panelUnlocked` | panel transitioned to unlocked |
|
|
116
|
+
| `panelSelected` | the whole-panel hitbox was clicked |
|
|
117
|
+
| `pressureChecked` | a pressure check was triggered |
|
|
118
|
+
|
|
119
|
+
### Reactive view model (`PneumaticPanelVM`)
|
|
120
|
+
|
|
121
|
+
The live render picture streamed by `onViewModel`. **Distinct from the state snapshot** — the VM includes transient/derived fields (live `pressure`, derived `subComponentsInteractive`) and must not be merged with `PneumaticPanelState`.
|
|
122
|
+
|
|
123
|
+
| Field | Type | Notes |
|
|
124
|
+
| --- | --- | --- |
|
|
125
|
+
| `exhaustOpen` | `boolean` | |
|
|
126
|
+
| `isLocked` | `boolean` | |
|
|
127
|
+
| `pressure` | `number` | Live value — derived from exhaust state (0 open, 60 closed). |
|
|
128
|
+
| `showGhostLockHint` | `boolean` | |
|
|
129
|
+
| `panelHitboxEnabled` | `boolean` | |
|
|
130
|
+
| `subComponentsInteractive` | `boolean` | Derived: always `!panelHitboxEnabled`. |
|
|
131
|
+
|
|
132
|
+
### State snapshot (`PneumaticPanelState`)
|
|
133
|
+
|
|
134
|
+
The versioned, persistable subset of **authored configuration** captured by `getState()` and restored by `applyState()`. Excludes transient/derived fields that live only in the VM (e.g. `pressure`). Current `version` is **`2`**.
|
|
135
|
+
|
|
136
|
+
| Field | Type |
|
|
137
|
+
| --- | --- |
|
|
138
|
+
| `version` | `number` (currently `2`) |
|
|
139
|
+
| `exhaustOpen` | `boolean` |
|
|
140
|
+
| `isLocked` | `boolean` |
|
|
141
|
+
| `showGhostLockHint` | `boolean` |
|
|
142
|
+
| `panelHitboxEnabled` | `boolean` |
|
|
143
|
+
|
|
144
|
+
`applyState` is **best-effort forward-compatible** (ADR-0008): it applies the fields present and falls back to entity defaults for absent ones; it never throws on version mismatch. Older v1 snapshots (which carried a since-removed `highlightedObject` field) still restore cleanly — the unknown field is ignored.
|
|
145
|
+
|
|
146
|
+
### Variants and the objectId interaction map
|
|
147
|
+
|
|
148
|
+
`load(variant?)` accepts an optional variant string; an omitted or unrecognized variant falls back to the default asset (best-effort — no variants are implemented yet). The real contract between the domain and a GLB is the **objectId interaction map** — the named interactive nodes the view binds to — so any asset honoring this map can serve as a variant:
|
|
149
|
+
|
|
150
|
+
| objectId | Role |
|
|
151
|
+
| --- | --- |
|
|
152
|
+
| `exhaust_knob` | Click to toggle exhaust; rotates with exhaust state. |
|
|
153
|
+
| `lock` | Lock transform and child meshes; click to toggle lock state. |
|
|
154
|
+
| `ghost_lock` | Hover-reveal lock helper and click target for lock action. |
|
|
155
|
+
| `gauge` | Click to invoke pressure check. |
|
|
156
|
+
| `panel_hitbox` | Invisible full-panel bounding mesh; active only when `panelHitboxEnabled` is true. Keyed in `highlightGroupsByObjectId` to every visible panel mesh. |
|
|
157
|
+
|
|
158
|
+
## API Reference
|
|
159
|
+
|
|
160
|
+
### Public API (use these)
|
|
161
|
+
|
|
162
|
+
The **`PneumaticPanelFacade`** is THE host-facing surface for driving an instance. Its constructor synchronously creates the full domain stack (Entity, Use Cases, PM, adapter); `load()` attaches the Babylon view. For scenarios that need the raw `AppObject`, `createBabylonPneumaticPanel` is the equivalent composition entry point. The controllers and PM adapter below are the underlying mechanism the facade delegates to, and remain exported for direct use (the framework/view boundary calls them directly).
|
|
163
|
+
|
|
164
|
+
#### `PneumaticPanelFacade` (primary)
|
|
165
|
+
|
|
166
|
+
**The facade is THE way to drive an instance — see the [Facade](#facade-host-facing-surface) section above for the full host-facing surface:** the eight-member Contract v1, two-phase lifecycle, command methods, event catalog, reactive VM, state snapshot, and variants, all with inline signatures. `makePneumaticPanelFeatureFactory` (below) is the only other Public API entry point — call it once at startup before `setupDomain()`.
|
|
167
|
+
|
|
168
|
+
#### Composition entry point & controllers
|
|
169
|
+
|
|
170
|
+
| Function | Signature | Description |
|
|
171
|
+
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
172
|
+
| createBabylonPneumaticPanel | (id: string, appObjects: AppObjectRepo) => Promise\<AppObject \| undefined\> | Composition entry point. Creates the full domain stack and Babylon view, loads the asset, and returns the AppObject. |
|
|
173
|
+
| makePneumaticPanelFeatureFactory | (appObjects: AppObjectRepo) => PneumaticPanelFeatureFactory | Registers the feature factory. Call once at app startup before `setupDomain()`. |
|
|
174
|
+
| tryToggleExhaust | (id: string, appObjects: AppObjectRepo) => boolean | Toggles exhaust open/closed. Returns false if blocked by lock state. |
|
|
175
|
+
| tryToggleLock | (id: string, appObjects: AppObjectRepo) => boolean | Toggles lock state. Can only engage when exhaust is open; returns false if exhaust is closed. Always succeeds when unlocking. |
|
|
176
|
+
| checkPressure | (id: string, appObjects: AppObjectRepo) => void | Registers a pressure check and notifies `pressureChecked` subscribers. Renders nothing. |
|
|
177
|
+
| setShowGhostLockHint | (id: string, appObjects: AppObjectRepo, on: boolean) => void | Enables/disables always-visible ghost-lock hint behavior. |
|
|
178
|
+
| enablePanelHitbox | (id: string, appObjects: AppObjectRepo) => void | Enables whole-panel selection mode. Sub-component clicks are disabled while active. |
|
|
179
|
+
| disablePanelHitbox | (id: string, appObjects: AppObjectRepo) => void | Disables whole-panel selection mode. Restores sub-component interactivity. |
|
|
180
|
+
| getPanelState | (id: string, appObjects: AppObjectRepo, version: number) => PneumaticPanelState | Returns a versioned snapshot of panel state. (Facade: `getState()`.) |
|
|
181
|
+
| applyPanelState | (id: string, appObjects: AppObjectRepo, state: PneumaticPanelState) => void | Restores panel state from a snapshot. (Facade: `applyState()`.) |
|
|
182
|
+
| onExhaustOpened | (id: string, appObjects: AppObjectRepo, callback: () => void) => () => void | Subscribes to exhaust-opened transitions. Returns an unsubscribe function. |
|
|
183
|
+
| onExhaustClosed | (id: string, appObjects: AppObjectRepo, callback: () => void) => () => void | Subscribes to exhaust-closed transitions. Returns an unsubscribe function. |
|
|
184
|
+
| onExhaustCloseBlockedByLock | (id: string, appObjects: AppObjectRepo, callback: () => void) => () => void | Subscribes to blocked exhaust-close attempts (exhaust is locked). Returns an unsubscribe function. |
|
|
185
|
+
| onPanelLocked | (id: string, appObjects: AppObjectRepo, callback: () => void) => () => void | Subscribes to panel-locked transitions. Returns an unsubscribe function. |
|
|
186
|
+
| onPanelUnlocked | (id: string, appObjects: AppObjectRepo, callback: () => void) => () => void | Subscribes to panel-unlocked transitions. Returns an unsubscribe function. |
|
|
187
|
+
| onPanelSelected | (id: string, appObjects: AppObjectRepo, callback: () => void) => () => void | Subscribes to whole-panel hitbox clicks. Returns an unsubscribe function. |
|
|
188
|
+
| onCheckPressure | (id: string, appObjects: AppObjectRepo, callback: () => void) => () => void | Subscribes to pressure-check requests (gauge click). Returns an unsubscribe function. |
|
|
189
|
+
|
|
190
|
+
> **Reactive view model:** subscribe via the facade's `onViewModel(cb)` — the
|
|
191
|
+
> canonical host-facing surface for VM updates. The raw `pneumaticPanelPMAdapter`
|
|
192
|
+
> export still exists for the framework/view boundary and advanced use, but is no
|
|
193
|
+
> longer the advertised consumer path (see Internal / Advanced).
|
|
194
|
+
|
|
195
|
+
### Accessors (read after creation)
|
|
196
|
+
|
|
197
|
+
Use these to read state or retrieve the view from an already-created instance.
|
|
198
|
+
|
|
199
|
+
| Accessor | Returns | Description |
|
|
200
|
+
| ------------------------------------- | -------------------------------------- | ------------------------------------------------------------- |
|
|
201
|
+
| PneumaticPanelBabylonView.get(appObj) | PneumaticPanelBabylonView \| undefined | Retrieve the Babylon view from an AppObject after creation. |
|
|
202
|
+
| view.rootTransformNode | TransformNode \| undefined | Root transform for positioning/parenting in scene. |
|
|
203
|
+
| view.shadowCasters | AbstractMesh[] | Meshes suitable for shadow registration. |
|
|
204
|
+
| view.nodesByObjectId | ReadonlyMap\<string, Node\> | Scene-node lookup keyed by lowercased glTF objectId metadata. |
|
|
205
|
+
| view.highlightGroupsByObjectId | ReadonlyMap\<string, AbstractMesh[]\> | Highlight groups keyed by semantic objectId. `panel_hitbox` maps to every visible panel mesh (so the Host can outline the whole panel when the invisible hitbox is the pointer target); other objectIds map to a one-element array. Populated after `load()`. |
|
|
206
|
+
|
|
207
|
+
### Internal / Advanced (do not call directly)
|
|
208
|
+
|
|
209
|
+
> **Warning:** These are exported for extensibility and testing only. They are created automatically by `PneumaticPanelFacade` / `createBabylonPneumaticPanel`. Calling them directly without first creating the full domain stack will produce a partially-initialized object whose behavior silently does not work.
|
|
210
|
+
|
|
211
|
+
| Export | Description |
|
|
212
|
+
| --------------------------------- | -------------------------------------------------------------- |
|
|
213
|
+
| pneumaticPanelPMAdapter | Raw PM adapter (`subscribe`/`unsubscribe`). Used by the framework/view boundary; prefer the facade's `onViewModel(cb)` for host code. |
|
|
214
|
+
| makePneumaticPanelSelectUC | Creates panel-select UC only. No entity or view. |
|
|
215
|
+
| makePneumaticPanelBabylonView | Creates the Babylon view only — no Entity/UC/PM. Testing only. |
|
|
216
|
+
| makePneumaticPanelEntity | Creates entity only. No UCs, PM, or view. |
|
|
217
|
+
| makePneumaticPanelRepo | Creates the entity repo singleton. |
|
|
218
|
+
| makePneumaticPanelPM | Creates PM only. No entity or UCs. |
|
|
219
|
+
| makePneumaticPanelToggleExhaustUC | Creates exhaust UC only. No entity or view. |
|
|
220
|
+
| makePneumaticPanelToggleLockUC | Creates lock UC only. No entity or view. |
|
|
221
|
+
| makePneumaticPanelCheckPressureUC | Creates pressure-check UC only. No entity or view. |
|
|
222
|
+
| panelHitboxClicked | Internal boundary controller — called by the Babylon view on hitbox pick. |
|
|
223
|
+
| MockPneumaticPanelPM | Mock PM for consumer integration tests. |
|
|
224
|
+
| MockPneumaticPanelToggleExhaustUC | Mock exhaust UC for consumer tests. |
|
|
225
|
+
| MockPneumaticPanelToggleLockUC | Mock lock UC for consumer tests. |
|
|
226
|
+
| MockPneumaticPanelCheckPressureUC | Mock pressure-check UC for consumer tests. |
|
|
227
|
+
| MockPneumaticPanelSelectUC | Mock panel-select UC for consumer tests. |
|
|
228
|
+
| componentConfig | Metadata constant (name, version, asset IDs). |
|
|
229
|
+
|
|
230
|
+
### Types
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
// SmartComponent structural contract the facade conforms to (Contract v1, per ADR-0006).
|
|
234
|
+
// Eight mandatory members, shape-identical across every VIVED smart component.
|
|
235
|
+
interface SmartComponent {
|
|
236
|
+
readonly id: string;
|
|
237
|
+
readonly interfaceVersion: number;
|
|
238
|
+
onEvent(event: string, cb: (...args: never[]) => void): () => void;
|
|
239
|
+
onViewModel(cb: (vm: PneumaticPanelVM) => void): () => void;
|
|
240
|
+
load(variant?: string): Promise<void>;
|
|
241
|
+
destroy(): void;
|
|
242
|
+
getState(): PneumaticPanelState;
|
|
243
|
+
applyState(state: PneumaticPanelState): void;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Typed event catalog for the facade's onEvent(...) method
|
|
247
|
+
type PneumaticPanelEvents = {
|
|
248
|
+
exhaustOpened: () => void;
|
|
249
|
+
exhaustClosed: () => void;
|
|
250
|
+
exhaustCloseBlockedByLock: () => void;
|
|
251
|
+
panelLocked: () => void;
|
|
252
|
+
panelUnlocked: () => void;
|
|
253
|
+
panelSelected: () => void;
|
|
254
|
+
pressureChecked: () => void;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// Versioned, persistable panel state snapshot.
|
|
258
|
+
// Version 2 (per ADR-0009) removed the `highlightedObject` field; older v1
|
|
259
|
+
// snapshots still restore cleanly via ADR-0008 best-effort apply (unknown
|
|
260
|
+
// fields ignored, absent fields defaulted).
|
|
261
|
+
const PNEUMATIC_PANEL_STATE_VERSION = 2;
|
|
262
|
+
|
|
263
|
+
type PneumaticPanelState = {
|
|
264
|
+
version: number;
|
|
265
|
+
exhaustOpen: boolean;
|
|
266
|
+
isLocked: boolean;
|
|
267
|
+
showGhostLockHint: boolean;
|
|
268
|
+
panelHitboxEnabled: boolean;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
interface PneumaticPanelVM {
|
|
272
|
+
exhaustOpen: boolean;
|
|
273
|
+
isLocked: boolean;
|
|
274
|
+
pressure: number;
|
|
275
|
+
showGhostLockHint: boolean;
|
|
276
|
+
panelHitboxEnabled: boolean;
|
|
277
|
+
subComponentsInteractive: boolean; // derived: always !panelHitboxEnabled
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
> **Note:** `PneumaticPanelState` does not include `pressure` — pressure is derived
|
|
282
|
+
> automatically from exhaust state on restore (0 when open, max when closed).
|
|
283
|
+
|
|
284
|
+
## 3D View
|
|
285
|
+
|
|
286
|
+
### Asset
|
|
287
|
+
|
|
288
|
+
| Name | Asset ID | File |
|
|
289
|
+
| ------- | ------------------------------------ | ------------------ |
|
|
290
|
+
| default | db90a1dd-8cb9-4f56-954d-7a72610281a7 | pneumaticPanel.glb |
|
|
291
|
+
|
|
292
|
+
**The published package ships no 3D assets.** The GLB is fetched at load time by asset id through `getAssetBlobURL`, so `dist/` contains code only. The `pneumaticPanel.glb` and `studio.env` files in the repo's `public/` directory exist for the dev playground and are deliberately excluded from the build (`publicDir: false` in `vite.config.ts`). A host does not need to serve or vendor either file.
|
|
293
|
+
|
|
294
|
+
### Exposed Transform Nodes
|
|
295
|
+
|
|
296
|
+
The facade does not expose scene nodes directly. To position, parent, or register shadow casters, retrieve the view from the AppObject (the facade's `id` matches the AppObject id):
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
import {
|
|
300
|
+
PneumaticPanelFacade,
|
|
301
|
+
PneumaticPanelBabylonView,
|
|
302
|
+
} from "@vived/component-pneumatic-panel";
|
|
303
|
+
|
|
304
|
+
const panel = new PneumaticPanelFacade("panel-1", appObjects);
|
|
305
|
+
await panel.load();
|
|
306
|
+
|
|
307
|
+
const appObject = appObjects.get(panel.id);
|
|
308
|
+
const view = appObject ? PneumaticPanelBabylonView.get(appObject) : undefined;
|
|
309
|
+
if (view?.rootTransformNode) {
|
|
310
|
+
view.rootTransformNode.position.set(1, 0, -2);
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Interactive objectId mapping
|
|
315
|
+
|
|
316
|
+
The GLB includes these objectId metadata values:
|
|
317
|
+
|
|
318
|
+
| objectId | Role |
|
|
319
|
+
| ------------ | ------------------------------------------------------------------------------------------------- |
|
|
320
|
+
| exhaust_knob | Click to toggle exhaust; rotates with exhaust state. |
|
|
321
|
+
| lock | Lock transform and child meshes; click to toggle lock state. |
|
|
322
|
+
| ghost_lock | Hover-reveal lock helper and click target for lock action. |
|
|
323
|
+
| gauge | Click to invoke pressure check. |
|
|
324
|
+
| panel_hitbox | Invisible full-panel bounding mesh; active only when `panelHitboxEnabled` is true. |
|
|
325
|
+
|
|
326
|
+
## Recipes
|
|
327
|
+
|
|
328
|
+
### Mounting / parenting into a host scene
|
|
329
|
+
|
|
330
|
+
The facade drives behavior but does not expose scene nodes. When you need the root transform
|
|
331
|
+
node (to parent the panel into a host scene) or shadow casters, use the
|
|
332
|
+
`createBabylonPneumaticPanel` composition entry point, which returns the `AppObject`:
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
import {
|
|
336
|
+
createBabylonPneumaticPanel,
|
|
337
|
+
PneumaticPanelBabylonView,
|
|
338
|
+
} from "@vived/component-pneumatic-panel";
|
|
339
|
+
|
|
340
|
+
const appObject = await createBabylonPneumaticPanel("panel-1", appObjects);
|
|
341
|
+
if (appObject) {
|
|
342
|
+
const view = PneumaticPanelBabylonView.get(appObject);
|
|
343
|
+
if (view?.rootTransformNode) {
|
|
344
|
+
// Parent to an existing scene node (e.g. a workbench or equipment mount)
|
|
345
|
+
view.rootTransformNode.parent = parentTransformNode;
|
|
346
|
+
view.rootTransformNode.position.set(0, 1.2, 0);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Register shadow casters with a ShadowGenerator
|
|
350
|
+
for (const mesh of view?.shadowCasters ?? []) {
|
|
351
|
+
shadowGenerator.addShadowCaster(mesh);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Snapshot and restore panel state
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
import { PneumaticPanelFacade } from "@vived/component-pneumatic-panel";
|
|
360
|
+
|
|
361
|
+
const panel = new PneumaticPanelFacade("panel-1", appObjects);
|
|
362
|
+
await panel.load();
|
|
363
|
+
|
|
364
|
+
// Capture a versioned snapshot (e.g. to persist learner progress)
|
|
365
|
+
const snapshot = panel.getState();
|
|
366
|
+
localStorage.setItem("panel-1-state", JSON.stringify(snapshot));
|
|
367
|
+
|
|
368
|
+
// Restore later — pressure is recomputed from exhaust state automatically
|
|
369
|
+
const saved = JSON.parse(localStorage.getItem("panel-1-state")!);
|
|
370
|
+
panel.applyState(saved);
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Training vs assessment lock hint mode
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
// Training mode: ghost lock is visible when eligible.
|
|
377
|
+
panel.setShowGhostLockHint(true);
|
|
378
|
+
|
|
379
|
+
// Assessment mode: ghost lock is invisible unless the Host reveals it.
|
|
380
|
+
panel.setShowGhostLockHint(false);
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
`showGhostLockHint` is authored domain state — the Activity deciding whether this
|
|
384
|
+
learner is shown where the lock goes. It is not hover state: with the hint off the
|
|
385
|
+
ghost lock stays enabled and pickable but invisible, and the Host reveals it on
|
|
386
|
+
hover if it wants to (ADR-0010). A Host that wires nothing shows no reveal.
|
|
387
|
+
|
|
388
|
+
### Host-owned highlighting (outline the panel from the hitbox)
|
|
389
|
+
|
|
390
|
+
The component is presentation-free for interaction state (ADR-0009): it no longer
|
|
391
|
+
renders its own hover outline or hint glow. Instead the Host reads
|
|
392
|
+
`view.highlightGroupsByObjectId` and drives its own highlight system. The
|
|
393
|
+
`panel_hitbox` entry maps to every visible panel mesh, so the Host can outline the
|
|
394
|
+
whole visible panel even though the pointer target is the invisible hitbox.
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
const appObject = appObjects.get(panel.id);
|
|
398
|
+
const view = appObject ? PneumaticPanelBabylonView.get(appObject) : undefined;
|
|
399
|
+
|
|
400
|
+
// Outline the whole panel when the invisible hitbox is hovered/selected.
|
|
401
|
+
const panelMeshes = view?.highlightGroupsByObjectId.get("panel_hitbox") ?? [];
|
|
402
|
+
for (const mesh of panelMeshes) {
|
|
403
|
+
hostHighlightManager.add(mesh);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Every other objectId is registered as a single-mesh group, so the same map is
|
|
407
|
+
// how the Host reaches the ghost lock to reveal it on hover (ADR-0010).
|
|
408
|
+
const [ghostLock] = view?.highlightGroupsByObjectId.get("ghost_lock") ?? [];
|
|
409
|
+
if (ghostLock) {
|
|
410
|
+
ghostLock.visibility = isHoveringGhostLock ? 1 : 0;
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Subscribe to events (facade)
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
import { PneumaticPanelFacade } from "@vived/component-pneumatic-panel";
|
|
418
|
+
|
|
419
|
+
const panel = new PneumaticPanelFacade("panel-1", appObjects);
|
|
420
|
+
await panel.load();
|
|
421
|
+
|
|
422
|
+
const unsubscribeOpened = panel.onEvent("exhaustOpened", () => {
|
|
423
|
+
console.log("exhaust opened");
|
|
424
|
+
});
|
|
425
|
+
const unsubscribeLocked = panel.onEvent("panelLocked", () => {
|
|
426
|
+
console.log("panel locked");
|
|
427
|
+
});
|
|
428
|
+
// The component renders no dialog (ADR-0010). Track the VM, then present the
|
|
429
|
+
// value however the Host wants. There is no getter — `onViewModel` emits the
|
|
430
|
+
// current VM immediately on subscribe and on every change.
|
|
431
|
+
let latestVM: PneumaticPanelVM | undefined;
|
|
432
|
+
const unsubscribeVM = panel.onViewModel((vm) => {
|
|
433
|
+
latestVM = vm;
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
const unsubscribePressure = panel.onEvent("pressureChecked", () => {
|
|
437
|
+
hostShowAlertUC.showAlert({
|
|
438
|
+
title: "Pneumatic Panel Pressure",
|
|
439
|
+
message: `${latestVM?.pressure ?? 0} PSI`,
|
|
440
|
+
closeButtonLabel: "OK",
|
|
441
|
+
closeCallback: () => {},
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// Later — clean up subscriptions
|
|
446
|
+
unsubscribeOpened();
|
|
447
|
+
unsubscribeLocked();
|
|
448
|
+
unsubscribePressure();
|
|
449
|
+
unsubscribeVM();
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Subscribe to VM state updates (facade)
|
|
453
|
+
|
|
454
|
+
Use the facade's `onViewModel` — it emits the current VM immediately, then on every
|
|
455
|
+
change, and returns an unsubscribe function. This is the canonical reactive surface
|
|
456
|
+
(it wraps `pneumaticPanelPMAdapter`, which remains available for the view boundary /
|
|
457
|
+
advanced use).
|
|
458
|
+
|
|
459
|
+
```typescript
|
|
460
|
+
import {
|
|
461
|
+
PneumaticPanelFacade,
|
|
462
|
+
type PneumaticPanelVM,
|
|
463
|
+
} from "@vived/component-pneumatic-panel";
|
|
464
|
+
|
|
465
|
+
const panel = new PneumaticPanelFacade("panel-1", appObjects);
|
|
466
|
+
await panel.load();
|
|
467
|
+
|
|
468
|
+
const unsubscribe = panel.onViewModel((vm: PneumaticPanelVM) => {
|
|
469
|
+
console.log(vm.exhaustOpen, vm.isLocked, vm.pressure, vm.panelHitboxEnabled);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// Later
|
|
473
|
+
unsubscribe();
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
In React, `onViewModel` returns the unsubscribe directly, so `useEffect` consumes it:
|
|
477
|
+
|
|
478
|
+
```tsx
|
|
479
|
+
useEffect(() => panel.onViewModel(setVm), [panel]);
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### Whole-panel selection mode
|
|
483
|
+
|
|
484
|
+
Enable the panel hitbox to treat the whole panel as one clickable target (e.g. for a "select a panel" interaction in an activity). Hover feedback is the Host's responsibility (ADR-0009) — the component emits only the `panelSelected` click event.
|
|
485
|
+
|
|
486
|
+
```typescript
|
|
487
|
+
import { PneumaticPanelFacade } from "@vived/component-pneumatic-panel";
|
|
488
|
+
|
|
489
|
+
const panel = new PneumaticPanelFacade("panel-1", appObjects);
|
|
490
|
+
await panel.load();
|
|
491
|
+
|
|
492
|
+
// Enable — panel becomes a single click target; sub-components deactivate
|
|
493
|
+
panel.enableHitbox();
|
|
494
|
+
|
|
495
|
+
const unsubscribe = panel.onEvent("panelSelected", () => {
|
|
496
|
+
console.log("panel was selected");
|
|
497
|
+
// Restore sub-component interactions when done
|
|
498
|
+
panel.disableHitbox();
|
|
499
|
+
unsubscribe();
|
|
500
|
+
});
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
### Creating multiple instances
|
|
504
|
+
|
|
505
|
+
```typescript
|
|
506
|
+
import { PneumaticPanelFacade } from "@vived/component-pneumatic-panel";
|
|
507
|
+
|
|
508
|
+
const panel1 = new PneumaticPanelFacade("panel-1", appObjects);
|
|
509
|
+
const panel2 = new PneumaticPanelFacade("panel-2", appObjects);
|
|
510
|
+
await Promise.all([panel1.load(), panel2.load()]);
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
## Common Mistakes
|
|
514
|
+
|
|
515
|
+
### Calling `makePneumaticPanelBabylonView` directly
|
|
516
|
+
|
|
517
|
+
❌ **Wrong** — creates the view only, with no Entity, Use Cases, or PM. Click interactions silently do nothing.
|
|
518
|
+
|
|
519
|
+
```typescript
|
|
520
|
+
import { makePneumaticPanelBabylonView } from "@vived/component-pneumatic-panel";
|
|
521
|
+
|
|
522
|
+
const appObject = appObjects.getOrCreate("panel-1");
|
|
523
|
+
const view = makePneumaticPanelBabylonView(appObject);
|
|
524
|
+
await view.load(); // Renders, but toggling exhaust/lock silently fails
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
✅ **Correct** — use the `PneumaticPanelFacade` (or `createBabylonPneumaticPanel`), which create the full domain stack AND the view.
|
|
528
|
+
|
|
529
|
+
```typescript
|
|
530
|
+
import { PneumaticPanelFacade } from "@vived/component-pneumatic-panel";
|
|
531
|
+
|
|
532
|
+
const panel = new PneumaticPanelFacade("panel-1", appObjects);
|
|
533
|
+
await panel.load();
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### Guessing an AppObject ID to grab an existing instance
|
|
537
|
+
|
|
538
|
+
❌ **Wrong** — constructing IDs by naming convention to retrieve a pre-existing AppObject skips the factory and results in a missing domain stack.
|
|
539
|
+
|
|
540
|
+
```typescript
|
|
541
|
+
const appObject = appObjects.get("panel-1");
|
|
542
|
+
const view = PneumaticPanelBabylonView.get(appObject!); // may be undefined or partial
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
✅ **Correct** — always create instances through the facade or `createBabylonPneumaticPanel`. After creation, the facade's `id` (or the returned AppObject) is the supported handle.
|
|
546
|
+
|
|
547
|
+
### Reaching into view internals instead of using accessors
|
|
548
|
+
|
|
549
|
+
❌ **Wrong** — accessing private meshes or nodes directly from sub-views.
|
|
550
|
+
|
|
551
|
+
✅ **Correct** — use `PneumaticPanelBabylonView.get(appObject)` to access the public view surface (`rootTransformNode`, `shadowCasters`, `nodesByObjectId`, `highlightGroupsByObjectId`).
|
|
552
|
+
|
|
553
|
+
## Constraints & Defaults
|
|
554
|
+
|
|
555
|
+
### Default state
|
|
556
|
+
|
|
557
|
+
| Property | Default |
|
|
558
|
+
| ----------------- | ------- |
|
|
559
|
+
| exhaustOpen | false |
|
|
560
|
+
| isLocked | false |
|
|
561
|
+
| pressure | 60 |
|
|
562
|
+
| showGhostLockHint | false |
|
|
563
|
+
| panelHitboxEnabled | false |
|
|
564
|
+
|
|
565
|
+
### Automatic behaviors
|
|
566
|
+
|
|
567
|
+
- **Pressure management**: Pressure is automatically set to 0 when exhaust opens and restored to max pressure (60) when exhaust closes. Developers do not need to manage pressure values manually — and `PneumaticPanelState` therefore omits `pressure`, recomputing it from exhaust state on restore.
|
|
568
|
+
- **Lock constraint**: The lock can only be engaged when the exhaust is open (lockout/tagout safety). Attempting to lock with exhaust closed returns false.
|
|
569
|
+
- **Exhaust constraint**: The exhaust cannot be toggled while locked. Attempting to toggle returns false.
|
|
570
|
+
- **Ghost lock visibility**: In hint mode (`showGhostLockHint: true`), the ghost lock mesh is visible whenever eligible (exhaust open, not locked). With the hint off it is enabled and pickable but invisible — the component detects no hover and reveals nothing (ADR-0010).
|
|
571
|
+
- **Panel hitbox / sub-component interactivity**: When `panelHitboxEnabled` is true, sub-component clicks (exhaust, lock, gauge) are disabled and the whole panel acts as a single target. `subComponentsInteractive` in the VM is always derived as `!panelHitboxEnabled`.
|
|
572
|
+
- **Presentation-free interaction state (ADR-0009, ADR-0010)**: The component does not render hover outlines, hint glows, selection feedback, or dialogs, and detects no hover. The Host owns all of it, reading `view.highlightGroupsByObjectId` to map semantic objectIds to meshes.
|
|
573
|
+
|
|
574
|
+
### Known limitations
|
|
575
|
+
|
|
576
|
+
- `checkPressure` displays nothing. A Host that does not subscribe to `pressureChecked` gives the learner no feedback when the gauge is clicked.
|
|
577
|
+
- The ghost lock does not reveal on hover. A Host that does not drive `ghost_lock` visibility gives the learner no hint affordance in non-hint mode.
|
|
578
|
+
- The GLB asset must include `objectId` metadata on the expected nodes (`exhaust_knob`, `lock`, `ghost_lock`, `gauge`, `panel_hitbox`) for click interactions and highlight grouping to work.
|
|
579
|
+
- `PneumaticPanelState` is versioned (`PNEUMATIC_PANEL_STATE_VERSION = 2`). Snapshots are restored best-effort (ADR-0008): unknown fields are ignored and absent fields default, so older v1 snapshots (which carried `highlightedObject`) still apply cleanly.
|
|
580
|
+
|
|
581
|
+
## Repository
|
|
582
|
+
|
|
583
|
+
| Field | Value |
|
|
584
|
+
| -------------- | --------------------------------------- |
|
|
585
|
+
| Package | @vived/component-pneumatic-panel |
|
|
586
|
+
| GitHub | vivedlearning/component-pneumatic-panel |
|
|
587
|
+
| Version | 3.0.0 |
|
|
588
|
+
| Interface version | 1 |
|
|
589
|
+
| State schema version | 2 |
|
|
590
|
+
| Multi-instance | Yes |
|
|
591
|
+
|
|
592
|
+
### Full export list
|
|
593
|
+
|
|
594
|
+
#### Public API (use these)
|
|
595
|
+
|
|
596
|
+
- `PneumaticPanelFacade` — **primary host-facing surface** (class; drives commands, events, state, lifecycle)
|
|
597
|
+
- `createBabylonPneumaticPanel` — composition entry point (full domain + view; returns AppObject)
|
|
598
|
+
- `makePneumaticPanelFeatureFactory` — factory registration
|
|
599
|
+
- `tryToggleExhaust` — controller
|
|
600
|
+
- `tryToggleLock` — controller
|
|
601
|
+
- `checkPressure` — controller
|
|
602
|
+
- `setShowGhostLockHint` — controller
|
|
603
|
+
- `enablePanelHitbox` — controller
|
|
604
|
+
- `disablePanelHitbox` — controller
|
|
605
|
+
- `getPanelState` — state snapshot controller (facade: `getState()`)
|
|
606
|
+
- `applyPanelState` — state restore controller (facade: `applyState()`)
|
|
607
|
+
- `onExhaustOpened` — event subscription controller
|
|
608
|
+
- `onExhaustClosed` — event subscription controller
|
|
609
|
+
- `onExhaustCloseBlockedByLock` — event subscription controller
|
|
610
|
+
- `onPanelLocked` — event subscription controller
|
|
611
|
+
- `onPanelUnlocked` — event subscription controller
|
|
612
|
+
- `onPanelSelected` — event subscription controller
|
|
613
|
+
- `onCheckPressure` — event subscription controller
|
|
614
|
+
- `PNEUMATIC_PANEL_STATE_VERSION` — state version constant
|
|
615
|
+
|
|
616
|
+
#### Accessors (read after creation)
|
|
617
|
+
|
|
618
|
+
- `PneumaticPanelBabylonView` (class with static `.get()`) — retrieve the view from an AppObject
|
|
619
|
+
- `PneumaticPanelVM` (type) — view model interface
|
|
620
|
+
- `PneumaticPanelState` (type) — versioned state snapshot interface
|
|
621
|
+
- `SmartComponent` (type) — structural facade contract
|
|
622
|
+
- `PneumaticPanelEvents` (type) — facade event catalog
|
|
623
|
+
|
|
624
|
+
#### Internal / Advanced (extensibility/testing only — not for normal use)
|
|
625
|
+
|
|
626
|
+
- `PneumaticPanelEntity`, `makePneumaticPanelEntity`
|
|
627
|
+
- `PneumaticPanelRepo`, `makePneumaticPanelRepo`, `PneumaticPanelEntityFactory` (type)
|
|
628
|
+
- `PneumaticPanelToggleExhaustUC`, `makePneumaticPanelToggleExhaustUC`
|
|
629
|
+
- `PneumaticPanelToggleLockUC`, `makePneumaticPanelToggleLockUC`
|
|
630
|
+
- `PneumaticPanelCheckPressureUC`, `makePneumaticPanelCheckPressureUC`
|
|
631
|
+
- `PneumaticPanelSelectUC`, `makePneumaticPanelSelectUC`
|
|
632
|
+
- `PneumaticPanelPM`, `makePneumaticPanelPM`
|
|
633
|
+
- `PneumaticPanelFeatureFactory` (class)
|
|
634
|
+
- `makePneumaticPanelBabylonView` — creates view only, no domain stack
|
|
635
|
+
- `pneumaticPanelPMAdapter` — raw PM adapter; used by the view boundary. Host code should prefer the facade's `onViewModel(cb)`.
|
|
636
|
+
- `panelHitboxClicked` — internal boundary controller (called by Babylon view on hitbox pick)
|
|
637
|
+
- `MockPneumaticPanelPM` — mock for consumer tests
|
|
638
|
+
- `MockPneumaticPanelToggleExhaustUC` — mock for consumer tests
|
|
639
|
+
- `MockPneumaticPanelToggleLockUC` — mock for consumer tests
|
|
640
|
+
- `MockPneumaticPanelCheckPressureUC` — mock for consumer tests
|
|
641
|
+
- `MockPneumaticPanelSelectUC` — mock for consumer tests
|
|
642
|
+
- `componentConfig` — metadata constant
|
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Copyright © 2026 VIVED Learning. All rights reserved.
|
|
2
|
+
|
|
3
|
+
This software and its associated documentation (the "Software") are the
|
|
4
|
+
proprietary and confidential property of VIVED Learning. The Software is
|
|
5
|
+
intended for internal use with VIVED Learning's player and infrastructure only.
|
|
6
|
+
|
|
7
|
+
No license, right, or permission is granted to any party to use, copy, modify,
|
|
8
|
+
merge, publish, distribute, sublicense, or sell the Software, in whole or in
|
|
9
|
+
part, except as expressly authorized in writing by VIVED Learning. Publication
|
|
10
|
+
of this package to a public registry does not constitute such authorization or
|
|
11
|
+
grant any license to it.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
15
|
+
FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
|