Composable Svelte Code Package
Code editing, syntax highlighting, and visual programming components.
UPGRADE 1 AGENT ENTRY
For an application built with the integrated Upgrade 1 companion packages, begin with the managed package reference and its executable recipe. The same reference is included in the package at node_modules/@composable-svelte/code/recipes/managed/README.md; use the installed version's declarations and instructions as the API authority.
Pass genuine bound child views to the packaged editor, highlight and canvas components. Let those components own their native bindings and ordered command delivery; do not rebuild Store facades, root action listeners or command queues in application code.
The standalone store and callback examples below describe standalone usage. For an owned application feature, follow the managed recipe rather than copying the standalone setup and adding ad hoc lifetime glue. Candidate qualification and npm publication are separate; verify the installed package version contains this managed surface.
PACKAGE OVERVIEW
Package: @composable-svelte/code
Purpose: Rich interactive components for code editing and visual programming.
Technology Stack:
- CodeMirror 6: Modern code editor with extensible architecture
- Prism.js: Lightweight syntax highlighter
- SvelteFlow: Node-based visual programming canvas
Core Components:
CodeEditor- Full-featured code editor (CodeMirror)CodeHighlight- Read-only syntax highlighting (Prism)NodeCanvas- Visual node editor (SvelteFlow)
State Management: All components follow Composable Architecture patterns with dedicated reducers and type-safe actions.
CODE EDITOR (CodeMirror)
Purpose: Full-featured code editor with syntax highlighting, autocomplete, and language support.
Quick Start
<script lang="ts">
import { createStore } from '@composable-svelte/core';
import { CodeEditor, codeEditorReducer, createInitialCodeEditorState } from '@composable-svelte/code';
// Create editor store
const store = createStore({
initialState: createInitialCodeEditorState({
value: '// Write code here',
language: 'typescript',
theme: 'dark',
showLineNumbers: true
}),
reducer: codeEditorReducer,
dependencies: {
onSave: async (value) => {
await fetch('/api/save', { method: 'POST', body: value });
},
formatter: async (code, language) => {
// Use prettier or similar
return formatCode(code, language);
}
}
});
</script>
<CodeEditor {store} showToolbar={true} />
Props
store: Store<CodeEditorState, CodeEditorAction>- Editor store (required)showToolbar: boolean- Show toolbar with controls (default: true)
State Interface
interface CodeEditorState {
// Content
value: string; // Current code
language: SupportedLanguage; // Active language
// Cursor & Selection
cursorPosition: { line: number; column: number } | null;
selection: EditorSelection | null;
// UI State
theme: 'light' | 'dark' | 'auto';
showLineNumbers: boolean;
readOnly: boolean;
// Features
enableAutocomplete: boolean;
tabSize: number;
// Editor Status
hasUnsavedChanges: boolean;
lastSavedValue: string | null;
isFocused: boolean;
// History
canUndo: boolean;
canRedo: boolean;
// Errors
error: string | null;
saveError: string | null;
formatError: string | null;
}
Supported Languages
TypeScript, JavaScript, Svelte, HTML, CSS, JSON, Markdown, Bash, SQL, Python, Rust
Actions
type CodeEditorAction =
// Content
| { type: 'valueChanged'; value: string; cursorPosition?: {...} }
| { type: 'languageChanged'; language: SupportedLanguage }
// Editing
| { type: 'undo' }
| { type: 'redo' }
| { type: 'insertText'; text: string; position?: {...} }
| { type: 'selectAll' }
// Configuration
| { type: 'themeChanged'; theme: 'light' | 'dark' | 'auto' }
| { type: 'toggleLineNumbers' }
| { type: 'setReadOnly'; readOnly: boolean }
// Save/Format
| { type: 'save' }
| { type: 'format' };
Dependencies
interface CodeEditorDependencies {
// Save handler
onSave?: (value: string) => Promise<void>;
// Format handler
formatter?: (code: string, language: SupportedLanguage) => Promise<string>;
}
Complete Example
<script lang="ts">
import { createStore, Effect } from '@composable-svelte/core';
import { CodeEditor, codeEditorReducer, createInitialCodeEditorState } from '@composable-svelte/code';
import { formatCode } from './my-formatter'; // Your formatter
// Create store with dependencies
const editorStore = createStore({
initialState: createInitialCodeEditorState({
value: `function hello() {\n console.log('Hello, World!');\n}`,
language: 'typescript',
theme: 'dark',
showLineNumbers: true,
enableAutocomplete: true,
tabSize: 2
}),
reducer: codeEditorReducer,
dependencies: {
// Handle save
onSave: async (value: string) => {
console.log('Saving code:', value);
await fetch('/api/code/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code: value })
});
},
// Handle format
formatter: async (code: string, language: string) => {
return formatCode(code, language);
}
}
});
</script>
<div class="editor-container">
<CodeEditor store={editorStore} showToolbar={true} />
<!-- Status bar -->
<div class="status-bar">
{#if $editorStore.hasUnsavedChanges}
<span class="status-warning">Unsaved changes</span>
{/if}
{#if $editorStore.cursorPosition}
<span>Ln {$editorStore.cursorPosition.line}, Col {$editorStore.cursorPosition.column}</span>
{/if}
<span>{$editorStore.language}</span>
</div>
</div>
Toolbar Features
When showToolbar={true}:
- Language selector dropdown
- Line numbers toggle
- Theme toggle (light/dark)
- Format button
- Save button (shows "Save *" when unsaved changes)
Keyboard Shortcuts
CodeMirror provides standard keyboard shortcuts:
Ctrl/Cmd + S: SaveCtrl/Cmd + Z: UndoCtrl/Cmd + Shift + Z: RedoCtrl/Cmd + /: Toggle commentCtrl/Cmd + D: Delete lineCtrl/Cmd + A: Select all
CODE HIGHLIGHT (Prism.js)
Purpose: Read-only syntax highlighting for displaying code snippets.
Quick Start
import { CodeHighlight } from '@composable-svelte/code';
<CodeHighlight
code={`const greeting = "Hello, World!";`}
language="javascript"
theme="dark"
showLineNumbers={true}
/>
Props
code: string- Code to highlight (required)language: string- Language (e.g., 'javascript', 'typescript', 'python')theme: 'light' | 'dark'- Color theme (default: 'dark')showLineNumbers: boolean- Show line numbers (default: false)highlightLines: number[]- Lines to highlight (optional)class: string- Custom CSS class (optional)
Supported Languages
JavaScript, TypeScript, Python, Rust, Go, Java, C, C++, C#, PHP, Ruby, SQL, HTML, CSS, JSON, YAML, Markdown, Bash, Svelte, and more.
Examples
<!-- Basic highlighting -->
<CodeHighlight
code={`function add(a, b) {\n return a + b;\n}`}
language="javascript"
/>
<!-- With line numbers -->
<CodeHighlight
code={pythonCode}
language="python"
showLineNumbers={true}
/>
<!-- Highlight specific lines -->
<CodeHighlight
code={tsCode}
language="typescript"
highlightLines={[3, 5, 7]}
/>
<!-- Custom theme -->
<CodeHighlight
code={rustCode}
language="rust"
theme="light"
/>
When to Use CodeEditor vs CodeHighlight
Use CodeEditor:
- User needs to edit code
- Autocomplete required
- Linting/error checking needed
- Save functionality required
Use CodeHighlight:
- Display-only code snippets
- Documentation/tutorials
- Code examples in blog posts
- Faster rendering for many snippets
NODE CANVAS (SvelteFlow)
Purpose: Visual node-based programming canvas (node editor, flow diagrams).
Quick Start
<script lang="ts">
import { createStore } from '@composable-svelte/core';
import { NodeCanvas, nodeCanvasReducer, createInitialNodeCanvasState } from '@composable-svelte/code';
// Create node canvas store
const canvasStore = createStore({
initialState: createInitialNodeCanvasState({
nodes: [
{
id: '1',
type: 'input',
position: { x: 100, y: 100 },
data: { label: 'Input Node' }
},
{
id: '2',
type: 'default',
position: { x: 300, y: 100 },
data: { label: 'Process Node' }
}
],
edges: [
{ id: 'e1-2', source: '1', target: '2' }
]
}),
reducer: nodeCanvasReducer,
dependencies: {}
});
</script>
<NodeCanvas
store={canvasStore}
nodeTypes={customNodeTypes}
onNodeClick={(node) => console.log('Clicked:', node)}
/>
Props
store: Store<NodeCanvasState, NodeCanvasAction>- Canvas store (required)nodeTypes: Record<string, ComponentType>- Custom node components (optional)edgeTypes: Record<string, ComponentType>- Custom edge components (optional)onNodeClick: (node: Node) => void- Node click handler (optional)onEdgeClick: (edge: Edge) => void- Edge click handler (optional)class: string- Custom CSS class (optional)
State Interface
interface NodeCanvasState {
// Nodes
nodes: Node[];
selectedNodes: string[]; // Node IDs
// Edges
edges: Edge[];
selectedEdges: string[]; // Edge IDs
// Viewport
viewport: {
x: number;
y: number;
zoom: number;
};
// Interaction
isDragging: boolean;
isPanning: boolean;
// Error handling
error: string | null;
}
interface Node {
id: string;
type: string;
position: { x: number; y: number };
data: any;
}
interface Edge {
id: string;
source: string; // Node ID
target: string; // Node ID
type?: string;
data?: any;
}
Actions
type NodeCanvasAction =
// Nodes
| { type: 'addNode'; node: Node }
| { type: 'removeNode'; nodeId: string }
| { type: 'updateNode'; nodeId: string; data: Partial<Node> }
| { type: 'moveNode'; nodeId: string; position: { x: number; y: number } }
| { type: 'selectNode'; nodeId: string }
| { type: 'deselectNode'; nodeId: string }
| { type: 'clearNodeSelection' }
// Edges
| { type: 'addEdge'; edge: Edge }
| { type: 'removeEdge'; edgeId: string }
| { type: 'updateEdge'; edgeId: string; data: Partial<Edge> }
// Viewport
| { type: 'setViewport'; viewport: Partial<Viewport> }
| { type: 'zoomIn' }
| { type: 'zoomOut' }
| { type: 'fitView' }
// Error
| { type: 'errorOccurred'; error: string }
| { type: 'errorCleared' };
Use Cases
- Visual programming tools
- Flow diagrams
- Workflow builders
- Data pipelines
- State machines
- Architecture diagrams
Complete Example
<script lang="ts">
import { createStore } from '@composable-svelte/core';
import { NodeCanvas, nodeCanvasReducer, createInitialNodeCanvasState } from '@composable-svelte/code';
// Custom node component
import CustomNode from './CustomNode.svelte';
const canvasStore = createStore({
initialState: createInitialNodeCanvasState({
nodes: [
{
id: '1',
type: 'input',
position: { x: 100, y: 100 },
data: { label: 'Start', value: 0 }
},
{
id: '2',
type: 'process',
position: { x: 300, y: 100 },
data: { label: 'Add 10', operation: 'add', value: 10 }
},
{
id: '3',
type: 'output',
position: { x: 500, y: 100 },
data: { label: 'Result' }
}
],
edges: [
{ id: 'e1-2', source: '1', target: '2' },
{ id: 'e2-3', source: '2', target: '3' }
]
}),
reducer: nodeCanvasReducer,
dependencies: {}
});
// Handle node clicks.
//
// A bare `Node` is the DOM's. The canvas holds SvelteFlow's `Node<NodeData>`,
// which `@composable-svelte/code` does not re-export — so a handler that only
// needs the id says so structurally, and one that needs more imports the type
// from `@xyflow/svelte`.
function handleNodeClick(node: { id: string }) {
console.log('Node clicked:', node);
canvasStore.dispatch({ type: 'selectNode', nodeId: node.id });
}
// Handle edge clicks
function handleEdgeClick(edge: Edge) {
console.log('Edge clicked:', edge);
}
</script>
<div class="canvas-container">
<NodeCanvas
store={canvasStore}
nodeTypes={{ custom: CustomNode }}
onNodeClick={handleNodeClick}
onEdgeClick={handleEdgeClick}
/>
<!-- Controls -->
<div class="canvas-controls">
<button onclick={() => canvasStore.dispatch({ type: 'zoomIn' })}>
Zoom In
</button>
<button onclick={() => canvasStore.dispatch({ type: 'zoomOut' })}>
Zoom Out
</button>
<button onclick={() => canvasStore.dispatch({ type: 'fitView' })}>
Fit View
</button>
</div>
</div>
<style>
.canvas-container {
width: 100%;
height: 600px;
position: relative;
}
.canvas-controls {
position: absolute;
top: 10px;
right: 10px;
display: flex;
gap: 8px;
}
</style>
COMPONENT SELECTION GUIDE
When to use each component:
CodeEditor:
- User edits code
- Autocomplete needed
- Syntax checking required
- Save functionality required
CodeHighlight:
- Display-only code
- Documentation examples
- Fast rendering for many snippets
NodeCanvas:
- Visual programming
- Workflow editors
- Diagram builders
- Data pipeline visualization
CROSS-REFERENCES
Related Skills:
- composable-svelte-core: Store, reducer, Effect system, TestStore
- composable-svelte-media: Audio/video playback (AudioPlayer, VideoEmbed, VoiceInput)
- composable-svelte-chat: Real-time communication (StreamingChat)
- composable-svelte-components: UI components (Button, Input, etc.)
- composable-svelte-testing: TestStore for testing reducers
When to Use Each Package:
- code: Code editors, syntax highlighting, visual programming
- media: Audio players, video embeds, voice input
- chat: Real-time chat, streaming responses
- graphics: 3D scenes, WebGL rendering
- charts: 2D data visualization
- maps: Geospatial data
PERFORMANCE CONSIDERATIONS
CodeEditor:
- Heavy component (CodeMirror bundle ~300KB)
- Use CodeHighlight for display-only snippets
- Lazy load for better initial page load
NodeCanvas:
- Heavy component (SvelteFlow bundle ~200KB)
- Limit number of nodes for performance (< 1000 recommended)
- Use virtualization for large graphs
- Disable animations for large graphs
TESTING PATTERNS
CodeEditor Testing
import { TestStore } from '@composable-svelte/core/test';
import { codeEditorReducer, createInitialCodeEditorState } from '@composable-svelte/code';
const store = new TestStore({
initialState: createInitialCodeEditorState({ value: '' }),
reducer: codeEditorReducer,
dependencies: {
onSave: vi.fn(),
formatter: vi.fn((code) => Promise.resolve(code))
}
});
// Test value change
await store.send({
type: 'valueChanged',
value: 'console.log("test");'
}, (state) => {
expect(state.value).toBe('console.log("test");');
expect(state.hasUnsavedChanges).toBe(true);
});
// Test save
await store.send({ type: 'save' });
await store.receive({ type: 'saved', value: 'console.log("test");' }, (state) => {
expect(state.hasUnsavedChanges).toBe(false);
expect(state.lastSavedValue).toBe('console.log("test");');
});
NodeCanvas Testing
import { TestStore } from '@composable-svelte/core/test';
import { nodeCanvasReducer, createInitialNodeCanvasState } from '@composable-svelte/code';
const store = new TestStore({
initialState: createInitialNodeCanvasState({
nodes: [],
edges: []
}),
reducer: nodeCanvasReducer,
dependencies: {}
});
// Test add node
await store.send({
type: 'addNode',
node: {
id: '1',
type: 'input',
position: { x: 100, y: 100 },
data: { label: 'Test' }
}
}, (state) => {
expect(state.nodes.length).toBe(1);
expect(state.nodes[0].id).toBe('1');
});
// Test add edge
await store.send({
type: 'addEdge',
edge: { id: 'e1-2', source: '1', target: '2' }
}, (state) => {
expect(state.edges.length).toBe(1);
});
TROUBLESHOOTING
CodeEditor not rendering:
- Check CodeMirror peer dependency installed
- Verify store created with
codeEditorReducer - Ensure container has height set
NodeCanvas performance issues:
- Limit number of nodes (< 1000 recommended)
- Use simpler custom node components
- Disable animations for large graphs
CodeHighlight language not recognized:
- Check language name matches Prism.js language keys
- Ensure Prism.js language pack is loaded
- Verify syntax highlighting CSS is included
ALL EXPORTS
CodeHighlight
CodeHighlight— ComponentcodeHighlightReducer,createInitialCodeHighlightState()— State managementhighlightCode(code, lang)— Highlight code string with Prism.jsloadLanguage(lang)— Dynamically load a Prism.js language grammar- Types:
CodeHighlightState,CodeHighlightAction,CodeHighlightDependencies,SupportedLanguage
CodeEditor
CodeEditor— ComponentcodeEditorReducer,createInitialCodeEditorState()— State managementcreateEditorView(config)— Create a CodeMirror EditorViewloadEditorLanguage(lang)— Load a CodeMirror language extensionupdateEditorValue(view, value),updateEditorLanguage(view, lang),updateEditorTheme(view, theme),updateEditorReadOnly(view, readOnly),updateTabSize(view, size)— Programmatic editor updates- Focus is driven by dispatching
{ type: 'focus' }/{ type: 'blur' }— the rawfocusEditor(view)/blurEditor(view)helpers were removed in favour of the action stream, where the other editor commands live. - Types:
CodeEditorState,CodeEditorAction,CodeEditorDependencies,EditorLanguage,EditorSelection
NodeCanvas
NodeCanvas— ComponentnodeCanvasReducer,createInitialNodeCanvasState(config)— State managementcreateConnectionValidator(config),permissiveValidator,strictValidator,createStrictValidator(message),composeValidators(...validators)— Connection validation.strictValidatoris aConnectionValidatorconstant, not a factory; usecreateStrictValidatorfor a custom message.nodesToArray(nodes),edgesToArray(edges)— Conversion utilities- Types:
NodeCanvasState,NodeCanvasAction,NodeCanvasDependencies,NodeTypeDefinition,PortDefinition,ConnectionValidation,ConnectionValidator