@lupinum/board-core 1.0.0-beta.2 → 1.0.0-beta.4

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.
Files changed (51) hide show
  1. package/README.md +24 -0
  2. package/dist/agent/AGENTS.md +63 -0
  3. package/dist/agent/manifest.json +320 -0
  4. package/dist/agent/pages/docs/build-features/connections.md +241 -0
  5. package/dist/agent/pages/docs/build-features/custom-node-renderers.md +219 -0
  6. package/dist/agent/pages/docs/build-features/groups-and-nesting.md +172 -0
  7. package/dist/agent/pages/docs/build-features/performance.md +76 -0
  8. package/dist/agent/pages/docs/build-features/read-only-and-command-guards.md +52 -0
  9. package/dist/agent/pages/docs/build-features/save-and-load.md +142 -0
  10. package/dist/agent/pages/docs/build-features/selection-and-keyboard.md +145 -0
  11. package/dist/agent/pages/docs/build-features/ssr-and-deterministic-state.md +67 -0
  12. package/dist/agent/pages/docs/build-features/theming.md +102 -0
  13. package/dist/agent/pages/docs/build-features/undo-and-redo.md +124 -0
  14. package/dist/agent/pages/docs/evaluate/design-decisions.md +44 -0
  15. package/dist/agent/pages/docs/evaluate/how-nuxt-board-works.md +41 -0
  16. package/dist/agent/pages/docs/evaluate/why-nuxt-board.md +61 -0
  17. package/dist/agent/pages/docs/project/contributing.md +101 -0
  18. package/dist/agent/pages/docs/project/support-and-security.md +40 -0
  19. package/dist/agent/pages/docs/reference/board-core-types.md +82 -0
  20. package/dist/agent/pages/docs/reference/board-core.md +261 -0
  21. package/dist/agent/pages/docs/reference/connections.md +531 -0
  22. package/dist/agent/pages/docs/reference/events-and-errors.md +158 -0
  23. package/dist/agent/pages/docs/reference/glossary.md +58 -0
  24. package/dist/agent/pages/docs/reference/history.md +193 -0
  25. package/dist/agent/pages/docs/reference/minimap.md +157 -0
  26. package/dist/agent/pages/docs/reference/nuxt-board.md +148 -0
  27. package/dist/agent/pages/docs/reference/package-overview.md +58 -0
  28. package/dist/agent/pages/docs/reference/vue-board.md +424 -0
  29. package/dist/agent/pages/docs/reference/vue-composables.md +293 -0
  30. package/dist/agent/pages/docs/solutions/mind-map.md +104 -0
  31. package/dist/agent/pages/docs/solutions/nuxt-application.md +47 -0
  32. package/dist/agent/pages/docs/solutions/planning-board.md +51 -0
  33. package/dist/agent/pages/docs/solutions/read-only-viewer.md +61 -0
  34. package/dist/agent/pages/docs/solutions/workflow-builder.md +124 -0
  35. package/dist/agent/pages/docs/start-building/add-connections-and-history.md +42 -0
  36. package/dist/agent/pages/docs/start-building/customize-your-first-node.md +38 -0
  37. package/dist/agent/pages/docs/start-building/installation.md +76 -0
  38. package/dist/agent/pages/docs/start-building/your-first-board.md +52 -0
  39. package/dist/agent/pages/docs/understand-the-system/camera-and-coordinates.md +22 -0
  40. package/dist/agent/pages/docs/understand-the-system/commands-and-transactions.md +31 -0
  41. package/dist/agent/pages/docs/understand-the-system/document-and-session-state.md +25 -0
  42. package/dist/agent/pages/docs/understand-the-system/nodes-and-hierarchy.md +24 -0
  43. package/dist/agent/pages/docs/understand-the-system/packages-and-plugins.md +29 -0
  44. package/dist/agent/pages/docs/understand-the-system/persistence-and-json-canvas.md +25 -0
  45. package/dist/agent/pages/docs/understand-the-system/rendering-and-interaction.md +22 -0
  46. package/dist/agent/pages/docs/understand-the-system/the-engine.md +28 -0
  47. package/dist/agent/pages/docs.md +18 -0
  48. package/dist/engine/transaction.d.ts +2 -2
  49. package/dist/index.js +18 -7
  50. package/dist/types.d.ts +2 -2
  51. package/package.json +3 -2
@@ -0,0 +1,104 @@
1
+ ---
2
+ title: "Mind map"
3
+ description: "Spatial topic hierarchy with connections, custom renderers, and branch-from-selection."
4
+ url: "https://nuxt-board.lupinum.com/docs/solutions/mind-map"
5
+ route: "/docs/solutions/mind-map"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Mind map
13
+
14
+ > Spatial topic hierarchy with connections, custom renderers, and branch-from-selection.
15
+
16
+ > Component omitted: `mind-map-demo`.
17
+ > This page contains an interactive or site-specific block that has no agent markdown serializer yet.
18
+
19
+ ## What happens
20
+
21
+ The root topic sits at the center with branches radiating outward. Each topic is a custom renderer that styles itself based on depth (root, branch, leaf). Connections use bezier routing to draw the edges between parent and child topics.
22
+
23
+ Select any topic and click "Add branch" to grow the map from that point. The new node auto-connects to its parent using the connections plugin. History tracks every change so you can undo freely.
24
+
25
+ Topic text stays as topic text. The renderer gets depth from the connection
26
+ graph, so branch styling is derived from the map structure instead of hidden in
27
+ the first line of `node.text`.
28
+
29
+ ## The code
30
+
31
+ <code-tree defaultValue="app.vue">
32
+ ```vue [app.vue]
33
+ <script setup lang="ts">
34
+ import { computed } from 'vue'
35
+ import { asNodeId, createBoardEngine } from '@lupinum/board-core'
36
+ import { connectionsPlugin } from '@lupinum/board-connections'
37
+ import { BoardConnectionLayer } from '@lupinum/board-connections/vue'
38
+ import { historyPlugin } from '@lupinum/board-history'
39
+ import TopicNode from './TopicNode.vue'
40
+
41
+ const ROOT_ID = asNodeId('root')
42
+ const engine = createBoardEngine({
43
+ grid: { size: 20, snap: true, pattern: 'dot' },
44
+ plugins: [historyPlugin(), connectionsPlugin({ routing: 'bezier' })],
45
+ })
46
+
47
+ const topicDepthsById = computed(() => {
48
+ const depths = new Map([[ROOT_ID, 0]])
49
+ for (const edge of engine.plugins.connections.getEdges()) {
50
+ const fromDepth = depths.get(edge.from)
51
+ if (fromDepth !== undefined) {
52
+ depths.set(edge.to, fromDepth + 1)
53
+ }
54
+ }
55
+ return depths
56
+ })
57
+ </script>
58
+
59
+ <template>
60
+ <BoardRoot :engine="engine" style="height: 100vh">
61
+ <template #node:text="{ node, selected }">
62
+ <TopicNode
63
+ :node="node"
64
+ :selected="selected"
65
+ :depth="topicDepthsById.get(node.id) ?? 2"
66
+ />
67
+ </template>
68
+ <BoardConnectionLayer routing="bezier" />
69
+ </BoardRoot>
70
+ </template>
71
+ ```
72
+
73
+ ```vue [TopicNode.vue]
74
+ <script setup lang="ts">
75
+ import { computed } from 'vue'
76
+
77
+ const props = defineProps<{
78
+ node: { text?: string }
79
+ depth: number
80
+ }>()
81
+
82
+ const topic = computed(() => {
83
+ const [title = 'Topic', detail = ''] = props.node.text?.split('\n') ?? []
84
+ return { title, detail }
85
+ })
86
+ </script>
87
+
88
+ <template>
89
+ <div class="topic-card" :class="depthClass(depth)">
90
+ <strong>{{ topic.title }}</strong>
91
+ <p>
92
+ {{ topic.detail }}
93
+ </p>
94
+ </div>
95
+ </template>
96
+ ```
97
+ </code-tree>
98
+
99
+ ## Try these things
100
+
101
+ - Select the root topic, then add a branch. The new node connects automatically.
102
+ - Drag a branch away from the center. The bezier edge stretches to follow.
103
+ - Undo a few times to watch the map shrink back.
104
+ - Zoom out to see the full map structure.
@@ -0,0 +1,47 @@
1
+ ---
2
+ title: "Nuxt auto-imports"
3
+ description: "Use board components and composables without manual imports via the Nuxt module."
4
+ url: "https://nuxt-board.lupinum.com/docs/solutions/nuxt-application"
5
+ route: "/docs/solutions/nuxt-application"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Nuxt auto-imports
13
+
14
+ > Use board components and composables without manual imports via the Nuxt module.
15
+
16
+ > Component omitted: `nuxt-auto-imports-demo`.
17
+ > This page contains an interactive or site-specific block that has no agent markdown serializer yet.
18
+
19
+ ## What happens
20
+
21
+ This is the same board model you would build in Vue, but the Nuxt module auto-imports the component and composable layer. That removes boilerplate without changing the engine surface.
22
+
23
+ ## The code
24
+
25
+ <code-tree defaultValue="app.vue">
26
+ ```ts [nuxt.config.ts]
27
+ export default defineNuxtConfig({
28
+ modules: ['@lupinum/nuxt-board'],
29
+ })
30
+ ```
31
+
32
+ ```vue [app.vue]
33
+ <script setup lang="ts">
34
+ const engine = createBoardEngine()
35
+ </script>
36
+
37
+ <template>
38
+ <BoardRoot :engine="engine" style="height: 100vh" />
39
+ </template>
40
+ ```
41
+ </code-tree>
42
+
43
+ ## Try these things
44
+
45
+ - Add a node from the demo toolbar.
46
+ - Confirm the board works without any explicit imports in the example source.
47
+ - Compare it with the [Quick Start](/raw/docs/start-building/your-first-board.md) tabs.
@@ -0,0 +1,51 @@
1
+ ---
2
+ title: "Planning board"
3
+ description: "A practical planning surface with notes, grouping, and zoom-to-fit."
4
+ url: "https://nuxt-board.lupinum.com/docs/solutions/planning-board"
5
+ route: "/docs/solutions/planning-board"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Planning board
13
+
14
+ > A practical planning surface with notes, grouping, and zoom-to-fit.
15
+
16
+ > Component omitted: `basic-board-demo`.
17
+ > This page contains an interactive or site-specific block that has no agent markdown serializer yet.
18
+
19
+ ## What happens
20
+
21
+ This planning surface uses a seeded document, direct engine commands, grouping, and the default interaction model. The application can add domain renderers later without replacing its board document.
22
+
23
+ ## The code
24
+
25
+ <code-tree defaultValue="app.vue">
26
+ ```ts [app.vue]
27
+ const engine = createBoardEngine({
28
+ grid: { size: 20, majorEvery: 5, snap: true, pattern: 'line' },
29
+ })
30
+
31
+ engine.loadDocument(document, { mode: 'replace' })
32
+ ```
33
+
34
+ ```ts [toolbar.ts]
35
+ engine.createNode({ type: 'text', text: 'New note' })
36
+ engine.zoomToFit(72, false)
37
+ ```
38
+ </code-tree>
39
+
40
+ ## Try these things
41
+
42
+ - Click **Add note** and drag the new node around. It snaps to the grid.
43
+ - Hold <kbd>
44
+ Shift
45
+ </kbd>
46
+ and click two nodes, then click **Group selection**.
47
+ - Double-click a node to edit its text. Press <kbd>
48
+ Escape
49
+ </kbd>
50
+ to cancel.
51
+ - Spread nodes apart, then click **Zoom to fit** to recenter.
@@ -0,0 +1,61 @@
1
+ ---
2
+ title: "Read-only viewer"
3
+ description: "Display a board for viewing without allowing edits by using a command guard."
4
+ url: "https://nuxt-board.lupinum.com/docs/solutions/read-only-viewer"
5
+ route: "/docs/solutions/read-only-viewer"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Read-only viewer
13
+
14
+ > Display a board for viewing without allowing edits by using a command guard.
15
+
16
+ > Component omitted: `read-only-toggle-demo`.
17
+ > This page contains an interactive or site-specific block that has no agent markdown serializer yet.
18
+
19
+ ## What happens
20
+
21
+ This example uses `addCommandGuard` to register a command guard. The board still renders and the camera still moves, but the edit commands used by the demo exit early when read-only mode is enabled.
22
+
23
+ ## The code
24
+
25
+ <steps>
26
+ ### Load the scene
27
+
28
+ ```ts
29
+ const engine = createBoardEngine()
30
+ engine.loadDocument(JSON.parse(savedBoardJson), { mode: 'replace' })
31
+ ```
32
+
33
+ ### Block mutation commands
34
+
35
+ ```ts
36
+ const blocked = new Set([
37
+ 'beginNodeDrag',
38
+ 'beginResize',
39
+ 'beginTextEdit',
40
+ 'createNode',
41
+ 'updateNode',
42
+ 'deleteSelected',
43
+ ])
44
+
45
+ const stopReadOnly = engine.addCommandGuard(({ name }) =>
46
+ blocked.has(name) ? 'Board is read-only.' : true,
47
+ )
48
+ ```
49
+
50
+ ### Remove the guard to restore editing
51
+
52
+ ```ts
53
+ stopReadOnly()
54
+ ```
55
+ </steps>
56
+
57
+ ## Try these things
58
+
59
+ - Leave the demo in read-only mode and try dragging or deleting a node.
60
+ - Toggle back to edit mode and repeat the same action.
61
+ - Watch the blocked command counter increment when a guard rejects a mutation.
@@ -0,0 +1,124 @@
1
+ ---
2
+ title: "Workflow renderer demo"
3
+ description: "Render step-style cards with custom renderers, connections, and undo/redo."
4
+ url: "https://nuxt-board.lupinum.com/docs/solutions/workflow-builder"
5
+ route: "/docs/solutions/workflow-builder"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Workflow renderer demo
13
+
14
+ > Render step-style cards with custom renderers, connections, and undo/redo.
15
+
16
+ > Component omitted: `workflow-renderer-demo`.
17
+ > This page contains an interactive or site-specific block that has no agent markdown serializer yet.
18
+
19
+ ## What happens
20
+
21
+ This example combines three patterns: app-owned workflow records, board-owned
22
+ layout, and first-party connections/history. The workflow step fields live in
23
+ one app model. Board nodes keep only the spatial identity that the canvas needs.
24
+
25
+ The history plugin records board commands such as dragging, resizing, grouping,
26
+ and connection edits. If your product needs undo for workflow status changes,
27
+ put that in the workflow model's own command/history path instead of copying the
28
+ status into `node.text`.
29
+
30
+ ## The code
31
+
32
+ <code-tree defaultValue="app.vue">
33
+ ```vue [app.vue]
34
+ <script setup lang="ts">
35
+ import { computed, ref } from 'vue'
36
+ import { asNodeId, createBoardEngine } from '@lupinum/board-core'
37
+ import { historyPlugin } from '@lupinum/board-history'
38
+ import { connectionsPlugin } from '@lupinum/board-connections'
39
+ import { BoardConnectionLayer } from '@lupinum/board-connections/vue'
40
+ import StepNode from './StepNode.vue'
41
+
42
+ const steps = ref([
43
+ {
44
+ id: 'capture',
45
+ status: 'done',
46
+ label: 'Capture lead',
47
+ summary: 'Collect the request and normalize inputs.',
48
+ },
49
+ {
50
+ id: 'qualify',
51
+ status: 'active',
52
+ label: 'Qualify',
53
+ summary: 'Score, tag, and route the lead.',
54
+ },
55
+ ])
56
+ const stepsById = computed(
57
+ () => new Map(steps.value.map((step) => [step.id, step])),
58
+ )
59
+ const engine = createBoardEngine({
60
+ plugins: [historyPlugin(), connectionsPlugin({ routing: 'step' })],
61
+ })
62
+
63
+ for (const step of steps.value) {
64
+ engine.createNode({
65
+ id: asNodeId(step.id),
66
+ type: 'text',
67
+ text: '',
68
+ })
69
+ }
70
+ </script>
71
+
72
+ <template>
73
+ <BoardRoot :engine="engine" style="height: 100vh">
74
+ <template #node:text="{ node, selected }">
75
+ <StepNode :step="stepsById.get(node.id)" :selected="selected" />
76
+ </template>
77
+ <BoardConnectionLayer routing="step" />
78
+ </BoardRoot>
79
+ </template>
80
+ ```
81
+
82
+ ```vue [StepNode.vue]
83
+ <script setup lang="ts">
84
+ defineProps<{
85
+ step?: {
86
+ status: 'pending' | 'active' | 'done'
87
+ label: string
88
+ summary: string
89
+ }
90
+ selected: boolean
91
+ }>()
92
+ </script>
93
+
94
+ <template>
95
+ <div class="step-card">
96
+ <span>{{ step?.status ?? 'pending' }}</span>
97
+ <strong>{{ step?.label ?? 'Missing step' }}</strong>
98
+ <p>{{ step?.summary }}</p>
99
+ </div>
100
+ </template>
101
+ ```
102
+ </code-tree>
103
+
104
+ ## Try these things
105
+
106
+ - Select a step, then click **Cycle selected step** to advance its status.
107
+ - Drag a step — the routed edges redraw to stay readable and the engine history records the layout change.
108
+ - Press <kbd>
109
+ ⌘
110
+ </kbd>
111
+ + <kbd>
112
+ Z
113
+ </kbd>
114
+ to undo the layout change, then <kbd>
115
+ ⌘
116
+ </kbd>
117
+ + <kbd>
118
+ Shift
119
+ </kbd>
120
+ + <kbd>
121
+ Z
122
+ </kbd>
123
+ to redo it.
124
+ - Watch the undo/redo counter in the toolbar update in real time.
@@ -0,0 +1,42 @@
1
+ ---
2
+ title: "Add connections and history"
3
+ description: "Install edges and undo without expanding the core package."
4
+ url: "https://nuxt-board.lupinum.com/docs/start-building/add-connections-and-history"
5
+ route: "/docs/start-building/add-connections-and-history"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Add connections and history
13
+
14
+ > Install edges and undo without expanding the core package.
15
+
16
+ Install both plugins when constructing the engine:
17
+
18
+ ```ts
19
+ import { connectionsPlugin } from '@lupinum/board-connections'
20
+ import { historyPlugin } from '@lupinum/board-history'
21
+
22
+ const engine = createBoardEngine({
23
+ plugins: [connectionsPlugin(), historyPlugin()],
24
+ })
25
+ ```
26
+
27
+ Render connections under the matching `BoardRoot`:
28
+
29
+ ```vue
30
+ <BoardRoot :engine="engine" style="height: 100vh">
31
+ <BoardConnectionLayer />
32
+ </BoardRoot>
33
+ ```
34
+
35
+ Create an edge through its installed API and undo through history:
36
+
37
+ ```ts
38
+ engine.plugins.connections.createEdge({ from: source.id, to: target.id })
39
+ engine.plugins.history.undo()
40
+ ```
41
+
42
+ Plugin names must be unique. Construction throws `BoardConflictError` before installation when the tuple contains duplicate names.
@@ -0,0 +1,38 @@
1
+ ---
2
+ title: "Customize your first node"
3
+ description: "Render product-specific Vue content without creating another document schema."
4
+ url: "https://nuxt-board.lupinum.com/docs/start-building/customize-your-first-node"
5
+ route: "/docs/start-building/customize-your-first-node"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Customize your first node
13
+
14
+ > Render product-specific Vue content without creating another document schema.
15
+
16
+ Register a Vue component for an existing JSON Canvas node type.
17
+
18
+ ```ts
19
+ import type { BoardRendererRegistry } from '@lupinum/vue-board'
20
+ import TaskCard from './TaskCard.vue'
21
+
22
+ const renderers: BoardRendererRegistry = {
23
+ text: TaskCard,
24
+ }
25
+ ```
26
+
27
+ ```vue
28
+ <BoardRoot :engine="engine" :renderers="renderers" style="height: 100vh" />
29
+ ```
30
+
31
+ The renderer receives the node and selection state. Store canonical content in direct node fields such as `text`; do not introduce a nested content mirror.
32
+
33
+ > Component omitted: `renderer-lab`.
34
+ > This page contains an interactive or site-specific block that has no agent markdown serializer yet.
35
+
36
+ Switch the renderer in the lab. The card changes while the record keeps the same ID, type, text, and color.
37
+
38
+ For a complete renderer contract, editing hooks, slots, and LOD behavior, read [Custom node renderers](/raw/docs/build-features/custom-node-renderers.md).
@@ -0,0 +1,76 @@
1
+ ---
2
+ title: "Installation"
3
+ description: "Install the smallest Nuxt Board package set for Vue or Nuxt."
4
+ url: "https://nuxt-board.lupinum.com/docs/start-building/installation"
5
+ route: "/docs/start-building/installation"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Installation
13
+
14
+ > Install the smallest Nuxt Board package set for Vue or Nuxt.
15
+
16
+ ## Use a coding agent
17
+
18
+ A coding agent is a development tool that can inspect and change your project.
19
+
20
+ Copy this task prompt into your application agent:
21
+
22
+ ```text
23
+ Use the installed Nuxt Board packages to implement my requested feature.
24
+ Read the application's instructions first. Resolve @lupinum/board-core/agent-docs
25
+ and each installed Board package’s agent-docs entry from this application
26
+ directory. Read the relevant local pages.
27
+ Preserve the existing AGENTS.md. If it has no equivalent guidance, append
28
+ one short note to resolve installed package docs before integration work
29
+ and after dependency changes. Do not install a consumer skill.
30
+ Check the completed feature using this project's normal commands.
31
+ ```
32
+
33
+ The installed documentation matches the package version. If an older version
34
+ has no documentation export, use its README, types and matching release docs.
35
+
36
+ ## Vue
37
+
38
+ ```bash
39
+ pnpm add @lupinum/board-core @lupinum/vue-board
40
+ ```
41
+
42
+ Import the stylesheet once in the application entry or root component:
43
+
44
+ ```ts
45
+ import '@lupinum/vue-board/style.css'
46
+ ```
47
+
48
+ Nuxt Board requires Vue 3.5 and Node 20.19 or newer for the supported toolchain.
49
+
50
+ ## Nuxt
51
+
52
+ ```bash
53
+ pnpm add @lupinum/nuxt-board @lupinum/board-core @lupinum/vue-board
54
+ ```
55
+
56
+ ```ts
57
+ export default defineNuxtConfig({
58
+ modules: ['@lupinum/nuxt-board'],
59
+ })
60
+ ```
61
+
62
+ The module registers styles and optional component/composable auto-imports. It owns no board behavior.
63
+
64
+ Vue and Nuxt use the same engine. For SSR, use deterministic IDs and complete
65
+ initial records; do not create random initial nodes during hydration. Browser-only
66
+ measurements remain session state and are populated after mount.
67
+
68
+ ## Optional packages
69
+
70
+ ```bash
71
+ pnpm add @lupinum/board-connections @lupinum/board-history
72
+ ```
73
+
74
+ Import the minimap from `@lupinum/vue-board/minimap`; it is a subpath, not a separate package.
75
+
76
+ Install optional packages only when the product needs their capability. Continue with [Your first board](/raw/docs/start-building/your-first-board.md).
@@ -0,0 +1,52 @@
1
+ ---
2
+ title: "Your first board"
3
+ description: "Create, populate, and mount a working board with one canonical example."
4
+ url: "https://nuxt-board.lupinum.com/docs/start-building/your-first-board"
5
+ route: "/docs/start-building/your-first-board"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Your first board
13
+
14
+ > Create, populate, and mount a working board with one canonical example.
15
+
16
+ Create one stable engine and pass it to `BoardRoot`.
17
+
18
+ ```vue
19
+ <script setup lang="ts">
20
+ import { createBoardEngine } from '@lupinum/board-core'
21
+ import { BoardRoot } from '@lupinum/vue-board'
22
+ import '@lupinum/vue-board/style.css'
23
+
24
+ const engine = createBoardEngine({
25
+ grid: { size: 20, snap: true },
26
+ })
27
+
28
+ engine.createNode({
29
+ type: 'text',
30
+ x: 80,
31
+ y: 80,
32
+ width: 260,
33
+ height: 140,
34
+ color: '5',
35
+ text: 'Review onboarding',
36
+ })
37
+ </script>
38
+
39
+ <template>
40
+ <BoardRoot :engine="engine" style="height: 100vh" />
41
+ </template>
42
+ ```
43
+
44
+ The container needs a real height. `BoardRoot` cannot infer space from an auto-sized empty parent.
45
+
46
+ Try dragging, resizing, selecting, panning, and zooming. `createNode()` selects the new node by default.
47
+
48
+ ## What happened
49
+
50
+ The engine created and selected a JSON Canvas text record. `BoardRoot` subscribed to the engine and rendered the record with the default renderer. Pointer interactions update the same engine through the framework adapter.
51
+
52
+ Keep the engine instance stable after mount. Load new contents with `engine.loadDocument()` instead of replacing the engine prop.
@@ -0,0 +1,22 @@
1
+ ---
2
+ title: "Camera and coordinates"
3
+ description: "Pan, zoom, viewport measurements, and board-space conversion."
4
+ url: "https://nuxt-board.lupinum.com/docs/understand-the-system/camera-and-coordinates"
5
+ route: "/docs/understand-the-system/camera-and-coordinates"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Camera and coordinates
13
+
14
+ > Pan, zoom, viewport measurements, and board-space conversion.
15
+
16
+ The camera stores translation `x`, `y`, and zoom `z`. Nodes remain in board coordinates. `BoardRoot` applies the camera transform and owns viewport measurement.
17
+
18
+ Use engine camera commands such as `panBy`, `zoomTo`, and `zoomToFit`. Use the exported math helpers when application UI must convert between screen and board space.
19
+
20
+ Viewport size is runtime session state because it depends on the mounted DOM. It does not belong in the persisted JSON Canvas document.
21
+
22
+ Rendering can derive visible bounds from the camera and viewport, then cull nodes outside the margin. Camera changes publish through their focused subscribable and events.
@@ -0,0 +1,31 @@
1
+ ---
2
+ title: "Commands and transactions"
3
+ description: "How changes are guarded, staged, validated, committed, and published."
4
+ url: "https://nuxt-board.lupinum.com/docs/understand-the-system/commands-and-transactions"
5
+ route: "/docs/understand-the-system/commands-and-transactions"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Commands and transactions
13
+
14
+ > How changes are guarded, staged, validated, committed, and published.
15
+
16
+ Every persistent command joins one outer transaction.
17
+
18
+ 1. Command guards inspect an immutable description.
19
+ 2. Writable roots and plugin slices are staged.
20
+ 3. The command and nested commands update the candidate.
21
+ 4. Core and plugin invariants validate the candidate.
22
+ 5. Commit projectors finalize history and plugin effects.
23
+ 6. Subscriptions publish.
24
+ 7. Public events publish.
25
+
26
+ > Component omitted: `failed-transaction-lab`.
27
+ > This page contains an interactive or site-specific block that has no agent markdown serializer yet.
28
+
29
+ Run the failing batch. The first nested update is staged, but the duplicate ID rejects the candidate. State, plugin slices, queued events, subscriptions, and history remain unchanged.
30
+
31
+ Subscriber callbacks receive the value from before the outer command as `prev`, even when nested commands updated the same concept several times.
@@ -0,0 +1,25 @@
1
+ ---
2
+ title: "Document and session state"
3
+ description: "Why rendered state and exported state can differ during a gesture."
4
+ url: "https://nuxt-board.lupinum.com/docs/understand-the-system/document-and-session-state"
5
+ route: "/docs/understand-the-system/document-and-session-state"
6
+ locale: "en"
7
+ section: "Documentation"
8
+ collection: "docs"
9
+ source: "ginko-content"
10
+ ---
11
+
12
+ # Document and session state
13
+
14
+ > Why rendered state and exported state can differ during a gesture.
15
+
16
+ Committed document state contains the records a successful command can persist. Session state contains browser measurements, active interaction, snap guides, clipboard data, and transient geometry.
17
+
18
+ > Component omitted: `document-session-lab`.
19
+ > This page contains an interactive or site-specific block that has no agent markdown serializer yet.
20
+
21
+ During a drag, `$nodes` exposes effective geometry: committed nodes plus transient overrides. `exportDocument()` continues to return committed geometry. Releasing the pointer commits the final position once. Escape or pointer cancellation discards the override.
22
+
23
+ This distinction prevents every pointer move from becoming a document transaction or history entry. The trade-off is intentional: runtime and exported coordinates may differ while the gesture is active.
24
+
25
+ Persist `exportDocument()`, not `getState()`, Vue refs, or DOM state.