Warning, /firebird/docs/command-bus.md is written in an unsupported language. File is not indexed.
0001 # Command Bus
0002
0003 Firebird actions that arrive from outside the UI — URL deep links, server
0004 startup configuration, batch scripts — all travel as **commands**: small
0005 serializable objects with a `type` and arguments. One dispatcher executes them;
0006 handlers are registered per command type. This page is for developers who want
0007 to script the display or add their own commands.
0008
0009 For the end-user view of URL parameters, see
0010 [Deep Links](/deep-links).
0011
0012 The bus, the startup queue and the URL parsing come from the generic
0013 [`@dexvis/app-features`](https://github.com/dexvis/app-features-ng) package;
0014 `@dexvis/firebird-ng` re-exports what extension authors use, so the imports on
0015 this page stay `@dexvis/firebird-ng`.
0016
0017 ## Command anatomy
0018
0019 ```ts
0020 interface FbCommand {
0021 type: string; // selects the handler, e.g. 'open-dex'
0022 source?: 'url' | 'server' | 'batch' | 'ui' | 'code';
0023 [key: string]: unknown; // handler-specific arguments
0024 }
0025 ```
0026
0027 `FbCommand` is the same type as `AppCommand` from `@dexvis/app-features`;
0028 use either name.
0029
0030 The same command can be written as a URL-grammar string: `type:argument`.
0031 Multiple commands join with `;`. The argument is everything after the FIRST
0032 colon, so URLs with colons survive: `open-dex:https://host/file.zip`.
0033
0034 ## Built-in commands
0035
0036 | Type | Arguments | URL grammar | Action |
0037 |-----------------|----------------------|--------------------------------|--------|
0038 | `open-geometry` | `url` | `open-geometry:<url>` | Load detector geometry. The loader is chosen from the registered geometry loaders by file extension / URL scheme. |
0039 | `open-dex` | `url` | `open-dex:<url>` | Load event data. DEX json/zip and `.root` (server conversion) are chosen the same registry-driven way. |
0040 | `show-event` | `index` (number) | `show-event:2` | Select event by index. Waits up to 30 s for events to finish loading first. |
0041 | `set-config` | `key`, `value` | `set-config:key=value` | Set a configuration value. From `url`/`server`/`batch` sources the value is session-scoped (not persisted); from `ui`/`code` it is saved normally, converted to the key's type (`'7'` becomes `7` for a numeric key). A key that no code declared yet keeps the value until the declaration arrives, which then sets the key's default and type. |
0042 | `camera-preset` | `name` | `camera-preset:top` | Move the camera to a named preset (`withCameraPreset()`). Face views `front`, `back`, `left`, `right`, `top`, `bottom` keep the current orbit target and distance; `home` is the start view (the top view framing the geometry, or the view a pack pinned). The flagship's ePIC pack adds the fixed poses `center` and `farforward`. |
0043 | `animate-collision` | none | `animate-collision` | Play the collision intro (`withCollisionIntro()`), then the event's time animation from the start. Without an intro, the time animation starts at once. |
0044
0045 The URL shorthands `?dex=`, `?geometry=`, `?event=` expand to `open-dex`,
0046 `open-geometry`, `show-event`: `?dex=X` queues the same command as
0047 `?cmd=open-dex:X`, built by the handler's `fromUrlArg()`. Unlike a `?cmd=`
0048 item, a shorthand value may contain `;`.
0049
0050 Face presets follow the accelerator (HENP) axis convention: the beam runs
0051 along Z (pointing right on screen in the front view), Y is up, and X points
0052 toward the accelerator center. The top and bottom views keep Z horizontal on
0053 screen. The same views are reachable interactively through the navigation
0054 cube in the display's top-right corner.
0055
0056 ## Where commands come from
0057
0058 Commands queue at application startup and run once the 3D scene is
0059 initialized, in this order:
0060
0061 1. **Server**: the `startupCommands` array in `config.jsonc` (or injected by
0062 pyrobird — see below). Entries are command objects or grammar strings.
0063 2. **URL**: the shorthands and `?cmd=...` from the page address.
0064
0065 Server commands queue first, so a URL deep link can follow up on or override
0066 what the server started. Execution is sequential — each command awaits the
0067 previous one, which is why `show-event` after `open-dex` works reliably.
0068 When startup commands carry data-loading commands, the corresponding
0069 config-driven auto-load is skipped (no double loading).
0070
0071 ### Server configuration
0072
0073 ```jsonc
0074 // config.jsonc
0075 {
0076 "startupCommands": [
0077 "open-dex:mydata.firebird.zip;show-event:2",
0078 { "type": "camera-preset", "name": "farforward" }
0079 ]
0080 }
0081 ```
0082
0083 With pyrobird, pass commands from the CLI instead of editing files:
0084
0085 ```bash
0086 pyrobird serve --startup-commands "open-dex:mydata.firebird.zip;show-event:2"
0087 pyrobird screenshot --commands "open-dex:mydata.firebird.zip;show-event:2;camera-preset:farforward"
0088 ```
0089
0090 ## Readiness flags for batch tools
0091
0092 The display publishes its state to `window.firebird` so headless drivers know
0093 when it is safe to capture:
0094
0095 ```js
0096 window.firebird = {
0097 geometryReady: true, // geometry finished loading (or none pending)
0098 startupCommandsDone: true, // the startup queue ran to completion
0099 pendingLoads: 0, // in-flight geometry/event loads
0100 ready: true, // all of the above - safe to screenshot
0101 errors: [], // messages of failed loads and startup commands
0102 }
0103 ```
0104
0105 A load counts from the moment it is requested, including a ROOT file that the
0106 browser converts first. A load that a newer one replaced stops counting. A
0107 load that fails still settles, so `ready` turns true; its message lands in
0108 `errors` and on screen. `errors` also lists every startup command that threw.
0109
0110 `pyrobird screenshot` waits for `ready === true` or the first error, saves
0111 the screenshot either way, and reports the outcome in its exit code: 0 ready
0112 without errors, 1 server, browser or capture failure, 2 never ready within
0113 `--ready-timeout`, 3 non-empty `errors`. Custom Playwright/Puppeteer scripts
0114 should wait the same way and check `errors`:
0115
0116 ```js
0117 await page.waitForFunction("window.firebird && window.firebird.ready === true");
0118 const errors = await page.evaluate(() => window.firebird.errors);
0119 ```
0120
0121 The object exists only on the display route, after the display page
0122 initializes.
0123
0124 ## Adding your own command
0125
0126 A command handler is a class registered through the extension system (see
0127 [Extension System](/extensions)):
0128
0129 ```ts
0130 import { Injectable } from '@angular/core';
0131 import { CommandHandler, FbCommand } from '@dexvis/firebird-ng';
0132
0133 @Injectable()
0134 export class FocusDetectorCommandHandler implements CommandHandler {
0135 readonly type = 'focus-detector';
0136
0137 // optional: how '?cmd=focus-detector:DIRC' maps to the command object
0138 fromUrlArg(arg: string): FbCommand {
0139 return { type: this.type, detectorName: arg };
0140 }
0141
0142 async execute(command: FbCommand): Promise<void> {
0143 const name = command['detectorName'] as string;
0144 // ... find the detector node, move the camera
0145 }
0146 }
0147 ```
0148
0149 ```ts
0150 // app.config.ts
0151 provideFirebird(
0152 withCommandHandler(FocusDetectorCommandHandler),
0153 )
0154 ```
0155
0156 The handler class is instantiated through dependency injection, so `inject()`
0157 works in it. One handler per type; registering an existing type replaces the
0158 built-in — that is allowed and deliberate. After registration the command
0159 works from every source: `?cmd=focus-detector:DIRC` in a link,
0160 `startupCommands` on a server, `--commands` in batch.
0161
0162 Not implemented on purpose (yet): undo/rewind and command journaling. The
0163 `source` field exists so a journal can be added later without changing
0164 dispatch call sites.