@intenda/opus-ui-app-graph
v0.1.0
Published
Compiles the packaged Opus UI app.json into a connection graph of the JSON dashboard layer — trait composition and scoped-state wiring — with a CLI for composition, impact, orphan, and broken-reference queries.
Readme
app-graph
A connection graph for the JSON dashboard layer of this Opus UI app. It answers "how are these dashboards/traits wired together" without reading the source files by hand.
Why it's built on legoz/public/app.json
app.json is the packaged artifact the Opus UI packager produces from legoz/app +
every ensemble. Two properties make it the ideal source:
- Trait paths are already resolved. A source ref like
./../tasksis rewritten to its canonical@l2_dashboards/home/tasksform. No path-resolution guesswork. - It mirrors the file tree (
dashboard → @ensemble → folder → index.json), so every node maps back to a real source file, andtrait/scope/flows/scpssurvive.
What it extracts
- Trait graph — file A composes file B (forward + reverse edges).
- State wiring, keyed by
scope.relId.key(relId omitted = scope owner):- writes —
setState/setMultiState(explicittargetor, when absent, self) and a flow'sto/toKey(or self). - reads — a flow's
from/fromKey(or self), scp triggers (source/sourceList+key), and inline state accessors in any string (state.||scope.relId||.key,state.self.key,state.app.key). - "self" with no enclosing declared scope (a mounted fragment) is recorded under the
selfscope, since its real scope is determined where it's composed.
- writes —
- Health — orphans (never composed), dangling static refs (target missing), and
dynamic refs (
%prop%,((accessor)),{theme..}— runtime-supplied, not broken).
Install & invoke
Published as @intenda/opus-ui-app-graph, exposing the opus-ui-app-graph bin:
npm i -D @intenda/opus-ui-app-graph # or file:../tools/app-graph for local dev
npx opus-ui-app-graph build
npx opus-ui-app-graph tree home/indexOr run the source directly (no install), from anywhere in the workspace:
node tools/app-graph/cli.cjs build
node tools/app-graph/cli.cjs tree home/indexPath resolution
All paths resolve against the consuming project, with overrides:
| input | flag | env | default |
| --- | --- | --- | --- |
| app.json | --app <path> | OPUS_APP_JSON | cwd's opusPackagerConfig (public/app.json); auto-discovered from a child project if cwd has none |
| out dir | --out <dir> | OPUS_APP_GRAPH_OUT | <project>/app-graph-out (next to app.json, regardless of cwd) |
So node tools/app-graph/cli.cjs build works from the repo root and from legoz/ —
both resolve legoz/public/app.json and write legoz/app-graph-out/.
Build
node tools/app-graph/cli.cjs build # or: npx opus-ui-app-graph buildOutputs <project>/app-graph-out/app-graph.json (machine) and REPORT.md (summary).
Pure JSON/AST parse — no API cost, ~0.5s.
Automatic rebuild (wired into the dev loop)
legoz's start, start-test, start-from-workspace, and start-from-workspace-test
scripts run appgraph:watch alongside the packager and Vite via concurrently. It uses
nodemon --watch public/app.json so the graph recompiles on every packager rebuild.
Requires a one-time npm i in legoz (declared there as a file: devDependency until
published). Standalone: cd legoz && npm run appgraph:watch.
If app.json doesn't exist yet, build.cjs no-ops with a warning; nodemon picks it up
once the packager writes it.
Query
node tools/app-graph/cli.cjs <command> [args] # or: npx opus-ui-app-graph <command>| command | answers |
| --- | --- |
| tree <id> [--depth N] | what this file composes via traits (recursive) |
| who <id> [--deep] | who composes this file (direct, or all ancestors) — orphan / impact check |
| node <id> | one-node summary: source, scopes, traits, writes, reads |
| writers <scope.key> | files that write this scoped state key |
| readers <scope.key> | files that read / subscribe to it |
| orphans [prefix] | nodes never composed by anyone (dead-code candidates) |
| dangling | static trait refs whose target is missing |
| path <a> <b> | shortest trait path a → b |
| find <substr> | node ids containing substr |
| stats | totals |
Ids may be abbreviated — home/index resolves to @l2_dashboards/home/index; the CLI
reports candidates when ambiguous.
Examples
node tools/app-graph/cli.cjs tree home/index --depth 2
node tools/app-graph/cli.cjs who home/pieArea # -> orphan = safe to delete
node tools/app-graph/cli.cjs writers home.filter
node tools/app-graph/cli.cjs readers home.filterKnown limitations
- Trait-prop-driven state keys aren't resolved. A card whose action key is
%setGridFilterKey%showshome.%setGridFilterKey%, nothome.gridFilterPubLog(the concrete value lives in the instance'straitPrps, which this graph does not yet inline).writers/readersare exact for literal scoped keys (e.g.home.filter). - Reads cover
state.…accessors, flows, and triggers — notflow.…/variable.…accessors. Edges are trait composition + state wiring only; gateway actions and theme tokens are not modeled. - A self reference inside a mounted fragment (no declared scope in that file) is recorded
under the
selfscope rather than the scope it ends up in at runtime. - Generated from packaged output: a stale
app.jsonyields a stale graph. The watcher keeps it current duringnpm run start*.
