{"_id":"@alexguha/rplib","_rev":"4-605a69e43964616c4d33d05e3df9a5fe","name":"@alexguha/rplib","dist-tags":{"latest":"0.3.0"},"versions":{"0.1.0":{"name":"@alexguha/rplib","version":"0.1.0","keywords":["diagram","svg","d3","visualization","dsl","relational-positioning"],"license":"ISC","_id":"@alexguha/rplib@0.1.0","maintainers":[{"name":"alexguha","email":"alexandersguha@gmail.com"}],"homepage":"https://github.com/Alex-Guha/rplib#readme","bugs":{"url":"https://github.com/Alex-Guha/rplib/issues"},"dist":{"shasum":"1afa66ce68f01fbee589bd09cb83b56041d09855","tarball":"https://registry.npmjs.org/@alexguha/rplib/-/rplib-0.1.0.tgz","fileCount":43,"integrity":"sha512-SFA5dpUq0FZN3hBCWwGQpNPqqe/ZyBanRtDG3WWcN8veaPHLCA1/jVbuqXVoc09FzpVSCC6menoeHkcSWZcm0g==","signatures":[{"sig":"MEQCIFRviJ9tWL20SlAahhjk0jtz9GhSMPnmzk+/72Ix1QsOAiBVO3fEH7I4hxPcBSM3xYqTDlFe00uPsh5DDqOWPiNBxA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":138691},"main":"index.js","type":"module","types":"types/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./types/index.d.ts","default":"./index.js"},"./layout":{"types":"./types/layout.d.ts","default":"./layout.js"},"./mutate":{"types":"./types/mutate.d.ts","default":"./mutate.js"},"./parser":{"types":"./types/parser/index.d.ts","default":"./parser/index.js"},"./defaults":{"types":"./types/defaults.d.ts","default":"./defaults.js"},"./renderView":{"types":"./types/renderView.d.ts","default":"./renderView.js"}},"gitHead":"649560143b4ef431e8d7dd7875cc7b5696e58def","scripts":{"test":"node --test","build:types":"tsc","prepublishOnly":"npm test && npm run build:types"},"_npmUser":{"name":"alexguha","email":"alexandersguha@gmail.com"},"repository":{"url":"git+https://github.com/Alex-Guha/rplib.git","type":"git","directory":"core"},"_npmVersion":"11.11.1","description":"Relational-positioning diagram library: anchor-based DSL with recursive composition and in-place component swap.","directories":{},"_nodeVersion":"25.8.2","_hasShrinkwrap":false,"devDependencies":{"d3":"^7.0.0","katex":"^0.16.0","typescript":"^5.4.0"},"peerDependencies":{"d3":"^7.0.0","katex":"^0.16.0"},"_npmOperationalInternal":{"tmp":"tmp/rplib_0.1.0_1779992335624_0.17170356751251648","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@alexguha/rplib","version":"0.1.1","keywords":["diagram","svg","d3","visualization","dsl","relational-positioning"],"license":"ISC","_id":"@alexguha/rplib@0.1.1","maintainers":[{"name":"alexguha","email":"alexandersguha@gmail.com"}],"homepage":"https://github.com/Alex-Guha/rplib#readme","bugs":{"url":"https://github.com/Alex-Guha/rplib/issues"},"dist":{"shasum":"6a400b1bcaee169988fe6a840a4e09ee4652e9fb","tarball":"https://registry.npmjs.org/@alexguha/rplib/-/rplib-0.1.1.tgz","fileCount":43,"integrity":"sha512-FJGFvDjQR7N2755kAGQavm2VJFOH0SPgBRGtUNEWhXunWegroVXY2/9rurYc5yLt7SSCjUlmJwae4T7SFhEEWw==","signatures":[{"sig":"MEUCIFMHqgGMI07fOoF9QDYj2fHNBRqCx1rvTPNEpfg6pY9nAiEAoH4ms2EuYnVRzh8f8dh6WgZwd0ulWdNFOGbStWyN0+8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":140308},"main":"index.js","type":"module","types":"types/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./types/index.d.ts","default":"./index.js"},"./layout":{"types":"./types/layout.d.ts","default":"./layout.js"},"./mutate":{"types":"./types/mutate.d.ts","default":"./mutate.js"},"./parser":{"types":"./types/parser/index.d.ts","default":"./parser/index.js"},"./defaults":{"types":"./types/defaults.d.ts","default":"./defaults.js"},"./renderView":{"types":"./types/renderView.d.ts","default":"./renderView.js"}},"gitHead":"d4a23729c4a075fb70a98a61ef1fefaa363f2d56","scripts":{"test":"node --test","build:types":"tsc","prepublishOnly":"npm test && npm run build:types"},"_npmUser":{"name":"alexguha","email":"alexandersguha@gmail.com"},"repository":{"url":"git+https://github.com/Alex-Guha/rplib.git","type":"git","directory":"core"},"_npmVersion":"10.8.2","description":"Relational-positioning diagram library: anchor-based DSL with recursive composition and in-place component swap.","directories":{},"_nodeVersion":"18.20.8","_hasShrinkwrap":false,"devDependencies":{"d3":"^7.0.0","katex":"^0.16.0","typescript":"^5.4.0"},"peerDependencies":{"d3":"^7.0.0","katex":"^0.16.0"},"_npmOperationalInternal":{"tmp":"tmp/rplib_0.1.1_1779995760891_0.24801981824330932","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@alexguha/rplib","version":"0.2.0","keywords":["diagram","svg","d3","visualization","dsl","relational-positioning"],"license":"ISC","_id":"@alexguha/rplib@0.2.0","maintainers":[{"name":"alexguha","email":"alexandersguha@gmail.com"}],"homepage":"https://github.com/Alex-Guha/rplib#readme","bugs":{"url":"https://github.com/Alex-Guha/rplib/issues"},"bin":{"rplib-build-data":"bin/build-data-manifest.js"},"dist":{"shasum":"9d0aab7a8a19376915d805a457d2bf21dac10268","tarball":"https://registry.npmjs.org/@alexguha/rplib/-/rplib-0.2.0.tgz","fileCount":49,"integrity":"sha512-az90GdmYZOtuELRlVuvGRNgioudp3xqTn/WC7s+Wv5XtvoCofyM6jWbdj+WkX632GbN8QOaeEORtSOy/eiExzQ==","signatures":[{"sig":"MEQCIH/as0JK4XXOzx2FUc38SjWUcP3YW50tW4ocsnBXbkMnAiAcERFcydCnQ1fcJ670rOGQVG8peYR0yc9CH1DG03aw/Q==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":162113},"main":"index.js","type":"module","types":"types/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./types/index.d.ts","default":"./index.js"},"./layout":{"types":"./types/layout.d.ts","default":"./layout.js"},"./mutate":{"types":"./types/mutate.d.ts","default":"./mutate.js"},"./parser":{"types":"./types/parser/index.d.ts","default":"./parser/index.js"},"./defaults":{"types":"./types/defaults.d.ts","default":"./defaults.js"},"./renderView":{"types":"./types/renderView.d.ts","default":"./renderView.js"}},"gitHead":"ee63f535efad9995192b16e65f5e34ff4e139224","scripts":{"test":"node --test","build:types":"tsc","prepublishOnly":"npm test && npm run build:types"},"_npmUser":{"name":"alexguha","email":"alexandersguha@gmail.com"},"repository":{"url":"git+https://github.com/Alex-Guha/rplib.git","type":"git","directory":"core"},"_npmVersion":"10.8.2","description":"Relational-positioning diagram library: anchor-based DSL with recursive composition and in-place component swap.","directories":{},"_nodeVersion":"18.20.8","_hasShrinkwrap":false,"devDependencies":{"d3":"^7.0.0","katex":"^0.16.0","typescript":"^5.4.0"},"peerDependencies":{"d3":"^7.0.0","katex":"^0.16.0"},"_npmOperationalInternal":{"tmp":"tmp/rplib_0.2.0_1780869570779_0.32077064540929356","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@alexguha/rplib","version":"0.3.0","description":"Relational-positioning diagram library: anchor-based DSL with recursive composition and in-place component swap.","type":"module","main":"index.js","types":"types/index.d.ts","bin":{"rplib-build-data":"bin/build-data-manifest.js"},"scripts":{"test":"node --test","build:types":"tsc","prepublishOnly":"npm test && npm run build:types"},"exports":{".":{"types":"./types/index.d.ts","default":"./index.js"},"./defaults":{"types":"./types/defaults.d.ts","default":"./defaults.js"},"./layout":{"types":"./types/layout.d.ts","default":"./layout.js"},"./mutate":{"types":"./types/mutate.d.ts","default":"./mutate.js"},"./panel":{"types":"./types/panel.d.ts","default":"./panel.js"},"./renderView":{"types":"./types/renderView.d.ts","default":"./renderView.js"},"./parser":{"types":"./types/parser/index.d.ts","default":"./parser/index.js"}},"keywords":["diagram","svg","d3","visualization","dsl","relational-positioning"],"repository":{"type":"git","url":"git+https://github.com/Alex-Guha/rplib.git","directory":"core"},"homepage":"https://github.com/Alex-Guha/rplib#readme","bugs":{"url":"https://github.com/Alex-Guha/rplib/issues"},"engines":{"node":">=18"},"peerDependencies":{"d3":"^7.0.0","katex":"^0.16.0"},"devDependencies":{"d3":"^7.0.0","katex":"^0.16.0","typescript":"^5.4.0"},"license":"ISC","gitHead":"3a15d9c28c2d74c5ce41012b5da76dfc567b503e","_id":"@alexguha/rplib@0.3.0","_nodeVersion":"25.8.2","_npmVersion":"11.11.1","dist":{"integrity":"sha512-ATp7TrDLPCB3fpme3LYXif5OKGxb00wkGDy6Dl/QYobRHVgeottyY8L2uHik/APBjV4kJiHpg0EnzAfVPxoEUA==","shasum":"6876cdcc07aceb370f028f23932b630ebc1b3432","tarball":"https://registry.npmjs.org/@alexguha/rplib/-/rplib-0.3.0.tgz","fileCount":51,"unpackedSize":182768,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCICve15Z0Hn7wc/I0PORdxuDFVKxdhA33Z23D++Hazu75AiEA7gJq+TuW4AGx6EnAs+UMrlLcAgyOcJhp7vvFX3nVqW0="}]},"_npmUser":{"name":"alexguha","email":"alexandersguha@gmail.com"},"directories":{},"maintainers":[{"name":"alexguha","email":"alexandersguha@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/rplib_0.3.0_1780963120792_0.1692809938543247"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-28T18:18:55.470Z","modified":"2026-06-08T23:58:41.070Z","0.1.0":"2026-05-28T18:18:55.760Z","0.1.1":"2026-05-28T19:16:01.026Z","0.2.0":"2026-06-07T21:59:30.924Z","0.3.0":"2026-06-08T23:58:40.940Z"},"bugs":{"url":"https://github.com/Alex-Guha/rplib/issues"},"license":"ISC","homepage":"https://github.com/Alex-Guha/rplib#readme","keywords":["diagram","svg","d3","visualization","dsl","relational-positioning"],"repository":{"type":"git","url":"git+https://github.com/Alex-Guha/rplib.git","directory":"core"},"description":"Relational-positioning diagram library: anchor-based DSL with recursive composition and in-place component swap.","maintainers":[{"name":"alexguha","email":"alexandersguha@gmail.com"}],"readme":"# @alexguha/rplib\n\nA small SVG diagramming library built around **relational positioning** — items declare a `position` and `previous` anchor, the renderer figures out where they go. Components are JSON, recursively composable, and swappable in place. The bundled DSL is one consumer; you can bring your own by passing a `resolveView` adapter.\n\nThis package is the core canvas. Sibling packages built on top:\n\n- [`@alexguha/rplib-editor`](https://www.npmjs.com/package/@alexguha/rplib-editor) — full diagram-editor UI.\n- [`@alexguha/rplib-viewer`](https://www.npmjs.com/package/@alexguha/rplib-viewer) — read-only viewer UI.\n\n## Install\n\n```sh\nnpm install @alexguha/rplib d3 katex\n```\n\n`d3` and `katex` are peer dependencies.\n\n## Quickstart (custom views)\n\nSkip the bundled DSL and feed resolved views directly:\n\n```js\nimport RPCanvas from '@alexguha/rplib';\nimport d3 from 'd3';\n\nconst myView = {\n  properties: { modelName: 'GPT-4' },\n  content: {\n    A: { shape: 'box', text: { text: '{{modelName}}' } },\n    B: { shape: 'box', previous: 'A', position: 'right', arrow: {} },\n  },\n};\n\nconst canvas = new RPCanvas({\n  svgDOM: d3.select('#svg'),\n  resolveView: (canvas, name) =>\n    name === 'demo' ? { view: myView, isRoot: true } : null,\n});\ncanvas.setTheme({\n  SHAPE_FILL: '#fff', SHAPE_STROKE: '#222',\n  ARROW_COLOR: '#222', TEXT_COLOR: ['#222'], OPACITY: 1,\n});\ncanvas.changeViews('demo');\n```\n\n## Quickstart (bundled DSL)\n\n```js\nimport RPCanvas from '@alexguha/rplib';\nimport {\n  parseAbstractContent,\n  loadAbstractDefinitions,\n  loadRootView,\n} from '@alexguha/rplib/parser';\nimport * as components from './my-components.js';\nimport d3 from 'd3';\n\nconst canvas = new RPCanvas({ svgDOM: d3.select('#svg'), components });\ncanvas.store.abstractDefinitions = parseAbstractContent(definitionText);\nloadAbstractDefinitions(canvas, localStorage);\nloadRootView(canvas, localStorage, /* fallback view name */ 'my_view');\n```\n\n## API surface\n\n| Constructor / method | What it does |\n| --- | --- |\n| `new RPCanvas({ svgDOM, defaults?, components?, eventListenerTargets?, elementToggleCallback?, resolveView? })` | Construct. Only `svgDOM` is required. |\n| `canvas.setTheme(theme)` | Apply theme via CSS variables. No re-render needed for color-only changes. |\n| `canvas.setReporter({ error, warn })` | Inject a diagnostics sink. Default is `console`. |\n| `canvas.changeViews(name)` | Resolve, render, push undo. Throws if `name` can't be resolved. |\n| `canvas.undoViewChange() / redoViewChange()` | Navigation history. |\n| `canvas.on('beforeViewChange'\\|'afterViewChange', fn)` | Lifecycle hooks. Payload `{ view, prevView }`. |\n| `canvas.invalidateView(name?)` | Drop the cached resolved view (force re-resolve next nav). |\n| `canvas.invalidateLayout(name?, ids?)` | Drop cached layouts (descendants chained via `previous` are auto-invalidated). |\n| `canvas.findHierarchicalElementProperty(id, prop)` | Walk a dotted id from most-specific to least to find an inherited property. |\n| `canvas.resolveProperty(id, prop)` | Resolve `prop` element → ancestor → current-view. `id === null` resolves the view-level value only. Backs the panel system's data path. |\n| `canvas.getItem(viewName, id)` | Read an item from a resolved view. Returns `null` if missing. |\n| `canvas.updateItem(viewName, id, patch)` | Shallow-merge patch into an item. `null` deletes a key. |\n| `canvas.addItem(viewName, id, item, {before?, after?})` | Insert an item; defaults to appending. |\n| `canvas.removeItem(viewName, id)` / `renameItem(viewName, oldId, newId)` | Remove / rename, rewriting `previous` refs on rename. |\n| `canvas.updateAbstract(name, patch)` / `updateComponent(name, patch)` | Patch authored sources; auto-invalidates cached views. |\n| `canvas.refresh(viewName?)` | Re-resolve + re-render. Not recorded on edit history. |\n| `canvas.undoEdit() / redoEdit() / canEditUndo() / canEditRedo() / clearEditHistory()` | Edit history (separate from nav). |\n| `canvas.on('beforeMutate'\\|'afterMutate', fn)` | Lifecycle hooks. Payload `{ viewName, changedIds, kind }`. |\n\n`changeViews` is the public navigation entry. `setCurrentView` exists on the\ninstance and fires the `beforeViewChange` / `afterViewChange` hooks, but it's\nthe internal render path — apps should always call `changeViews`.\n\n### Constructor options\n\n- `svgDOM` *(required)* — d3 selection of the target `<svg>`.\n- `defaults` — shallow overrides merged on top of [`rplib/defaults.js`](./defaults.js) (`SHAPE`, `ARROW`). Omit to use library defaults.\n- `components` — component map consumed by the bundled DSL resolver. Required only when you rely on the default resolver; ignored when you pass your own `resolveView`.\n- `eventListenerTargets` — `{ [attrName]: (d3Selection) => void }`. For each key, the renderer invokes the listener on any item carrying that attribute (own or inherited via `previous`-chain). Use for app-level click/hover wiring. See [`utils/attachListeners.js`](./utils/attachListeners.js).\n- `elementToggleCallback` — `(item) => boolean`. Return `true` to skip drawing an item. Use for app-level visibility filters.\n- `resolveView` — `(canvas, name) => { view, isRoot } | null`. App-owned DSL adapter; defaults to the bundled parser.\n\n**Adapters** (no global state inside the lib):\n\n- `resolveView(canvas, name) -> { view, isRoot } | null` — bring your own DSL.\n- `Reporter` `{ error, warn }` — `setReporter`.\n- `Storage` Web Storage-shaped `{ getItem, setItem, removeItem }` — every export in `parser/storage.js` takes one.\n\n## Panels (`@alexguha/rplib/panel`)\n\nThe generic sidebar-content mechanism shared by `@alexguha/rplib-editor` and\n`@alexguha/rplib-viewer`. Core ships only the mechanism — it has **no** built-in\npanel renderers (references included). A consumer configures an ordered array of\npanel defs and passes it to `createEditor({ panels })` / `createViewer({ panels })`.\n\n```js\nimport { definePanel, PanelHost, buildPanelContext, renderRichText } from '@alexguha/rplib/panel';\n```\n\n| Export | What it does |\n| --- | --- |\n| `definePanel(def)` | Validate/normalize a panel definition (below). |\n| `PanelHost` | Owns the sidebar content region; renders the panel array into stacked slots; `update(ctx)` / `pushTransient(fn)` / `restore()`. |\n| `buildPanelContext({ manager, id?, infoEl? })` | Build the per-update `ctx` passed to `resolve`/`render`. |\n| `renderRichText(str, el)` | Render `**bold**` / `$$latex$$` (via the global `katex`) / plaintext into `el`. |\n\n**Panel definition** — a plain object owning *display + edit + data-key* as one unit:\n\n```js\ndefinePanel({\n  name: 'references',                 // unique key (required)\n  title: 'References',                // string | (ctx) => string\n  property: 'references',             // element/view property to resolve…\n  // resolve: (ctx) => ctx.resolveProperty('references'),  // …or full control\n  showWhen: (data, ctx) => !!data,    // optional; hide empty panels\n  render: (container, data, ctx) => { /* build DOM */ },   // required\n  theme: { background: 'PANEL_BACKGROUND' },               // optional theme key (defaults to PANEL_BACKGROUND)\n\n  // Editability (consumed by the editor's component editor):\n  itemFields: { title: 'text', link: 'text', info: 'textarea', authors: 'list' },\n  itemKey: 'title',                   // store as an object keyed by this field\n  // renderEditor: (container, value, onChange, ctx) => { /* full control */ },\n})\n```\n\nA panel with none of `itemFields` / `renderEditor` edits as raw JSON (the\nautomatic fallback).\n\n**The `ctx` object** (built once per sidebar update):\n\n- `manager`, `id` (hovered element id, or `null` on reset), `view`, `viewData`\n- `resolveProperty(prop)` — element → ancestor → view-level (`canvas.resolveProperty`)\n- `renderRichText(str, el)` — bold/LaTeX/plaintext\n- `attachHoverPreview(linkEl)` — while hovering `linkEl`, swap the info pane for the rich-text in `linkEl.dataset.info`\n\n## Tests\n\n```sh\nnpm test\n```\n\nRuns the `node:test`-based parser/serializer round-trip suite under `parser/__tests__/` plus the live-update mutation and partial-redraw suites under `__tests__/` (the latter exercises `renderView` against a stubbed canvas — no DOM). Full d3/DOM renderer tests are still TODO.\n\n## Live editing\n\n`canvas.updateItem`, `addItem`, `removeItem`, `renameItem` mutate the cached\nresolved view in place and re-render. `updateAbstract` / `updateComponent`\npatch the authored DSL sources and invalidate the relevant view cache.\nEvery mutation pushes an inverse onto the **edit history** (separate from the\nnavigation history) — call `canvas.undoEdit() / redoEdit()` to roll edits\nback, independent of `undoViewChange() / redoViewChange()`.\n\n```js\ncanvas.changeViews('demo');\n\n// Patch an item.\ncanvas.updateItem('demo', 'A', { width: 200, opacity: 0.5 });\n\n// Insert one after an existing anchor.\ncanvas.addItem('demo', 'X', { shape: 'box', previous: 'A', position: 'right' }, { after: 'A' });\n\n// Subscribe (e.g. to drive auto-save).\ncanvas.on('afterMutate', ({ viewName, changedIds, kind }) => {\n  saveToLocalStorage();\n});\n\ncanvas.undoEdit();\n```\n\nMutations on resolved-view items (`updateItem`, `addItem`, `removeItem`,\n`renameItem`) trigger a **partial redraw**: only the changed ids and their\n`previous`-chain descendants are removed from the SVG and redrawn. Mutations\non authored sources (`updateAbstract`, `updateComponent`) invalidate the view\ncache and do a full redraw on the next render. Edit history is per-canvas\nand not persisted — subscribe to `'afterMutate'` if you want to serialize\npatches yourself.\n\n**Known limitation:** if item A's `arrow.previous` explicitly references B (not\nA's own `previous`), changing B does not auto-invalidate A's arrow during\npartial redraw. Workaround: include A in your own changed-ids set, or call\n`canvas.refresh()` for a full redraw.\n\n### With a custom `resolveView`\n\nConsumers that bring their own DSL (skip the bundled parser) should use the\n**item-level** mutations only — `getItem`, `updateItem`, `addItem`, `removeItem`,\n`renameItem`. These operate directly on the resolved view in `store.views[name]`\nand don't care where it came from. `updateAbstract` / `updateComponent` are\nbundled-DSL-only and will warn if the name isn't found.\n\nIf your custom resolver reads from an external source-of-truth that changes\noutside rplib (e.g. an app store the user edits via their own UI), call\n`canvas.refresh(viewName)` to force a re-resolve + redraw. `refresh` is the\nescape hatch and does **not** record on the edit-history stack.\n\n---\n\n## Intermediate format\n\nThe canvas renders a resolved \"view\" — a flat JSON structure produced by either the bundled DSL parser or an app-provided `resolveView` adapter. If you want to skip the bundled DSL, your resolver returns objects shaped as follows.\n\n### Resolver\n\n```js\nresolveView(canvas, name) -> { view, isRoot } | null\n```\n\n- `view` — a resolved view object (shape below).\n- `isRoot` — `true` for top-level views (only roots own `properties` for placeholder substitution; the canvas tracks them in `store.rootViews` and updates `store.rootView`). `false` for detail/sub views.\n- Return `null` if the name is unknown; the canvas will fall back to `defaults.VIEW` and throw.\n\nSide effect: if your views reference detail views (via `item.details`), your resolver is responsible for populating `store.views[detailName]` for any such names it surfaces. If you want sidebars to enumerate detail views, also register them via the parser-owned API in `parser/viewStructures.js` (`setViewStructure` / `addDetailView`; apps read via `getViewStructure(name)`). The bundled parser does both recursively; see `parser/parseIntermediateFormat.js#handleDetails`.\n\n### View\n\n```ts\n{\n  properties?: { [name: string]: string },   // root-only; used for {{name}} substitution in text\n  content: { [id: string]: Item },           // render order = insertion order\n  settings?: any[],                          // optional; passed through, treated as set-like by parser\n  references?: any[],                        // optional; same\n  // ...other top-level fields are passed through untouched\n}\n```\n\n### Item\n\nAuthored fields the renderer reads:\n\n```ts\n{\n  shape?: 'box' | 'triangle' | 'trapezoid',\n  width?, height?, opacity?, count?, flipped?, shortSide?,\n  // `count` stacks `count` copies of the shape, offset by xSpacing/ySpacing.\n  // `flipped` mirrors triangle/trapezoid; `shortSide` is the trapezoid's short edge length.\n\n  // Layout, relative to the `previous` item. Unknown values fall through to 'right'.\n  position?: 'above' | 'below' | 'left' | 'right',\n  previous?: string,                          // id of the anchor item\n  x?, y?, xSpacing?, ySpacing?, separation?,  // overrides; defaults come from `defaults.SHAPE`\n\n  // Text (zero or more). `text` wrapped in `$...$` or `$$...$$` is rendered as LaTeX via KaTeX;\n  // otherwise as plain text. `position` is `'<vertical>'` or `'<vertical>-<side>'`, where vertical\n  // is one of 'top' | 'bottom' | 'left' | 'right' | 'center' (default 'top') and side is one of\n  // 'left' | 'right' | 'top' | 'bottom' | 'center' (default 'center'). `color` is either\n  // a numeric palette index (1-based into theme.TEXT_COLOR) or a CSS color string.\n  text?:  { text?: string, position?, xOffset?, yOffset?, color? }\n        | Array<{ ... }>,\n\n  // Arrows from `previous` to this item. See `core/utils/arrows.js` for segmented-arrow shape.\n  arrow?: ArrowObject | ArrowObject[],\n\n  // Navigation:\n  details?: string,                           // name of a detail view to drill into\n  description?: string, references?: any[],   // sidebar payloads\n}\n```\n\nThe renderer never writes back to items. Derived layout lives separately in `canvas.layouts[viewName][id]` (`{ x, y, width, height, xSpacing, ySpacing }`), keyed off the item's id, and is invalidated via `canvas.invalidateLayout(viewName, ids?)`.\n\n### Minimal example\n\n```js\nconst myView = {\n  properties: { modelName: 'GPT-4' },\n  content: {\n    A: { shape: 'box', text: { text: '{{modelName}}' } },\n    B: { shape: 'box', previous: 'A', position: 'right', arrow: {} },\n  },\n};\n\nconst canvas = new RPCanvas({\n  svgDOM: d3.select('#svg'),\n  resolveView: (canvas, name) => name === 'demo' ? { view: myView, isRoot: true } : null,\n});\ncanvas.changeViews('demo');\n```\n\nThe bundled DSL on top of this is `parser/parseAbstractFile.js` (text → intermediate definitions) and `parser/parseIntermediateFormat.js` (definitions + components → resolved views). Apps that want their own DSL bypass both and feed resolved views directly.\n","readmeFilename":"README.md"}