@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.
- package/README.md +24 -0
- package/dist/agent/AGENTS.md +63 -0
- package/dist/agent/manifest.json +320 -0
- package/dist/agent/pages/docs/build-features/connections.md +241 -0
- package/dist/agent/pages/docs/build-features/custom-node-renderers.md +219 -0
- package/dist/agent/pages/docs/build-features/groups-and-nesting.md +172 -0
- package/dist/agent/pages/docs/build-features/performance.md +76 -0
- package/dist/agent/pages/docs/build-features/read-only-and-command-guards.md +52 -0
- package/dist/agent/pages/docs/build-features/save-and-load.md +142 -0
- package/dist/agent/pages/docs/build-features/selection-and-keyboard.md +145 -0
- package/dist/agent/pages/docs/build-features/ssr-and-deterministic-state.md +67 -0
- package/dist/agent/pages/docs/build-features/theming.md +102 -0
- package/dist/agent/pages/docs/build-features/undo-and-redo.md +124 -0
- package/dist/agent/pages/docs/evaluate/design-decisions.md +44 -0
- package/dist/agent/pages/docs/evaluate/how-nuxt-board-works.md +41 -0
- package/dist/agent/pages/docs/evaluate/why-nuxt-board.md +61 -0
- package/dist/agent/pages/docs/project/contributing.md +101 -0
- package/dist/agent/pages/docs/project/support-and-security.md +40 -0
- package/dist/agent/pages/docs/reference/board-core-types.md +82 -0
- package/dist/agent/pages/docs/reference/board-core.md +261 -0
- package/dist/agent/pages/docs/reference/connections.md +531 -0
- package/dist/agent/pages/docs/reference/events-and-errors.md +158 -0
- package/dist/agent/pages/docs/reference/glossary.md +58 -0
- package/dist/agent/pages/docs/reference/history.md +193 -0
- package/dist/agent/pages/docs/reference/minimap.md +157 -0
- package/dist/agent/pages/docs/reference/nuxt-board.md +148 -0
- package/dist/agent/pages/docs/reference/package-overview.md +58 -0
- package/dist/agent/pages/docs/reference/vue-board.md +424 -0
- package/dist/agent/pages/docs/reference/vue-composables.md +293 -0
- package/dist/agent/pages/docs/solutions/mind-map.md +104 -0
- package/dist/agent/pages/docs/solutions/nuxt-application.md +47 -0
- package/dist/agent/pages/docs/solutions/planning-board.md +51 -0
- package/dist/agent/pages/docs/solutions/read-only-viewer.md +61 -0
- package/dist/agent/pages/docs/solutions/workflow-builder.md +124 -0
- package/dist/agent/pages/docs/start-building/add-connections-and-history.md +42 -0
- package/dist/agent/pages/docs/start-building/customize-your-first-node.md +38 -0
- package/dist/agent/pages/docs/start-building/installation.md +76 -0
- package/dist/agent/pages/docs/start-building/your-first-board.md +52 -0
- package/dist/agent/pages/docs/understand-the-system/camera-and-coordinates.md +22 -0
- package/dist/agent/pages/docs/understand-the-system/commands-and-transactions.md +31 -0
- package/dist/agent/pages/docs/understand-the-system/document-and-session-state.md +25 -0
- package/dist/agent/pages/docs/understand-the-system/nodes-and-hierarchy.md +24 -0
- package/dist/agent/pages/docs/understand-the-system/packages-and-plugins.md +29 -0
- package/dist/agent/pages/docs/understand-the-system/persistence-and-json-canvas.md +25 -0
- package/dist/agent/pages/docs/understand-the-system/rendering-and-interaction.md +22 -0
- package/dist/agent/pages/docs/understand-the-system/the-engine.md +28 -0
- package/dist/agent/pages/docs.md +18 -0
- package/dist/engine/transaction.d.ts +2 -2
- package/dist/index.js +18 -7
- package/dist/types.d.ts +2 -2
- package/package.json +3 -2
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Selection and interaction"
|
|
3
|
+
description: "Selection modes, box selection, and the interaction state machine."
|
|
4
|
+
url: "https://nuxt-board.lupinum.com/docs/build-features/selection-and-keyboard"
|
|
5
|
+
route: "/docs/build-features/selection-and-keyboard"
|
|
6
|
+
locale: "en"
|
|
7
|
+
section: "Documentation"
|
|
8
|
+
collection: "docs"
|
|
9
|
+
source: "ginko-content"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Selection and interaction
|
|
13
|
+
|
|
14
|
+
> Selection modes, box selection, and the interaction state machine.
|
|
15
|
+
|
|
16
|
+
Selection and interaction are how users communicate with the board. Select nodes to act on them, drag to move, resize from handles, and box-select on empty canvas.
|
|
17
|
+
|
|
18
|
+
> Component omitted: `interaction-state-viz`.
|
|
19
|
+
> This page contains an interactive or site-specific block that has no agent markdown serializer yet.
|
|
20
|
+
|
|
21
|
+
## Selecting nodes
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
engine.select(nodeId) // replace selection
|
|
25
|
+
engine.select([nodeA, nodeB]) // replace with multiple
|
|
26
|
+
engine.select(nodeId, 'append') // add to selection
|
|
27
|
+
engine.select(nodeId, 'toggle') // flip in/out
|
|
28
|
+
engine.selectAll() // all visible nodes
|
|
29
|
+
engine.clearSelection() // deselect everything
|
|
30
|
+
const selected = engine.getSelection() // NodeId[]
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
| Mode | Behavior |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| `'replace'` | Clears existing selection, selects the given IDs (default). |
|
|
36
|
+
| `'append'` | Adds IDs to the existing selection. |
|
|
37
|
+
| `'toggle'` | Flips each ID in/out of the selection. |
|
|
38
|
+
|
|
39
|
+
Keyboard shortcuts: <kbd>
|
|
40
|
+
⌘
|
|
41
|
+
</kbd>
|
|
42
|
+
|
|
43
|
+
+ <kbd>
|
|
44
|
+
A
|
|
45
|
+
</kbd>
|
|
46
|
+
|
|
47
|
+
to select all, <kbd>
|
|
48
|
+
Escape
|
|
49
|
+
</kbd>
|
|
50
|
+
|
|
51
|
+
to deselect.
|
|
52
|
+
|
|
53
|
+
In Vue, use the `useBoardSelection()` composable for reactive access:
|
|
54
|
+
|
|
55
|
+
```vue
|
|
56
|
+
<script setup lang="ts">
|
|
57
|
+
const selection = useBoardSelection()
|
|
58
|
+
// selection.value is readonly NodeId[]
|
|
59
|
+
</script>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Interaction state machine
|
|
63
|
+
|
|
64
|
+
The engine tracks pointer interactions through six modes. Only one mode is active at a time.
|
|
65
|
+
|
|
66
|
+
<accordion>
|
|
67
|
+
<accordion-item title="Idle">
|
|
68
|
+
No interaction is active. This is the default state.
|
|
69
|
+
</accordion-item>
|
|
70
|
+
|
|
71
|
+
<accordion-item title="Panning">
|
|
72
|
+
Triggered by <kbd>
|
|
73
|
+
Space
|
|
74
|
+
</kbd>
|
|
75
|
+
|
|
76
|
+
+ drag or middle mouse drag. The camera follows the pointer.
|
|
77
|
+
</accordion-item>
|
|
78
|
+
|
|
79
|
+
<accordion-item title="Dragging nodes">
|
|
80
|
+
Triggered by clicking and dragging a node. If the node is part of a selection, all selected nodes move. Grid and edge snapping produce alignment guides.
|
|
81
|
+
</accordion-item>
|
|
82
|
+
|
|
83
|
+
<accordion-item title="Resizing a node">
|
|
84
|
+
Triggered by dragging a resize handle (`n`, `ne`, `e`, `se`, `s`, `sw`, `w`, `nw`). Hold <kbd>
|
|
85
|
+
Shift
|
|
86
|
+
</kbd>
|
|
87
|
+
|
|
88
|
+
for aspect-ratio locking.
|
|
89
|
+
The default Vue handles are also keyboard-operable: focus a handle and use the
|
|
90
|
+
arrow keys to move its active edge by one grid step, or hold <kbd>
|
|
91
|
+
Shift
|
|
92
|
+
</kbd>
|
|
93
|
+
|
|
94
|
+
for a
|
|
95
|
+
major-grid step.
|
|
96
|
+
</accordion-item>
|
|
97
|
+
|
|
98
|
+
<accordion-item title="Box selection">
|
|
99
|
+
Triggered by clicking on empty canvas and dragging. By default, box selection follows AutoCAD semantics: drag left-to-right for window selection (node must be fully enclosed) and right-to-left for crossing selection (node only needs to intersect the box). Override this with `createBoardEngine({ boxSelect: { behavior: 'contain' | 'intersect' } })`.
|
|
100
|
+
</accordion-item>
|
|
101
|
+
|
|
102
|
+
<accordion-item title="Text editing">
|
|
103
|
+
Triggered by double-clicking a text node, pressing `Enter` with one text node selected, or choosing **Edit** in the selection toolbar. The built-in `BoardNode` shows a `<textarea>` overlay. File, link, and group renderers own any custom editing UI they provide.
|
|
104
|
+
</accordion-item>
|
|
105
|
+
</accordion>
|
|
106
|
+
|
|
107
|
+
## Pointer event flow
|
|
108
|
+
|
|
109
|
+
When a pointer event hits `BoardRoot`:
|
|
110
|
+
|
|
111
|
+
1. **Middle mouse / Space** starts panning.
|
|
112
|
+
2. **Resize handle** selects the node, then starts resizing after the drag threshold.
|
|
113
|
+
3. **Node** selects the node, then starts dragging after the drag threshold.
|
|
114
|
+
4. **Empty canvas** clears selection, then starts a box-selection session after the drag threshold.
|
|
115
|
+
|
|
116
|
+
Pointer movement is throttled through `requestAnimationFrame` and translated by `BoardRoot`; the low-level start, update, commit, and cancellation methods are absent from the normal engine API and available only through the unsupported first-party framework ABI. Pointer up commits the session, while pointer cancellation or Escape restores its starting geometry.
|
|
117
|
+
|
|
118
|
+
<note>
|
|
119
|
+
`BoardRoot` handles two-finger pinch gestures on touch devices automatically.
|
|
120
|
+
</note>
|
|
121
|
+
|
|
122
|
+
<collapsible>
|
|
123
|
+
### Text editing commands
|
|
124
|
+
|
|
125
|
+
`BoardRoot` exclusively owns pointer sessions. Applications can still control
|
|
126
|
+
the meaningful text-edit boundary directly:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
engine.beginTextEdit(nodeId)
|
|
130
|
+
engine.commitTextEdit(nodeId, 'New text')
|
|
131
|
+
engine.cancelTextEdit()
|
|
132
|
+
```
|
|
133
|
+
</collapsible>
|
|
134
|
+
|
|
135
|
+
<collapsible>
|
|
136
|
+
### Interaction events
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
engine.on('interaction:start', (state) => console.log('Started:', state.mode))
|
|
140
|
+
engine.on('interaction:update', (state) => {
|
|
141
|
+
/* high-frequency during drag */
|
|
142
|
+
})
|
|
143
|
+
engine.on('interaction:end', (state) => console.log('Ended:', state.mode))
|
|
144
|
+
```
|
|
145
|
+
</collapsible>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "SSR deterministic scene"
|
|
3
|
+
description: "Seed a board with initial nodes for server-side rendering and hydration."
|
|
4
|
+
url: "https://nuxt-board.lupinum.com/docs/build-features/ssr-and-deterministic-state"
|
|
5
|
+
route: "/docs/build-features/ssr-and-deterministic-state"
|
|
6
|
+
locale: "en"
|
|
7
|
+
section: "Documentation"
|
|
8
|
+
collection: "docs"
|
|
9
|
+
source: "ginko-content"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# SSR deterministic scene
|
|
13
|
+
|
|
14
|
+
> Seed a board with initial nodes for server-side rendering and hydration.
|
|
15
|
+
|
|
16
|
+
SSR only works if the server and client render the same scene. That means deterministic IDs, deterministic node order, and no random creation during setup.
|
|
17
|
+
|
|
18
|
+
<tabs>
|
|
19
|
+
<tab label="Server" icon="i-lucide-server">
|
|
20
|
+
```ts
|
|
21
|
+
const engine = createBoardEngine({
|
|
22
|
+
initialNodes: [
|
|
23
|
+
{
|
|
24
|
+
id: asNodeId('welcome'),
|
|
25
|
+
type: 'text',
|
|
26
|
+
x: 100,
|
|
27
|
+
y: 100,
|
|
28
|
+
width: 240,
|
|
29
|
+
height: 160,
|
|
30
|
+
text: 'Node',
|
|
31
|
+
zIndex: 1,
|
|
32
|
+
locked: false,
|
|
33
|
+
visible: true,
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
})
|
|
37
|
+
```
|
|
38
|
+
</tab>
|
|
39
|
+
|
|
40
|
+
<tab label="Client" icon="i-lucide-monitor">
|
|
41
|
+
```vue
|
|
42
|
+
<BoardRoot :engine="engine" style="height: 100vh" />
|
|
43
|
+
```
|
|
44
|
+
</tab>
|
|
45
|
+
</tabs>
|
|
46
|
+
|
|
47
|
+
<steps>
|
|
48
|
+
### Use deterministic IDs
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
id: asNodeId('welcome')
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Seed the full node records up front
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
initialNodes: [{ id, type, x, y, width, height, text, zIndex, locked, visible }]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Avoid random runtime creation during hydration
|
|
61
|
+
|
|
62
|
+
Do not call `createNode()` in a way that would diverge between server and client.
|
|
63
|
+
</steps>
|
|
64
|
+
|
|
65
|
+
<tip>
|
|
66
|
+
If the scene is bigger, build a deterministic JSON Canvas document once and import that document on both sides.
|
|
67
|
+
</tip>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Theming"
|
|
3
|
+
description: "Customize board appearance with CSS custom properties."
|
|
4
|
+
url: "https://nuxt-board.lupinum.com/docs/build-features/theming"
|
|
5
|
+
route: "/docs/build-features/theming"
|
|
6
|
+
locale: "en"
|
|
7
|
+
section: "Documentation"
|
|
8
|
+
collection: "docs"
|
|
9
|
+
source: "ginko-content"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Theming
|
|
13
|
+
|
|
14
|
+
> Customize board appearance with CSS custom properties.
|
|
15
|
+
|
|
16
|
+
Theme the board with CSS variables. Keep semantic state, such as node color presets, in engine state.
|
|
17
|
+
|
|
18
|
+
> Component omitted: `theme-playground`.
|
|
19
|
+
> This page contains an interactive or site-specific block that has no agent markdown serializer yet.
|
|
20
|
+
|
|
21
|
+
## Core tokens
|
|
22
|
+
|
|
23
|
+
| Token | Default | Purpose |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| `--board-bg` | `#fbfbfd` | Page and panel background token used by the board UI. |
|
|
26
|
+
| `--board-canvas-bg` | `#f5f6fa` | Canvas background behind nodes and overlays. |
|
|
27
|
+
| `--board-fg` | `#14161f` | Text, grid contrast, and general foreground color. |
|
|
28
|
+
| `--board-node-bg` | `#fff` | Default node surface. |
|
|
29
|
+
| `--board-node-border` | `#e4e5ee` | Default node border color. |
|
|
30
|
+
| `--board-accent` | `#6366e8` | Selection accents and snap guides. |
|
|
31
|
+
| `--board-node-color` | `unset` | Resolved border color for a colored node, usually set by the renderer from `node.color`. |
|
|
32
|
+
| `--board-node-tint` | `unset` | Subtle surface tint for colored cards and groups. |
|
|
33
|
+
| `--board-node-color-ring` | `unset` | Selection outline color for a colored node. |
|
|
34
|
+
| `--board-snap-guide-color` | `rgba(99, 102, 232, 0.95)` | Snap guide line color. |
|
|
35
|
+
|
|
36
|
+
## Light and dark examples
|
|
37
|
+
|
|
38
|
+
<tabs>
|
|
39
|
+
<tab label="Light" icon="i-lucide-sun">
|
|
40
|
+
```css
|
|
41
|
+
.board-root {
|
|
42
|
+
--board-bg: #f8fafc;
|
|
43
|
+
--board-fg: #0f172a;
|
|
44
|
+
--board-node-bg: #ffffff;
|
|
45
|
+
--board-node-border: rgba(15, 23, 42, 0.14);
|
|
46
|
+
--board-accent: #0ea5e9;
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
</tab>
|
|
50
|
+
|
|
51
|
+
<tab label="Dark" icon="i-lucide-moon">
|
|
52
|
+
```css
|
|
53
|
+
.board-root {
|
|
54
|
+
--board-bg: #0f172a;
|
|
55
|
+
--board-fg: #e2e8f0;
|
|
56
|
+
--board-node-bg: #162033;
|
|
57
|
+
--board-node-border: rgba(226, 232, 240, 0.12);
|
|
58
|
+
--board-accent: #22d3ee;
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
</tab>
|
|
62
|
+
</tabs>
|
|
63
|
+
|
|
64
|
+
## Node colors
|
|
65
|
+
|
|
66
|
+
Node colors are stored on `node.color` as preset IDs or six-digit hex colors:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
engine.updateNode(card.id, { color: '5' })
|
|
70
|
+
engine.updateNode(card.id, { color: undefined })
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The built-in renderer resolves those colors into `--board-node-color`, `--board-node-tint`, `--board-node-color-soft`, and `--board-node-color-ring`. Use those variables in custom renderers instead of reading preset IDs directly in CSS.
|
|
74
|
+
|
|
75
|
+
```css
|
|
76
|
+
.task-card {
|
|
77
|
+
border-color: var(--board-node-color, var(--board-node-border));
|
|
78
|
+
background: var(--board-node-tint, var(--board-node-bg));
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The selection toolbar can batch-apply the same preset to selected cards and groups.
|
|
83
|
+
|
|
84
|
+
<collapsible>
|
|
85
|
+
### Styling specific states
|
|
86
|
+
|
|
87
|
+
```css
|
|
88
|
+
.board-node.is-selected {
|
|
89
|
+
outline: calc(3px / var(--board-zoom, 1)) solid var(--board-accent);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.board-node.is-locked {
|
|
93
|
+
opacity: 0.72;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.board-node.is-group {
|
|
97
|
+
background: var(--board-node-tint, var(--board-group-bg));
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Use state classes when you want a special selected, locked, or group look without replacing the full renderer.
|
|
102
|
+
</collapsible>
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "History"
|
|
3
|
+
description: "Record state changes and navigate them with the history plugin."
|
|
4
|
+
url: "https://nuxt-board.lupinum.com/docs/build-features/undo-and-redo"
|
|
5
|
+
route: "/docs/build-features/undo-and-redo"
|
|
6
|
+
locale: "en"
|
|
7
|
+
section: "Documentation"
|
|
8
|
+
collection: "docs"
|
|
9
|
+
source: "ginko-content"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# History
|
|
13
|
+
|
|
14
|
+
> Record state changes and navigate them with the history plugin.
|
|
15
|
+
|
|
16
|
+
The history plugin stores references to committed structural roots. Undo and redo restore those roots atomically, without inverse actions, replay ordering, or timers. A completed gesture or explicit batch is one deterministic undo step.
|
|
17
|
+
|
|
18
|
+
## Setup
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { createBoardEngine } from '@lupinum/board-core'
|
|
22
|
+
import { historyPlugin } from '@lupinum/board-history'
|
|
23
|
+
|
|
24
|
+
const engine = createBoardEngine({
|
|
25
|
+
plugins: [historyPlugin({ maxSteps: 100 })],
|
|
26
|
+
})
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Render the shortcut component inside the matching board root:
|
|
30
|
+
|
|
31
|
+
```vue
|
|
32
|
+
<script setup lang="ts">
|
|
33
|
+
import { createBoardEngine } from '@lupinum/board-core'
|
|
34
|
+
import { historyPlugin } from '@lupinum/board-history'
|
|
35
|
+
import { BoardHistoryShortcuts } from '@lupinum/board-history/vue'
|
|
36
|
+
|
|
37
|
+
const engine = createBoardEngine({ plugins: [historyPlugin()] })
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<template>
|
|
41
|
+
<BoardRoot :engine="engine">
|
|
42
|
+
<BoardHistoryShortcuts :history="engine.plugins.history" />
|
|
43
|
+
</BoardRoot>
|
|
44
|
+
</template>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Plugin options
|
|
48
|
+
|
|
49
|
+
| Option | Type | Default | Purpose |
|
|
50
|
+
| --- | --- | --- | --- |
|
|
51
|
+
| `maxSteps` | `number` | `200` | Maximum undo steps to keep. Older entries are dropped. |
|
|
52
|
+
|
|
53
|
+
## Undo and redo
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
engine.plugins.history.undo()
|
|
57
|
+
engine.plugins.history.redo()
|
|
58
|
+
|
|
59
|
+
if (engine.plugins.history.canUndo()) {
|
|
60
|
+
/* ... */
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const state = engine.plugins.history.getState()
|
|
64
|
+
// { undoDepth, redoDepth }
|
|
65
|
+
|
|
66
|
+
engine.plugins.history.clear()
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Call `undo()` and `redo()` outside `engine.batch()`. Replay with a retained frame inside a batch throws `BoardConflictError` before changing the document or history. If that error leaves the batch, preceding edits roll back. Group new edits in a batch, then undo or redo the completed batch as one operation.
|
|
70
|
+
|
|
71
|
+
A rejected undo or redo retains its frame. Replay completes its history bookkeeping before notifying listeners, so an edit from a listener starts a new branch and clears redo.
|
|
72
|
+
|
|
73
|
+
History entries are available synchronously after a successful outer command. Read methods such as `canUndo()`, `canRedo()`, and `getState()` never mutate history state.
|
|
74
|
+
|
|
75
|
+
`BoardHistoryShortcuts` handles <kbd>
|
|
76
|
+
⌘
|
|
77
|
+
</kbd>
|
|
78
|
+
|
|
79
|
+
+ <kbd>
|
|
80
|
+
Z
|
|
81
|
+
</kbd>
|
|
82
|
+
|
|
83
|
+
to undo and <kbd>
|
|
84
|
+
⌘
|
|
85
|
+
</kbd>
|
|
86
|
+
|
|
87
|
+
+ <kbd>
|
|
88
|
+
Shift
|
|
89
|
+
</kbd>
|
|
90
|
+
|
|
91
|
+
+ <kbd>
|
|
92
|
+
Z
|
|
93
|
+
</kbd>
|
|
94
|
+
|
|
95
|
+
or <kbd>
|
|
96
|
+
⌘
|
|
97
|
+
</kbd>
|
|
98
|
+
|
|
99
|
+
+ <kbd>
|
|
100
|
+
Y
|
|
101
|
+
</kbd>
|
|
102
|
+
|
|
103
|
+
to redo. Pass the matching board's history API through its required `history` prop. It handles only events from its enclosing board.
|
|
104
|
+
|
|
105
|
+
<note>
|
|
106
|
+
When both `historyPlugin` and `connectionsPlugin` are installed, undoing a node deletion also restores its connected edges.
|
|
107
|
+
</note>
|
|
108
|
+
|
|
109
|
+
<collapsible>
|
|
110
|
+
### Ignored commands
|
|
111
|
+
|
|
112
|
+
History capture follows command metadata emitted by the engine and first-party features. Camera movement, transient interaction setup/teardown, imports, and selection-only commands are ignored; geometry and content mutations are recorded.
|
|
113
|
+
</collapsible>
|
|
114
|
+
|
|
115
|
+
<collapsible>
|
|
116
|
+
### Events
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
engine.on('history:push', (entry) => console.log('New step:', entry.label))
|
|
120
|
+
engine.on('history:undo', () => console.log('Undo'))
|
|
121
|
+
engine.on('history:redo', () => console.log('Redo'))
|
|
122
|
+
engine.on('history:clear', () => console.log('History cleared'))
|
|
123
|
+
```
|
|
124
|
+
</collapsible>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Design decisions"
|
|
3
|
+
description: "The constraints Nuxt Board chooses, the problems they solve, and their costs."
|
|
4
|
+
url: "https://nuxt-board.lupinum.com/docs/evaluate/design-decisions"
|
|
5
|
+
route: "/docs/evaluate/design-decisions"
|
|
6
|
+
locale: "en"
|
|
7
|
+
section: "Documentation"
|
|
8
|
+
collection: "docs"
|
|
9
|
+
source: "ginko-content"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Design decisions
|
|
13
|
+
|
|
14
|
+
> The constraints Nuxt Board chooses, the problems they solve, and their costs.
|
|
15
|
+
|
|
16
|
+
Nuxt Board limits its extension points to keep state ownership and failure behavior understandable.
|
|
17
|
+
|
|
18
|
+
## One engine owns board state
|
|
19
|
+
|
|
20
|
+
**Decision:** nodes, camera, grid, selection, hierarchy, and plugin slices belong to one engine.
|
|
21
|
+
|
|
22
|
+
**Benefit:** commands, snapshots, events, and persistence cannot silently use different board instances.
|
|
23
|
+
|
|
24
|
+
**Cost:** applications must adapt existing product data at a deliberate boundary instead of mirroring every field into Vue state.
|
|
25
|
+
|
|
26
|
+
## Commands are the mutation boundary
|
|
27
|
+
|
|
28
|
+
Persistent changes stage a candidate, validate it, and publish only after the outer transaction succeeds. The cost is that direct record mutation is unsupported. The benefit is atomic failure and one observable lifecycle.
|
|
29
|
+
|
|
30
|
+
## Document and session state are separate
|
|
31
|
+
|
|
32
|
+
Pointer gestures update transient geometry. Completion commits once; cancellation discards the override. Runtime and exported state can differ during a gesture. That difference keeps persistence and history stable.
|
|
33
|
+
|
|
34
|
+
## JSON Canvas node types are canonical
|
|
35
|
+
|
|
36
|
+
Custom renderers use existing `text`, `file`, `link`, and `group` records. This rules out arbitrary type strings, but prevents a second persistence schema.
|
|
37
|
+
|
|
38
|
+
## Plugins are first-party infrastructure
|
|
39
|
+
|
|
40
|
+
Connections and history install during engine construction. The unsupported `@lupinum/board-core/internal` ABI exists for separately published first-party packages, not application extensions.
|
|
41
|
+
|
|
42
|
+
## Nuxt owns no board behavior
|
|
43
|
+
|
|
44
|
+
`@lupinum/nuxt-board` registers imports and styles. Domain invariants stay below the framework boundary.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "How Nuxt Board works"
|
|
3
|
+
description: "The complete mental model from application intent to rendered board."
|
|
4
|
+
url: "https://nuxt-board.lupinum.com/docs/evaluate/how-nuxt-board-works"
|
|
5
|
+
route: "/docs/evaluate/how-nuxt-board-works"
|
|
6
|
+
locale: "en"
|
|
7
|
+
section: "Documentation"
|
|
8
|
+
collection: "docs"
|
|
9
|
+
source: "ginko-content"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# How Nuxt Board works
|
|
13
|
+
|
|
14
|
+
> The complete mental model from application intent to rendered board.
|
|
15
|
+
|
|
16
|
+
Nuxt Board follows one direct flow:
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
application intent
|
|
20
|
+
→ engine command
|
|
21
|
+
→ validated transaction
|
|
22
|
+
→ canonical board state
|
|
23
|
+
→ subscriptions
|
|
24
|
+
→ Vue rendering
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`createBoardEngine()` creates the source of truth. Application code changes it through commands. `BoardRoot` subscribes to the engine, renders effective state, and translates pointer and keyboard input through the framework adapter.
|
|
28
|
+
|
|
29
|
+
## Four boundaries
|
|
30
|
+
|
|
31
|
+
1. **Commands own persistent mutation.** Guards, validation, plugins, history, subscriptions, and events see the same commit.
|
|
32
|
+
2. **Session state stays transient.** Drag and resize updates render immediately without rewriting the persisted document on every pointer move.
|
|
33
|
+
3. **Renderers own presentation.** A renderer receives a node record. It does not create a competing state model.
|
|
34
|
+
4. **Packages own named capabilities.** Core owns nodes; connections owns edges; history owns undo stacks; Nuxt owns registration.
|
|
35
|
+
|
|
36
|
+
> Component omitted: `engine-command-lab`.
|
|
37
|
+
> This page contains an interactive or site-specific block that has no agent markdown serializer yet.
|
|
38
|
+
|
|
39
|
+
Run a command in the lab. The document changes before queued public events publish. Subscribers never receive an intermediate candidate.
|
|
40
|
+
|
|
41
|
+
The engine is mutable internally so commands remain direct. Its public records and snapshots are immutable contracts. Keep the engine out of Vue deep reactivity; use a stable instance or `shallowRef`.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Why Nuxt Board"
|
|
3
|
+
description: "The product problems Nuxt Board solves and the boundaries it keeps explicit."
|
|
4
|
+
url: "https://nuxt-board.lupinum.com/docs/evaluate/why-nuxt-board"
|
|
5
|
+
route: "/docs/evaluate/why-nuxt-board"
|
|
6
|
+
locale: "en"
|
|
7
|
+
section: "Documentation"
|
|
8
|
+
collection: "docs"
|
|
9
|
+
source: "ginko-content"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Why Nuxt Board
|
|
13
|
+
|
|
14
|
+
> The product problems Nuxt Board solves and the boundaries it keeps explicit.
|
|
15
|
+
|
|
16
|
+
Nuxt Board gives your application a board model that exists independently of the component tree. That matters when the canvas is part of a product rather than a disposable visualization.
|
|
17
|
+
|
|
18
|
+
## What it owns
|
|
19
|
+
|
|
20
|
+
The headless engine owns spatial state and behavior: nodes, hierarchy, camera, selection, commands, validation, events, and JSON Canvas persistence. `@lupinum/vue-board` owns DOM interaction and rendering. Optional packages own connections and history.
|
|
21
|
+
|
|
22
|
+
This separation gives application code one mutation path. A command can be guarded, validated, recorded in history, and observed without coordinating several Vue stores.
|
|
23
|
+
|
|
24
|
+
## What you keep
|
|
25
|
+
|
|
26
|
+
Your product owns business rules, backend execution, and domain data. Custom Vue renderers control how records look. Nuxt Board does not turn a planning card into a task service or a workflow node into an execution engine.
|
|
27
|
+
|
|
28
|
+
## Why the limits help
|
|
29
|
+
|
|
30
|
+
Nuxt Board supports the JSON Canvas node types: `text`, `file`, `link`, and `group`. Custom renderers change presentation without introducing another node schema. First-party plugins add bounded capabilities instead of an unrestricted extension system.
|
|
31
|
+
|
|
32
|
+
The cost is less speculative flexibility. The benefit is one document model, predictable persistence, and fewer invalid combinations.
|
|
33
|
+
|
|
34
|
+
Choose Nuxt Board when those boundaries match the system you want to maintain.
|
|
35
|
+
|
|
36
|
+
## Strong fit
|
|
37
|
+
|
|
38
|
+
- Users arrange, resize, group, select, or connect structured records.
|
|
39
|
+
- Board state must exist outside the Vue component tree.
|
|
40
|
+
- Mutations need guards, atomic batches, events, validation, or undo boundaries.
|
|
41
|
+
- Custom Vue components should render a stable document schema.
|
|
42
|
+
- JSON Canvas is a useful persistence or interchange format.
|
|
43
|
+
- Connections, history, and minimaps should remain optional.
|
|
44
|
+
|
|
45
|
+
## Look elsewhere when
|
|
46
|
+
|
|
47
|
+
- You only need a static graph renderer.
|
|
48
|
+
- Freehand drawing and vector illustration are the primary interaction.
|
|
49
|
+
- You want a complete collaborative whiteboard product rather than a library.
|
|
50
|
+
- Your graph is already modeled around another library's node and edge model.
|
|
51
|
+
- You need backend workflow execution; Nuxt Board only owns the editor surface.
|
|
52
|
+
|
|
53
|
+
Graph-first components such as Vue Flow are a better fit when handles and edges
|
|
54
|
+
define the product. Drawing libraries such as Konva or Fabric are a better fit
|
|
55
|
+
when shapes and low-level scene manipulation are the core abstraction. Complete
|
|
56
|
+
whiteboard SDKs fit products that value a prescribed toolset over a narrow
|
|
57
|
+
Vue-specific engine, while static renderers such as Mermaid fit non-interactive
|
|
58
|
+
diagrams.
|
|
59
|
+
|
|
60
|
+
The deciding question is where canonical state should live. Choose Nuxt Board
|
|
61
|
+
when you want a headless board document changed through explicit commands.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Contributing"
|
|
3
|
+
description: "Set up the repo, run the docs, and make changes that are ready to review."
|
|
4
|
+
url: "https://nuxt-board.lupinum.com/docs/project/contributing"
|
|
5
|
+
route: "/docs/project/contributing"
|
|
6
|
+
locale: "en"
|
|
7
|
+
section: "Documentation"
|
|
8
|
+
collection: "docs"
|
|
9
|
+
source: "ginko-content"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Contributing
|
|
13
|
+
|
|
14
|
+
> Set up the repo, run the docs, and make changes that are ready to review.
|
|
15
|
+
|
|
16
|
+
Contributions are welcome when they keep the library easier to use, test, and maintain.
|
|
17
|
+
|
|
18
|
+
## Set up the repository
|
|
19
|
+
|
|
20
|
+
The canonical contribution policy is [CONTRIBUTING.md](https://github.com/lupinum-dev/nuxt-board/blob/main/CONTRIBUTING.md). From the repository root:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
corepack enable
|
|
24
|
+
pnpm install
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Start the playground or docs app:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pnpm dev:playground
|
|
31
|
+
pnpm dev:docs
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The playground is the fastest place to verify interaction changes. The docs app uses the workspace packages, so examples exercise the same code consumers install.
|
|
35
|
+
|
|
36
|
+
## Run the checks
|
|
37
|
+
|
|
38
|
+
Run the narrowest relevant check while you work, then the repository gate before handoff:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pnpm verify
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Use `pnpm test:e2e` for browser interaction or screenshot changes. Maintainers use `pnpm release:verify` only on a release candidate.
|
|
45
|
+
|
|
46
|
+
## Where docs live
|
|
47
|
+
|
|
48
|
+
Docs content lives in `docs/content`.
|
|
49
|
+
|
|
50
|
+
| Folder | Purpose |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| `1.evaluate` | Product fit and architecture orientation. |
|
|
53
|
+
| `2.start-building` | Installation and first working board. |
|
|
54
|
+
| `3.understand-the-system` | State, commands, coordinates, and data. |
|
|
55
|
+
| `4.build-features` | Task-focused feature guides. |
|
|
56
|
+
| `5.solutions` | Complete application examples. |
|
|
57
|
+
| `6.reference` | Maintained public API contracts. |
|
|
58
|
+
| `7.project` | Contribution, support, and security. |
|
|
59
|
+
|
|
60
|
+
## Documentation standards
|
|
61
|
+
|
|
62
|
+
Write each page for one reader and one job. A quickstart should not become a reference page, and a reference page should not become a tutorial.
|
|
63
|
+
|
|
64
|
+
Before shipping docs, check:
|
|
65
|
+
|
|
66
|
+
- The first paragraph says what the page helps the reader do or understand.
|
|
67
|
+
- Examples include imports and setup when needed.
|
|
68
|
+
- Claims about defaults, fields, commands, and behavior match source or tests.
|
|
69
|
+
- Links describe the destination.
|
|
70
|
+
- The page ends with a useful next step.
|
|
71
|
+
|
|
72
|
+
## When code needs docs
|
|
73
|
+
|
|
74
|
+
Update docs when a change affects:
|
|
75
|
+
|
|
76
|
+
- exported package APIs
|
|
77
|
+
- node, edge, selection, camera, or import/export behavior
|
|
78
|
+
- Nuxt module options or auto-imports
|
|
79
|
+
- default UI behavior
|
|
80
|
+
- migration steps for existing users
|
|
81
|
+
- setup commands or package names
|
|
82
|
+
|
|
83
|
+
For publishable package changes, add a changeset:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pnpm changeset
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Pull request shape
|
|
90
|
+
|
|
91
|
+
A reviewable change has a narrow scope, tests for changed behavior, and docs for user-visible changes.
|
|
92
|
+
|
|
93
|
+
Include screenshots or short recordings for visual interaction changes. For API docs, include the source change and matching reference update together.
|
|
94
|
+
|
|
95
|
+
## Documentation voice
|
|
96
|
+
|
|
97
|
+
Write in a calm, direct, technically honest voice. Lead with the answer. Name the package that owns a capability. Explain non-obvious constraints through decision, reason, benefit, cost, and consequence.
|
|
98
|
+
|
|
99
|
+
Use product-direct writing for evaluation, principle-led writing for concepts, task-first writing for guides, contract-style writing for reference, and neutral analytical writing for comparisons.
|
|
100
|
+
|
|
101
|
+
Do not hide required facts inside a demo, tab, or accordion. Every interactive lab needs a textual objective, expected result, invariant, and relevant consumer code.
|