This is a yarn classic + vite + ts + react 19 + tailwindcss v4 + openmct project
Unlike a regular react project, the page root is controlled by openmct, and react is used to render plugins inside openmct. There is typically one react tree per displayed plugin.
Check if the dev server is running with ps aux | grep "vite", if not, start it with yarn dev.
OpenMCT source lives in vendor/openmct/ (git submodule). The full documentation is at vendor/openmct/API.md.
NICK uses yarn classic, but openmct uses npm. They are independent projects with separate dependency trees. Never use yarn inside vendor/openmct/, and never use npm at the nick root.
IMPORTANT: If you encounter a type error on an OpenMCT API object, investigate whether the OpenMCT type definition is wrong first, and fix the type definition in vendor/openmct/ rather than working around it in NICK code. The JSDoc comments in the OpenMCT source are used to generate TypeScript declarations.
- Run
yarn install --force— this rebuilds openmct (via thepreinstallhook) and re-copies it intonode_modules/openmct. - Clear vite dependency cache with
rm -rf node_modules/.vite/deps - Restart the dev server with
yarn dev
Note: yarn install automatically runs npm install inside vendor/openmct via the preinstall script, which triggers openmct's prepare script (build:prod + tsc). So a fresh yarn install always builds openmct from source.
When the user says to commit or push, the user means both the submodule and the parent git project.
The parent repo stores a pointer to a specific openmct commit, not the files themselves.
- Only if the user want to commit and push: run
yarn tsc --noEmit,yarn test,yarn format:checkand ensure no errors. - Commit and push inside the submodule first:
cd vendor/openmct git add -A && git commit -m "description of change" git push origin # if user wants to push - Then update the parent repo to point to the new commit:
cd ../.. git add vendor/openmct git commit -m "update openmct submodule" git push # if user wants to push - Always push the submodule before pushing the parent repo, otherwise CI will reference a commit that doesn't exist on the remote.
- Create
src/sources/<name>.tsimplementingDataSource(allKeys()+subscribe()). - In
src/main.ts, callregisterDataSource(new MySource())(guard withgetBackendType()if needed).
- Refer to
src/layout/avionics.tsfor an example. - In
src/main.ts, callopenmct.install(MyLayoutPlugin)under the// register layouts herecomment.
Datum.timestampMs is a single timestamp. If a sensor value carries both a
measured timestamp (e.g. GPS fix time, which may be unavailable) and a
received timestamp (wall-clock time the data arrived, always present), model
them as two separate keys:
- when measured timestamp is avaliable, emit one key with timestampMs = measured timestamp
- when measured timestamp is unavaliable, emit another key with timestampMs = received timestamp
Then, in the layout, create an overlay plot with both keys.
- Create
src/plugins/<name>.ts. Export a plugin functionMyPlugin(openmct: any). - In
src/main.ts, callopenmct.install(MyPlugin).
- Prefer readability over performance: dumb code over clever solutions (e.g. for loop > reduce)
- offensive programming: aggressively use types to convey contracts
- assert non-null if all code path supports it (with comment explaining why), instead of adding if else.
- No
any: always use proper types - No vague object types:
Record<string, unknown>,object, etc. are forbidden; define explicit types or derive. - Single source of truth: derive types from generated types or existing interfaces using
Pick,Omitetc; reuse SDK types even if they have extra fields; useReturnType,Awaited,Parameters, etc when types aren't exported - No barrel files: import directly from concrete module files, not re-export
index.tsfiles - Document public APIs: JSDoc on all public methods/constructors with
@param/@returns; keep docs in sync when modifying - Refactor call sites: when changing a function signature, update all call sites instead of adding optional parameters
- Avoid hardcoded strings: export and reuse constants if possible
IMPORTANT: Before you consider a task complete, always: (unless the task is trivial)
- Simplify: Re-examine all the files you just edited, think about all the related existing logic.
- Collapse single-use abstractions
- Align types with actual usage
- Aggressively factor out duplicate logic between new code and existing code
- Any other simplification you see appropriate
- Lint: Run
yarn tsc --noEmitandyarn lintand ensure no type or lint errors. - Test: Run
yarn testand ensure all tests pass - Format: Run
yarn formatat repo root.