@stephen-netu/design-system 0.1.0
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 +66 -0
- package/package.json +130 -0
- package/src/b0-app-shell/clarification-overlay/ClarificationOverlay.svelte +221 -0
- package/src/b0-app-shell/clarification-overlay/index.ts +1 -0
- package/src/b0-app-shell/command-bar/CommandBar.svelte +565 -0
- package/src/b0-app-shell/index.ts +14 -0
- package/src/base.css +152 -0
- package/src/d0-data-viz/BarChart.svelte +236 -0
- package/src/d0-data-viz/CheckpointBar.svelte +574 -0
- package/src/d0-data-viz/CheckpointPill.svelte +297 -0
- package/src/d0-data-viz/MetricCard.svelte +159 -0
- package/src/d0-data-viz/SparkLine.svelte +66 -0
- package/src/d0-data-viz/StorageMap.svelte +182 -0
- package/src/d0-data-viz/Timeline.svelte +188 -0
- package/src/d0-data-viz/checkpoint-types.ts +162 -0
- package/src/d0-data-viz/d0-tokens.css +32 -0
- package/src/d0-data-viz/index.ts +58 -0
- package/src/d0-data-viz/storage-map-types.ts +142 -0
- package/src/d0-data-viz/theme-integration.ts +134 -0
- package/src/d0-data-viz/types.ts +53 -0
- package/src/demos/BspSizingInfographic.svelte +386 -0
- package/src/demos/BspZoneDemo.svelte +255 -0
- package/src/demos/ControlRoomDemo.svelte +570 -0
- package/src/demos/NodepadBlocksShowcase.svelte +189 -0
- package/src/demos/index.ts +7 -0
- package/src/editor/CodeEditor.svelte +188 -0
- package/src/editor/FindReplaceDialog.svelte +516 -0
- package/src/editor/MarkdownEditor.svelte +660 -0
- package/src/editor/index.ts +2 -0
- package/src/entity-graph-view/WorldView.svelte +171 -0
- package/src/entity-graph-view/index.ts +6 -0
- package/src/entity-graph-view/types.ts +364 -0
- package/src/epistemic/index.ts +2 -0
- package/src/epistemic/types.ts +134 -0
- package/src/epistemic/utils.ts +32 -0
- package/src/f0-forms/checkbox/Checkbox.svelte +158 -0
- package/src/f0-forms/field/FormField.svelte +91 -0
- package/src/f0-forms/index.ts +11 -0
- package/src/f0-forms/radio/RadioGroup.svelte +192 -0
- package/src/f0-forms/search/SearchBar.svelte +99 -0
- package/src/f0-forms/select/Select.svelte +198 -0
- package/src/f0-forms/textarea/TextArea.svelte +170 -0
- package/src/f0-forms/toggle/FormToggle.svelte +147 -0
- package/src/f0-forms/validation/validate.ts +47 -0
- package/src/fabric/ai/ChatPanel.svelte +648 -0
- package/src/fabric/ai/index.ts +11 -0
- package/src/fabric/data/index.ts +21 -0
- package/src/fabric/feedback/StatusBar.svelte +274 -0
- package/src/fabric/feedback/ToastContainer.svelte +122 -0
- package/src/fabric/feedback/index.ts +14 -0
- package/src/fabric/index.ts +37 -0
- package/src/fabric/input/CommandBar.svelte +79 -0
- package/src/fabric/input/KeyboardShortcuts.svelte +388 -0
- package/src/fabric/input/index.ts +12 -0
- package/src/fabric/layout/GlassBox.svelte +193 -0
- package/src/fabric/layout/KanbanBoard.svelte +158 -0
- package/src/fabric/layout/KanbanCard.svelte +226 -0
- package/src/fabric/layout/KanbanColumn.svelte +278 -0
- package/src/fabric/layout/index.ts +17 -0
- package/src/fabric/navigation/ActivityRail.svelte +271 -0
- package/src/fabric/navigation/index.ts +11 -0
- package/src/g0-geo/SovereignMap.svelte +149 -0
- package/src/g0-geo/index.ts +2 -0
- package/src/index.ts +68 -0
- package/src/l0-layout/block-writer/BlockConnectionLines.svelte +98 -0
- package/src/l0-layout/block-writer/BlockContent.svelte +213 -0
- package/src/l0-layout/block-writer/BlockHeader.svelte +230 -0
- package/src/l0-layout/block-writer/BlockItem.svelte +177 -0
- package/src/l0-layout/block-writer/BlockWriter.svelte +219 -0
- package/src/l0-layout/block-writer/block-writer-types.ts +63 -0
- package/src/l0-layout/block-writer/index.ts +24 -0
- package/src/l0-layout/block-writer/markdown.ts +110 -0
- package/src/l0-layout/chat-view/ChatViewLayout.svelte +61 -0
- package/src/l0-layout/epistemic/AllayDetailPanel.svelte +113 -0
- package/src/l0-layout/epistemic/AssayDetailPanel.svelte +164 -0
- package/src/l0-layout/epistemic/AvenueTrace.svelte +253 -0
- package/src/l0-layout/epistemic/KnowledgeDetailPanel.svelte +204 -0
- package/src/l0-layout/epistemic/ParadigmLens.svelte +170 -0
- package/src/l0-layout/epistemic/ProvenanceChain.svelte +164 -0
- package/src/l0-layout/epistemic/TrustView.svelte +195 -0
- package/src/l0-layout/epistemic/index.ts +7 -0
- package/src/l0-layout/grove/AccordCard.svelte +156 -0
- package/src/l0-layout/grove/GroveCard.svelte +197 -0
- package/src/l0-layout/grove/MemberBadge.svelte +96 -0
- package/src/l0-layout/grove/MemberList.svelte +59 -0
- package/src/l0-layout/grove/grove-types.ts +29 -0
- package/src/l0-layout/grove/index.ts +14 -0
- package/src/l0-layout/index.ts +45 -0
- package/src/l0-layout/kanban/KanbanBoard.svelte +71 -0
- package/src/l0-layout/kanban/KanbanCard.svelte +276 -0
- package/src/l0-layout/kanban/KanbanColumn.svelte +198 -0
- package/src/l0-layout/kanban/KanbanViewLayout.svelte +61 -0
- package/src/l0-layout/workspace/WorkspaceLayout.svelte +93 -0
- package/src/n0-node-graph/GraphEditor.svelte +342 -0
- package/src/n0-node-graph/GraphHistory.svelte +122 -0
- package/src/n0-node-graph/Node.svelte +269 -0
- package/src/n0-node-graph/NodeCanvas.svelte +187 -0
- package/src/n0-node-graph/NodeEdge.svelte +152 -0
- package/src/n0-node-graph/NodePalette.svelte +318 -0
- package/src/n0-node-graph/NodePort.svelte +128 -0
- package/src/n0-node-graph/index.ts +33 -0
- package/src/n0-node-graph/n0-tokens.css +29 -0
- package/src/n0-node-graph/node-port-types.ts +19 -0
- package/src/n0-node-graph/types.ts +96 -0
- package/src/n0-node-graph/validate.test.ts +722 -0
- package/src/n0-node-graph/validate.ts +371 -0
- package/src/n1-flow-canvas/FlowCanvas.svelte +202 -0
- package/src/n1-flow-canvas/graph-state.svelte.ts +95 -0
- package/src/n1-flow-canvas/index.ts +21 -0
- package/src/n1-flow-canvas/layout.ts +118 -0
- package/src/n1-flow-canvas/layout.worker.ts +128 -0
- package/src/n1-flow-canvas/types.ts +102 -0
- package/src/n1-flow-canvas/utils.ts +146 -0
- package/src/n2-manifold-disk/ManifoldDisk.svelte +314 -0
- package/src/n2-manifold-disk/index.ts +6 -0
- package/src/n2-manifold-disk/poincare.ts +184 -0
- package/src/n3-force-graph/ForceGraph.svelte +180 -0
- package/src/n3-force-graph/GraphTooltip.svelte +99 -0
- package/src/n3-force-graph/animated-edge.ts +107 -0
- package/src/n3-force-graph/health-node.ts +128 -0
- package/src/n3-force-graph/index.ts +31 -0
- package/src/n3-force-graph/types.ts +94 -0
- package/src/n4-adaptive-manifold/AdaptiveManifold.svelte +1165 -0
- package/src/n4-adaptive-manifold/DetailCard.svelte +212 -0
- package/src/n4-adaptive-manifold/SuggestionIndicator.svelte +91 -0
- package/src/n4-adaptive-manifold/classifier.ts +139 -0
- package/src/n4-adaptive-manifold/index.ts +45 -0
- package/src/n4-adaptive-manifold/n4-tokens.css +155 -0
- package/src/n4-adaptive-manifold/orchestrator.ts +301 -0
- package/src/n4-adaptive-manifold/phase-theme.ts +92 -0
- package/src/n4-adaptive-manifold/pinnacle-events.ts +135 -0
- package/src/n4-adaptive-manifold/providers/gravitational-provider.ts +191 -0
- package/src/n4-adaptive-manifold/providers/manifold-provider.ts +276 -0
- package/src/n4-adaptive-manifold/transition.ts +90 -0
- package/src/n4-adaptive-manifold/types.ts +313 -0
- package/src/p0-primitives/index.ts +6 -0
- package/src/p0-primitives/tokens/control-room.css +309 -0
- package/src/p0-primitives/tokens/index.ts +6 -0
- package/src/p0-primitives/tokens/tokens.css +140 -0
- package/src/p0-primitives/tokens/tokens.json +132 -0
- package/src/p0-primitives/types/index.ts +123 -0
- package/src/p0-primitives/types/plugin-types.ts +74 -0
- package/src/p0-primitives/utils/index.ts +39 -0
- package/src/platform/hud-protocol.ts +20 -0
- package/src/platform/index.ts +3 -0
- package/src/s0-state/create-store.ts +84 -0
- package/src/s0-state/derived.ts +41 -0
- package/src/s0-state/index.ts +6 -0
- package/src/showcase/Showcase.svelte +401 -0
- package/src/spatial/BspTilingCanvas.svelte +232 -0
- package/src/spatial/ForceCanvas.svelte +289 -0
- package/src/spatial/LodRenderer.svelte +334 -0
- package/src/spatial/SnapZoomCamera.svelte +185 -0
- package/src/spatial/ZoneTiler.svelte +311 -0
- package/src/spatial/camera-types.ts +45 -0
- package/src/spatial/content-proportional-bsp.ts +253 -0
- package/src/spatial/force-canvas-types.ts +102 -0
- package/src/spatial/index.ts +15 -0
- package/src/spatial/lod-types.ts +70 -0
- package/src/spatial/zone-tiler-types.ts +177 -0
- package/src/stores/vaultStore.svelte +14 -0
- package/src/t0-transport/connection.ts +34 -0
- package/src/t0-transport/error.ts +37 -0
- package/src/t0-transport/events.ts +50 -0
- package/src/t0-transport/index.ts +6 -0
- package/src/t0-transport/invoke.ts +30 -0
- package/src/tauri/index.ts +13 -0
- package/src/u0-primitives/allay-gauge/AllayGauge.svelte +90 -0
- package/src/u0-primitives/allay-gauge/index.ts +1 -0
- package/src/u0-primitives/animated-icon/AnimatedIcon.svelte +273 -0
- package/src/u0-primitives/animated-icon/README.md +53 -0
- package/src/u0-primitives/animated-icon/index.ts +31 -0
- package/src/u0-primitives/assay-badge/AssayBadge.svelte +121 -0
- package/src/u0-primitives/assay-badge/AssaySignalBar.svelte +83 -0
- package/src/u0-primitives/assay-badge/index.ts +2 -0
- package/src/u0-primitives/avatar/Avatar.svelte +177 -0
- package/src/u0-primitives/badge/Badge.svelte +149 -0
- package/src/u0-primitives/button/Button.svelte +304 -0
- package/src/u0-primitives/card/Card.svelte +182 -0
- package/src/u0-primitives/dropdown/Dropdown.svelte +305 -0
- package/src/u0-primitives/expert-badge/ExpertBadge.svelte +89 -0
- package/src/u0-primitives/expert-badge/index.ts +1 -0
- package/src/u0-primitives/index.ts +35 -0
- package/src/u0-primitives/input/Input.svelte +237 -0
- package/src/u0-primitives/modal/Modal.svelte +331 -0
- package/src/u0-primitives/modal/index.ts +1 -0
- package/src/u0-primitives/spinner/Spinner.svelte +129 -0
- package/src/u0-primitives/tabs/Tabs.svelte +225 -0
- package/src/u0-primitives/tabs/tabs-types.ts +11 -0
- package/src/u0-primitives/toggle/Toggle.svelte +161 -0
- package/src/u0-primitives/tooltip/Tooltip.svelte +234 -0
- package/src/v0-diagrams/DiagramPanel.svelte +68 -0
- package/src/v0-diagrams/MarkmapDiagram.svelte +56 -0
- package/src/v0-diagrams/MermaidDiagram.svelte +53 -0
- package/src/v0-diagrams/index.ts +4 -0
- package/src/v0-diagrams/types.ts +28 -0
- package/src/v0-diagrams/v0-tokens.css +6 -0
- package/src/vite-env.d.ts +7 -0
- package/src/x0-enchanted-blocks/ENCHANTED_BLOCKS_SPEC.md +457 -0
- package/src/x0-enchanted-blocks/components/EnchantedBlock.svelte +485 -0
- package/src/x0-enchanted-blocks/components/NodepadBlock.svelte +262 -0
- package/src/x0-enchanted-blocks/core/block-layout.ts +211 -0
- package/src/x0-enchanted-blocks/core/block-types.ts +227 -0
- package/src/x0-enchanted-blocks/index.ts +46 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// NodepadBlock — Flat, inline-editing block inspired by Nodepad spatial notes
|
|
3
|
+
// Key differences from bad first attempt:
|
|
4
|
+
// - NO nested card containers (single flat element)
|
|
5
|
+
// - NO edit mode toggle (just click and type)
|
|
6
|
+
// - NO collapse button (content flows naturally)
|
|
7
|
+
// - Stable cursor (no $effect overwriting content while typing)
|
|
8
|
+
|
|
9
|
+
import { measureBlock, type BlockMetrics, DEFAULT_FONT } from '../core/block-layout.js';
|
|
10
|
+
import {
|
|
11
|
+
type BlockType,
|
|
12
|
+
type EnchantedBlockData,
|
|
13
|
+
BLOCK_TYPE_COLORS,
|
|
14
|
+
BLOCK_TYPE_ICONS,
|
|
15
|
+
classifyBlockHeuristic,
|
|
16
|
+
} from '../core/block-types.js';
|
|
17
|
+
|
|
18
|
+
interface Props {
|
|
19
|
+
block: EnchantedBlockData;
|
|
20
|
+
maxWidth?: number;
|
|
21
|
+
onChange?: (content: string) => void;
|
|
22
|
+
onTypeChange?: (type: BlockType) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let {
|
|
26
|
+
block,
|
|
27
|
+
maxWidth = 320,
|
|
28
|
+
onChange,
|
|
29
|
+
onTypeChange,
|
|
30
|
+
}: Props = $props();
|
|
31
|
+
|
|
32
|
+
// ── Stable Local State ─────────────────────────────────────────────────────
|
|
33
|
+
// CRITICAL: Local content copy prevents parent re-renders from resetting cursor
|
|
34
|
+
let localContent = $state('');
|
|
35
|
+
let isFocused = $state(false);
|
|
36
|
+
let hasUnsavedChanges = $state(false);
|
|
37
|
+
let contentEl: HTMLDivElement | undefined = $state();
|
|
38
|
+
|
|
39
|
+
// Initialize content on mount (once only)
|
|
40
|
+
$effect(() => {
|
|
41
|
+
if (contentEl && localContent === '') {
|
|
42
|
+
localContent = block.content;
|
|
43
|
+
contentEl.innerText = block.content;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// ── Derived ────────────────────────────────────────────────────────────────
|
|
48
|
+
const metrics = $derived<BlockMetrics>(
|
|
49
|
+
measureBlock(localContent, DEFAULT_FONT, maxWidth, { whiteSpace: 'pre-wrap' })
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const suggestedType = $derived(
|
|
53
|
+
block.type === 'general' && localContent.length > 10
|
|
54
|
+
? classifyBlockHeuristic(localContent)
|
|
55
|
+
: null
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const displayType = $derived(suggestedType?.type ?? block.type);
|
|
59
|
+
const typeColor = $derived(BLOCK_TYPE_COLORS[displayType]);
|
|
60
|
+
const typeIcon = $derived(BLOCK_TYPE_ICONS[displayType]);
|
|
61
|
+
|
|
62
|
+
// ── Handlers ─────────────────────────────────────────────────────────────────
|
|
63
|
+
function handleInput() {
|
|
64
|
+
if (!contentEl) return;
|
|
65
|
+
localContent = contentEl.innerText;
|
|
66
|
+
hasUnsavedChanges = true;
|
|
67
|
+
// Auto-save on input (debounced by parent or just stream it)
|
|
68
|
+
onChange?.(localContent);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function handleFocus() {
|
|
72
|
+
isFocused = true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function handleBlur() {
|
|
76
|
+
isFocused = false;
|
|
77
|
+
hasUnsavedChanges = false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function cycleType() {
|
|
81
|
+
const types: BlockType[] = ['claim', 'question', 'idea', 'task', 'reference', 'general'];
|
|
82
|
+
const currentIndex = types.indexOf(block.type);
|
|
83
|
+
const nextType = types[(currentIndex + 1) % types.length];
|
|
84
|
+
onTypeChange?.(nextType);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
88
|
+
// Tab inserts spaces
|
|
89
|
+
if (e.key === 'Tab') {
|
|
90
|
+
e.preventDefault();
|
|
91
|
+
document.execCommand('insertText', false, ' ');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
</script>
|
|
95
|
+
|
|
96
|
+
<div
|
|
97
|
+
class="nodepad-block"
|
|
98
|
+
style:--type-color={typeColor}
|
|
99
|
+
style:max-width="{maxWidth}px"
|
|
100
|
+
class:suggested={!!suggestedType && block.type === 'general'}
|
|
101
|
+
>
|
|
102
|
+
<!-- Header: Type indicator + ID -->
|
|
103
|
+
<div class="block-header">
|
|
104
|
+
<button class="type-chip" onclick={cycleType} title="Click to change type">
|
|
105
|
+
<span class="type-icon">{typeIcon}</span>
|
|
106
|
+
<span class="type-name">{displayType}</span>
|
|
107
|
+
</button>
|
|
108
|
+
<span class="block-id">{block.id}</span>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<!-- Content: Always editable (like Nodepad) -->
|
|
112
|
+
<!-- CRITICAL: No {block.content} binding here - we set innerText manually to prevent cursor reset -->
|
|
113
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
114
|
+
<div
|
|
115
|
+
bind:this={contentEl}
|
|
116
|
+
class="block-content"
|
|
117
|
+
contenteditable="true"
|
|
118
|
+
role="textbox"
|
|
119
|
+
aria-multiline="true"
|
|
120
|
+
tabindex="0"
|
|
121
|
+
oninput={handleInput}
|
|
122
|
+
onfocus={handleFocus}
|
|
123
|
+
onblur={handleBlur}
|
|
124
|
+
onkeydown={handleKeydown}
|
|
125
|
+
></div>
|
|
126
|
+
|
|
127
|
+
<!-- Footer: Annotation if present -->
|
|
128
|
+
{#if block.annotation}
|
|
129
|
+
<div class="block-annotation">
|
|
130
|
+
<span class="annotation-dot">•</span>
|
|
131
|
+
{block.annotation}
|
|
132
|
+
</div>
|
|
133
|
+
{/if}
|
|
134
|
+
|
|
135
|
+
<!-- Saving indicator -->
|
|
136
|
+
{#if hasUnsavedChanges}
|
|
137
|
+
<div class="save-indicator">•</div>
|
|
138
|
+
{/if}
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<style>
|
|
142
|
+
.nodepad-block {
|
|
143
|
+
/* FLAT: No nested containers, no card styling - just the essentials */
|
|
144
|
+
position: relative;
|
|
145
|
+
padding: 12px 14px;
|
|
146
|
+
background: var(--color-bg-elevated, #252422);
|
|
147
|
+
border-left: 3px solid var(--type-color, #b87333);
|
|
148
|
+
border-radius: 0 6px 6px 0;
|
|
149
|
+
/* CRITICAL: Constant dimensions - no size change on focus/hover */
|
|
150
|
+
transition: box-shadow 0.15s ease, border-color 0.15s ease;
|
|
151
|
+
box-sizing: border-box;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.nodepad-block:hover {
|
|
155
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.nodepad-block:focus-within {
|
|
159
|
+
/* Use outline instead of border change to avoid layout shift */
|
|
160
|
+
outline: 2px solid color-mix(in srgb, var(--type-color, #b87333) 50%, transparent);
|
|
161
|
+
outline-offset: 2px;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.nodepad-block.suggested {
|
|
165
|
+
border-left-style: dashed;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* Header */
|
|
169
|
+
.block-header {
|
|
170
|
+
display: flex;
|
|
171
|
+
align-items: center;
|
|
172
|
+
justify-content: space-between;
|
|
173
|
+
margin-bottom: 8px;
|
|
174
|
+
gap: 8px;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.type-chip {
|
|
178
|
+
display: inline-flex;
|
|
179
|
+
align-items: center;
|
|
180
|
+
gap: 4px;
|
|
181
|
+
padding: 2px 8px;
|
|
182
|
+
background: color-mix(in srgb, var(--type-color, #b87333) 15%, transparent);
|
|
183
|
+
border: 1px solid color-mix(in srgb, var(--type-color, #b87333) 25%, transparent);
|
|
184
|
+
border-radius: 4px;
|
|
185
|
+
font-size: 11px;
|
|
186
|
+
font-weight: 500;
|
|
187
|
+
color: var(--type-color, #b87333);
|
|
188
|
+
cursor: pointer;
|
|
189
|
+
transition: all 0.15s ease;
|
|
190
|
+
font-family: inherit;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.type-chip:hover {
|
|
194
|
+
background: color-mix(in srgb, var(--type-color, #b87333) 25%, transparent);
|
|
195
|
+
border-color: color-mix(in srgb, var(--type-color, #b87333) 50%, transparent);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.type-icon {
|
|
199
|
+
font-size: 10px;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.type-name {
|
|
203
|
+
text-transform: capitalize;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.block-id {
|
|
207
|
+
font-size: 11px;
|
|
208
|
+
color: var(--color-text-muted, #706858);
|
|
209
|
+
font-family: var(--font-mono, monospace);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* Content - The key: just a contenteditable div, no wrapper, no truncation */
|
|
213
|
+
.block-content {
|
|
214
|
+
font-size: 14px;
|
|
215
|
+
line-height: 1.6;
|
|
216
|
+
color: var(--color-text-primary, #e8e4dc);
|
|
217
|
+
white-space: pre-wrap;
|
|
218
|
+
word-break: break-word;
|
|
219
|
+
min-height: 1.6em;
|
|
220
|
+
outline: none;
|
|
221
|
+
caret-color: var(--type-color, #b87333);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.block-content:empty::before {
|
|
225
|
+
content: 'Click to add content...';
|
|
226
|
+
color: var(--color-text-muted, #706858);
|
|
227
|
+
font-style: italic;
|
|
228
|
+
pointer-events: none;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* Annotation */
|
|
232
|
+
.block-annotation {
|
|
233
|
+
margin-top: 8px;
|
|
234
|
+
padding-top: 8px;
|
|
235
|
+
border-top: 1px solid var(--color-border, #333);
|
|
236
|
+
font-size: 12px;
|
|
237
|
+
color: var(--color-text-secondary, #a09880);
|
|
238
|
+
font-style: italic;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.annotation-dot {
|
|
242
|
+
color: var(--type-color, #b87333);
|
|
243
|
+
margin-right: 4px;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/* Save indicator (subtle dot when typing) */
|
|
247
|
+
.save-indicator {
|
|
248
|
+
position: absolute;
|
|
249
|
+
top: 6px;
|
|
250
|
+
right: 6px;
|
|
251
|
+
width: 6px;
|
|
252
|
+
height: 6px;
|
|
253
|
+
border-radius: 50%;
|
|
254
|
+
background: var(--type-color, #b87333);
|
|
255
|
+
animation: pulse 1.5s infinite;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
@keyframes pulse {
|
|
259
|
+
0%, 100% { opacity: 1; }
|
|
260
|
+
50% { opacity: 0.4; }
|
|
261
|
+
}
|
|
262
|
+
</style>
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// Block Layout Engine — DOM-free text measurement using Pretext
|
|
2
|
+
// Provides accurate sizing for adaptive block rendering
|
|
3
|
+
|
|
4
|
+
import { prepareWithSegments, layout, measureNaturalWidth as pretextMeasureNatural } from '@chenglou/pretext';
|
|
5
|
+
|
|
6
|
+
/** Layout options for text measurement */
|
|
7
|
+
export interface LayoutOptions {
|
|
8
|
+
/** White space handling: 'normal' (collapse) or 'pre-wrap' (preserve) */
|
|
9
|
+
whiteSpace?: 'normal' | 'pre-wrap';
|
|
10
|
+
/** Word breaking: 'normal' (break at word boundaries) or 'keep-all' (CJK/Hangul) */
|
|
11
|
+
wordBreak?: 'normal' | 'keep-all';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Metrics returned from block measurement */
|
|
15
|
+
export interface BlockMetrics {
|
|
16
|
+
/** Minimum width before text wraps to additional lines (shrink-wrap) */
|
|
17
|
+
minWidth: number;
|
|
18
|
+
/** Optimal reading width (~65 characters) */
|
|
19
|
+
idealWidth: number;
|
|
20
|
+
/** Total height at the measured width */
|
|
21
|
+
height: number;
|
|
22
|
+
/** Number of lines at the measured width */
|
|
23
|
+
lineCount: number;
|
|
24
|
+
/** True if content exceeds reasonable display limits */
|
|
25
|
+
hasOverflow: boolean;
|
|
26
|
+
/** The font string used for measurement */
|
|
27
|
+
font: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Default font configuration matching KOS design system */
|
|
31
|
+
export const DEFAULT_FONT = '14px "Sovereign Sans", system-ui, -apple-system, sans-serif';
|
|
32
|
+
|
|
33
|
+
/** Default monospace font for code/preformatted content */
|
|
34
|
+
export const DEFAULT_MONO_FONT = '13px "Sovereign Mono", "SF Mono", Monaco, "Cascadia Code", monospace';
|
|
35
|
+
|
|
36
|
+
/** Ideal reading width in pixels (~65ch at 14px) */
|
|
37
|
+
export const IDEAL_READING_WIDTH = 520;
|
|
38
|
+
|
|
39
|
+
/** Maximum reasonable block height before overflow indicator */
|
|
40
|
+
export const MAX_BLOCK_HEIGHT = 400;
|
|
41
|
+
|
|
42
|
+
/** Maximum characters before overflow detection */
|
|
43
|
+
export const MAX_CONTENT_LENGTH = 5000;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Measure block content using Pretext's DOM-free layout engine.
|
|
47
|
+
*
|
|
48
|
+
* This function uses canvas-based text measurement for accurate sizing
|
|
49
|
+
* without triggering DOM layout/reflow cycles.
|
|
50
|
+
*
|
|
51
|
+
* @param content - The text content to measure
|
|
52
|
+
* @param font - Font specification string (defaults to DEFAULT_FONT)
|
|
53
|
+
* @param maxWidth - Maximum container width constraint
|
|
54
|
+
* @param options - Layout options (whiteSpace, wordBreak)
|
|
55
|
+
* @param lineHeight - Line height multiplier (default: 1.6)
|
|
56
|
+
* @returns BlockMetrics with sizing information
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* const metrics = measureBlock(
|
|
61
|
+
* 'Task: Implement authentication system with OAuth2 support',
|
|
62
|
+
* '14px Inter',
|
|
63
|
+
* 400,
|
|
64
|
+
* { whiteSpace: 'normal' }
|
|
65
|
+
* );
|
|
66
|
+
* // metrics.height → precise pixel height
|
|
67
|
+
* // metrics.lineCount → number of wrapped lines
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export function measureBlock(
|
|
71
|
+
content: string,
|
|
72
|
+
font: string = DEFAULT_FONT,
|
|
73
|
+
maxWidth: number = IDEAL_READING_WIDTH,
|
|
74
|
+
options: LayoutOptions = {},
|
|
75
|
+
lineHeight: number = 1.6
|
|
76
|
+
): BlockMetrics {
|
|
77
|
+
// SSR Safety: Pretext requires DOM/Canvas. Return safe defaults if run on server.
|
|
78
|
+
if (typeof window === 'undefined') {
|
|
79
|
+
return {
|
|
80
|
+
minWidth: 0,
|
|
81
|
+
idealWidth: Math.min(IDEAL_READING_WIDTH, maxWidth),
|
|
82
|
+
height: lineHeight * 14,
|
|
83
|
+
lineCount: 1,
|
|
84
|
+
hasOverflow: false,
|
|
85
|
+
font,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Handle empty content
|
|
90
|
+
if (!content || content.length === 0) {
|
|
91
|
+
return {
|
|
92
|
+
minWidth: 0,
|
|
93
|
+
idealWidth: Math.min(IDEAL_READING_WIDTH, maxWidth),
|
|
94
|
+
height: lineHeight * 14, // One line height
|
|
95
|
+
lineCount: 1,
|
|
96
|
+
hasOverflow: false,
|
|
97
|
+
font,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Truncate extremely long content to prevent performance issues
|
|
102
|
+
const truncatedContent = content.length > MAX_CONTENT_LENGTH
|
|
103
|
+
? content.slice(0, MAX_CONTENT_LENGTH) + '…'
|
|
104
|
+
: content;
|
|
105
|
+
|
|
106
|
+
// Prepare text for measurement (one-time work)
|
|
107
|
+
const prepared = prepareWithSegments(truncatedContent, font, {
|
|
108
|
+
whiteSpace: options.whiteSpace ?? 'normal',
|
|
109
|
+
wordBreak: options.wordBreak ?? 'normal',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Measure at the target width
|
|
113
|
+
const { height, lineCount } = layout(prepared, maxWidth, lineHeight);
|
|
114
|
+
|
|
115
|
+
// Calculate shrink-wrap width (tightest fit)
|
|
116
|
+
const minWidth = pretextMeasureNatural(prepared);
|
|
117
|
+
|
|
118
|
+
// Determine ideal width (between min and max, capped at reading width)
|
|
119
|
+
const idealWidth = Math.min(
|
|
120
|
+
Math.max(minWidth, Math.min(IDEAL_READING_WIDTH, maxWidth)),
|
|
121
|
+
maxWidth
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// Detect overflow conditions
|
|
125
|
+
const hasOverflow = height > MAX_BLOCK_HEIGHT || content.length > MAX_CONTENT_LENGTH;
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
minWidth,
|
|
129
|
+
idealWidth,
|
|
130
|
+
height,
|
|
131
|
+
lineCount,
|
|
132
|
+
hasOverflow,
|
|
133
|
+
font,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Quick measurement for content previews (truncated at first overflow).
|
|
139
|
+
* Returns faster results for long content by sampling first N characters.
|
|
140
|
+
*/
|
|
141
|
+
export function measureBlockPreview(
|
|
142
|
+
content: string,
|
|
143
|
+
font: string = DEFAULT_FONT,
|
|
144
|
+
maxWidth: number = IDEAL_READING_WIDTH,
|
|
145
|
+
maxLines: number = 10
|
|
146
|
+
): BlockMetrics {
|
|
147
|
+
const previewLength = 1000; // Sample size for preview
|
|
148
|
+
const previewContent = content.length > previewLength
|
|
149
|
+
? content.slice(0, previewLength) + '…'
|
|
150
|
+
: content;
|
|
151
|
+
|
|
152
|
+
const metrics = measureBlock(previewContent, font, maxWidth);
|
|
153
|
+
|
|
154
|
+
// Cap line count for preview
|
|
155
|
+
if (metrics.lineCount > maxLines) {
|
|
156
|
+
const lineHeight = 1.6 * 14; // approximate
|
|
157
|
+
return {
|
|
158
|
+
...metrics,
|
|
159
|
+
lineCount: maxLines,
|
|
160
|
+
height: maxLines * lineHeight,
|
|
161
|
+
hasOverflow: true,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return { ...metrics, hasOverflow: content.length > previewLength };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Measure multiple blocks efficiently with shared font preparation.
|
|
170
|
+
* Useful for virtualized lists where font is constant.
|
|
171
|
+
*/
|
|
172
|
+
export function measureBlocksBatch(
|
|
173
|
+
contents: string[],
|
|
174
|
+
font: string = DEFAULT_FONT,
|
|
175
|
+
maxWidth: number = IDEAL_READING_WIDTH
|
|
176
|
+
): BlockMetrics[] {
|
|
177
|
+
return contents.map(content => measureBlock(content, font, maxWidth));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Calculate total canvas height for a collection of blocks.
|
|
182
|
+
* Includes spacing between blocks.
|
|
183
|
+
*/
|
|
184
|
+
export function calculateCanvasHeight(
|
|
185
|
+
metrics: BlockMetrics[],
|
|
186
|
+
spacing: number = 16,
|
|
187
|
+
padding: number = 24
|
|
188
|
+
): number {
|
|
189
|
+
const contentHeight = metrics.reduce((sum, m) => sum + m.height, 0);
|
|
190
|
+
const spacingHeight = Math.max(0, metrics.length - 1) * spacing;
|
|
191
|
+
return padding * 2 + contentHeight + spacingHeight;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Determine optimal column width for kanban-style layouts.
|
|
196
|
+
* Balances content width against container constraints.
|
|
197
|
+
*/
|
|
198
|
+
export function calculateOptimalColumnWidth(
|
|
199
|
+
allMetrics: BlockMetrics[],
|
|
200
|
+
containerWidth: number,
|
|
201
|
+
columnCount: number,
|
|
202
|
+
minColumnWidth: number = 280
|
|
203
|
+
): number {
|
|
204
|
+
const maxContentWidth = Math.max(...allMetrics.map(m => m.minWidth));
|
|
205
|
+
const availableWidth = (containerWidth - (columnCount - 1) * 16) / columnCount;
|
|
206
|
+
|
|
207
|
+
return Math.max(
|
|
208
|
+
minColumnWidth,
|
|
209
|
+
Math.min(maxContentWidth + 48, availableWidth) // +48 for padding
|
|
210
|
+
);
|
|
211
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// Block Types — 14 spatial research classifications
|
|
2
|
+
// Based on Nodepad's auto-classification system
|
|
3
|
+
|
|
4
|
+
/** The 14 types of enchanted blocks for spatial research */
|
|
5
|
+
export type BlockType =
|
|
6
|
+
| 'claim' // Asserted truth or proposition
|
|
7
|
+
| 'question' // Open inquiry or research question
|
|
8
|
+
| 'idea' // Proposed concept or hypothesis
|
|
9
|
+
| 'task' // Action item or todo (Ryu integration)
|
|
10
|
+
| 'entity' // Named thing (person, organization, concept)
|
|
11
|
+
| 'quote' // External citation or verbatim excerpt
|
|
12
|
+
| 'reference' // Link, pointer, or citation
|
|
13
|
+
| 'definition' // Term explanation or glossary entry
|
|
14
|
+
| 'opinion' // Subjective stance or perspective
|
|
15
|
+
| 'reflection' // Self-aware musing or metacognition
|
|
16
|
+
| 'narrative' // Story, timeline, or sequential account
|
|
17
|
+
| 'comparison' // Side-by-side analysis or contrast
|
|
18
|
+
| 'thesis' // Synthesized position or argument
|
|
19
|
+
| 'general'; // Unclassified or mixed content
|
|
20
|
+
|
|
21
|
+
/** All block types as an array for iteration */
|
|
22
|
+
export const ALL_BLOCK_TYPES: BlockType[] = [
|
|
23
|
+
'claim',
|
|
24
|
+
'question',
|
|
25
|
+
'idea',
|
|
26
|
+
'task',
|
|
27
|
+
'entity',
|
|
28
|
+
'quote',
|
|
29
|
+
'reference',
|
|
30
|
+
'definition',
|
|
31
|
+
'opinion',
|
|
32
|
+
'reflection',
|
|
33
|
+
'narrative',
|
|
34
|
+
'comparison',
|
|
35
|
+
'thesis',
|
|
36
|
+
'general',
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
/** Human-readable labels for each block type */
|
|
40
|
+
export const BLOCK_TYPE_LABELS: Record<BlockType, string> = {
|
|
41
|
+
claim: 'Claim',
|
|
42
|
+
question: 'Question',
|
|
43
|
+
idea: 'Idea',
|
|
44
|
+
task: 'Task',
|
|
45
|
+
entity: 'Entity',
|
|
46
|
+
quote: 'Quote',
|
|
47
|
+
reference: 'Reference',
|
|
48
|
+
definition: 'Definition',
|
|
49
|
+
opinion: 'Opinion',
|
|
50
|
+
reflection: 'Reflection',
|
|
51
|
+
narrative: 'Narrative',
|
|
52
|
+
comparison: 'Comparison',
|
|
53
|
+
thesis: 'Thesis',
|
|
54
|
+
general: 'Note',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** Lucide icon names for each block type */
|
|
58
|
+
export const BLOCK_TYPE_ICONS: Record<BlockType, string> = {
|
|
59
|
+
claim: 'CheckCircle', // Asserted truth
|
|
60
|
+
question: 'HelpCircle', // Inquiry
|
|
61
|
+
idea: 'Lightbulb', // Concept
|
|
62
|
+
task: 'CheckSquare', // Action item
|
|
63
|
+
entity: 'Box', // Named thing
|
|
64
|
+
quote: 'Quote', // Citation
|
|
65
|
+
reference: 'Link', // Pointer
|
|
66
|
+
definition: 'BookOpen', // Explanation
|
|
67
|
+
opinion: 'MessageCircle', // Subjective view
|
|
68
|
+
reflection: 'Mirror', // Self-aware
|
|
69
|
+
narrative: 'ScrollText', // Story
|
|
70
|
+
comparison: 'Scale', // Analysis
|
|
71
|
+
thesis: 'GraduationCap', // Synthesized argument
|
|
72
|
+
general: 'StickyNote', // Default
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* CSS color tokens for each block type.
|
|
77
|
+
* Uses KOS brass/copper palette with semantic differentiation.
|
|
78
|
+
* Format: HSL values for --type-color CSS custom property.
|
|
79
|
+
*/
|
|
80
|
+
export const BLOCK_TYPE_COLORS: Record<BlockType, string> = {
|
|
81
|
+
// Warm/assertive types (brass/gold variants)
|
|
82
|
+
claim: 'hsl(30, 60%, 55%)', // Brass
|
|
83
|
+
thesis: 'hsl(25, 70%, 50%)', // Copper
|
|
84
|
+
task: 'hsl(35, 55%, 50%)', // Bronze
|
|
85
|
+
|
|
86
|
+
// Cool/inquisitive types (muted complementary)
|
|
87
|
+
question: 'hsl(210, 50%, 60%)', // Steel blue
|
|
88
|
+
idea: 'hsl(160, 45%, 50%)', // Sage
|
|
89
|
+
opinion: 'hsl(280, 40%, 55%)', // Muted purple
|
|
90
|
+
|
|
91
|
+
// Reference/external types (neutral)
|
|
92
|
+
quote: 'hsl(0, 0%, 70%)', // Silver
|
|
93
|
+
reference: 'hsl(200, 30%, 60%)', // Slate
|
|
94
|
+
definition: 'hsl(180, 35%, 55%)', // Teal
|
|
95
|
+
|
|
96
|
+
// Process/meta types (warm earth)
|
|
97
|
+
reflection: 'hsl(45, 50%, 55%)', // Amber
|
|
98
|
+
narrative: 'hsl(20, 45%, 50%)', // Terracotta
|
|
99
|
+
comparison: 'hsl(40, 45%, 50%)', // Gold ochre
|
|
100
|
+
|
|
101
|
+
// Structural types
|
|
102
|
+
entity: 'hsl(220, 40%, 55%)', // Denim
|
|
103
|
+
general: 'hsl(30, 40%, 50%)', // Default brass
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/** Get the next type in sequence (for cycling via click) */
|
|
107
|
+
export function nextBlockType(current: BlockType): BlockType {
|
|
108
|
+
const idx = ALL_BLOCK_TYPES.indexOf(current);
|
|
109
|
+
return ALL_BLOCK_TYPES[(idx + 1) % ALL_BLOCK_TYPES.length];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Get the previous type in sequence */
|
|
113
|
+
export function prevBlockType(current: BlockType): BlockType {
|
|
114
|
+
const idx = ALL_BLOCK_TYPES.indexOf(current);
|
|
115
|
+
return ALL_BLOCK_TYPES[(idx - 1 + ALL_BLOCK_TYPES.length) % ALL_BLOCK_TYPES.length];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Simple heuristic classifier based on content patterns.
|
|
120
|
+
* Returns confidence score 0-1 alongside type.
|
|
121
|
+
*/
|
|
122
|
+
export function classifyBlockHeuristic(content: string): { type: BlockType; confidence: number } {
|
|
123
|
+
const lower = content.toLowerCase().trim();
|
|
124
|
+
|
|
125
|
+
// Question detection
|
|
126
|
+
if (lower.match(/^(what|why|how|when|where|who|is\s+it|are\s+they|can\s+we|should\s+we)\b/i) ||
|
|
127
|
+
lower.endsWith('?')) {
|
|
128
|
+
return { type: 'question', confidence: 0.9 };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Task detection
|
|
132
|
+
if (lower.match(/^(todo|task|action|implement|fix|add|remove|update|create|build)\b/i) ||
|
|
133
|
+
lower.match(/\[x\]|\[ \]|\[-\]/)) {
|
|
134
|
+
return { type: 'task', confidence: 0.85 };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Quote detection
|
|
138
|
+
if (lower.startsWith('"') && lower.includes('"') ||
|
|
139
|
+
lower.startsWith('"') && lower.endsWith('"') ||
|
|
140
|
+
lower.match(/^as\s+\w+\s+said/i)) {
|
|
141
|
+
return { type: 'quote', confidence: 0.8 };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Definition detection
|
|
145
|
+
if (lower.match(/\bis\s+(defined\s+as|a\s+type\s+of|the\s+process\s+of)\b/i) ||
|
|
146
|
+
lower.match(/^\w+:\s+\w+/)) {
|
|
147
|
+
return { type: 'definition', confidence: 0.75 };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Claim detection (assertive language)
|
|
151
|
+
if (lower.match(/\b(is|are|will|must|should|always|never|proves|shows|demonstrates)\b/i) &&
|
|
152
|
+
!lower.endsWith('?')) {
|
|
153
|
+
return { type: 'claim', confidence: 0.7 };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Idea detection
|
|
157
|
+
if (lower.match(/\b(idea|concept|proposal|suggest|consider|perhaps|maybe|could|might)\b/i)) {
|
|
158
|
+
return { type: 'idea', confidence: 0.65 };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Reference detection
|
|
162
|
+
if (lower.match(/^(see|cf\.|refer\s+to|check\s+out|read|http|www\.|github\.com)/i)) {
|
|
163
|
+
return { type: 'reference', confidence: 0.8 };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Narrative detection (time words)
|
|
167
|
+
if (lower.match(/\b(then|next|after|before|finally|eventually|once|when\s+we)\b/i)) {
|
|
168
|
+
return { type: 'narrative', confidence: 0.6 };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Comparison detection
|
|
172
|
+
if (lower.match(/\b(vs|versus|compared\s+to|unlike|similarly|both|either|or)\b/i)) {
|
|
173
|
+
return { type: 'comparison', confidence: 0.65 };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Reflection detection (self-aware)
|
|
177
|
+
if (lower.match(/\b(i\s+think|i\s+wonder|i\s+realized|reflecting|thinking\s+about)\b/i)) {
|
|
178
|
+
return { type: 'reflection', confidence: 0.7 };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Opinion detection
|
|
182
|
+
if (lower.match(/\b(i\s+believe|in\s+my\s+opinion|personally|i\s+feel|i\s+prefer)\b/i)) {
|
|
183
|
+
return { type: 'opinion', confidence: 0.75 };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Entity detection (proper nouns, capitalized)
|
|
187
|
+
if (lower.match(/^[A-Z][a-z]+\s+(is|was|are|refers\s+to)/)) {
|
|
188
|
+
return { type: 'entity', confidence: 0.6 };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return { type: 'general', confidence: 0.5 };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Core data structure for an enchanted block */
|
|
195
|
+
export interface EnchantedBlockData {
|
|
196
|
+
id: string;
|
|
197
|
+
type: BlockType;
|
|
198
|
+
content: string;
|
|
199
|
+
|
|
200
|
+
/** AI-generated annotation: "what the note doesn't say" */
|
|
201
|
+
annotation?: string;
|
|
202
|
+
|
|
203
|
+
/** Epistemic provenance from KVE */
|
|
204
|
+
provenance?: {
|
|
205
|
+
source: string;
|
|
206
|
+
createdAt: number;
|
|
207
|
+
modifiedAt: number;
|
|
208
|
+
confidence?: number;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
/** Inferred connections to other blocks */
|
|
212
|
+
connections?: {
|
|
213
|
+
targetId: string;
|
|
214
|
+
strength: number; // 0.0 - 1.0
|
|
215
|
+
type: 'supports' | 'contradicts' | 'extends' | 'similar' | 'related';
|
|
216
|
+
}[];
|
|
217
|
+
|
|
218
|
+
/** Spatial position (for canvas views) */
|
|
219
|
+
x?: number;
|
|
220
|
+
y?: number;
|
|
221
|
+
width?: number;
|
|
222
|
+
height?: number;
|
|
223
|
+
|
|
224
|
+
/** Timestamps */
|
|
225
|
+
createdAt: number;
|
|
226
|
+
modifiedAt: number;
|
|
227
|
+
}
|