Architecture
High-level technical design for Canopy.
Data Flow
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Source │────▶│ Parser │────▶│ Patterns │────▶│ Renderer │
│ │ │ │ │ │ │ │
│ fetch/ws/ │ │ json/proto/ │ │ match shape │ │ tree/table/ │
│ sse/file │ │ msgpack/bin │ │ select view │ │ chart/hex │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘Each stage is pluggable.
Core Abstractions
Source
Produces raw bytes + metadata.
typescript
type Source = {
kind: 'fetch' | 'websocket' | 'sse' | 'file' | 'static'
url?: string
contentType?: string
// ...
}
type SourceResult = {
data: ArrayBuffer | ReadableStream<Uint8Array>
contentType: string
headers?: Headers
metadata?: Record<string, unknown>
}Parser
Transforms bytes into structured data.
typescript
type Parser = {
id: string
name: string
// Which content types this parser handles
contentTypes: string[]
// Parse raw bytes into structured form
parse(input: ArrayBuffer, ctx: ParseContext): ParseResult
}
type ParseResult =
| { ok: true; value: unknown; schema?: Schema }
| { ok: false; error: string; partial?: unknown }Built-in parsers: JSON, text, binary (passthrough). Plugin parsers: protobuf, msgpack, CBOR, custom binary formats.
Pattern
Recognizes structure in parsed data, suggests renderers.
typescript
type Pattern = {
id: string
name: string
// Higher priority = matched first
priority: number
// Does this pattern match the data?
match(data: unknown, ctx: MatchContext): MatchResult
}
type MatchResult =
| { matched: false }
| { matched: true; renderer: string; confidence: number; children?: ChildMatch[] }Patterns can be:
- Structural: "array of objects with numeric x/y fields"
- Heuristic: "number that looks like a Unix timestamp"
- Semantic: "field named
*_colorcontaining hex string" - Schema-derived: "OpenAPI says this is a DateTime"
Renderer
Displays data.
typescript
type Renderer = {
id: string
name: string
// Render data to DOM/component
render(data: unknown, ctx: RenderContext): RenderResult
}Renderers receive matched data + any child pattern matches, produce UI.
Plugin System
Plugins are ES modules exporting a manifest:
typescript
// my-plugin.ts
export const manifest: PluginManifest = {
id: 'my-plugin',
name: 'My Plugin',
version: '1.0.0',
parsers: [myParser],
patterns: [myPattern],
renderers: [myRenderer],
}Loaded dynamically. Core provides:
- Plugin discovery/loading
- Dependency resolution
- Sandboxing (future)
Configuration
Layered, VSCode-style:
defaults < user settings < workspace settings < source overridesExample user settings:
jsonc
{
// Default renderer for arrays
"renderers.array.default": "table",
// Pattern overrides
"patterns.timestamp.range": [0, 2000000000000],
// Per-source config
"sources": {
"https://api.example.com/*": {
"auth": { "type": "bearer", "token": "${env:EXAMPLE_TOKEN}" }
}
}
}Open Questions
- [x]
How do user scripts integrate?No distinction - everything is a plugin. Local plugins live in~/.config/canopy/plugins/local/, installed ones inplugins/installed/. Same API, same power. - [x]
Streaming data - how do patterns work on partial data?Patterns match individual items. Renderers control collection behavior (append/window/replace). Same pattern can render as "table, append last 1000" or "chart, window last 5 minutes". Multiple views on same source can have different collection modes. - [x]
Binary annotation UI - how to persist user structure annotations?Layered like VSCode settings: user (~/.config/canopy/), workspace (.canopy/), folder, file. User can "promote to plugin" when they've identified a general format. - [x]
Schema discovery - auto-fetch OpenAPI, or explicit config?Both: auto-discover well-known paths, allow explicit override/disable. Schema flows intoMatchContextso patterns can use it (authoritative) or fall back to data heuristics. Also used for: validation indicators, documentation tooltips (like VSCode JSON language server). - [x]
Plugin distribution - registry, or just URLs?No custom registry. Use npm/jsr for published plugins, URLs for direct install, local paths for personal plugins. All resolve to ES modules.canopy install npm:@canopy/foo,canopy install https://...,canopy install ./local/bar.ts.