tinkerdown

package module
v0.3.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 8, 2026 License: MIT Imports: 29 Imported by: 0

README

Tinkerdown

v0.2.x — early but tagged. Expect breaking changes between minors.

Markdown is great for writing. HTML is great for showing. Tinkerdown lets you write the first and serve the second — from one file.

Animated demo showing a markdown file with YAML frontmatter and lvt attributes being served by Tinkerdown into a live interactive task manager with table, status badges, and action buttons

Tinkerdown turns a single markdown file — frontmatter for data sources, prose for content, declarative attributes for interactivity — into a live, themed, URL-routable web app. Built on LiveTemplate.

Why Tinkerdown?

  • One file, co-authorable. The source stays human- and AI-editable markdown. The rich rendered page is a result, not the artifact you have to maintain.
  • Declarative, not freeform. Interactivity comes from a fixed vocabulary of lvt-* attributes — predictable for LLMs, consistent across pages, no generic LLM-output aesthetic.
  • Live data, not a snapshot. Bind to SQLite, PostgreSQL, REST, JSON, CSV, shell commands, markdown, WASM, or computed sources. The rendered page reflects current state, not a frozen export.
  • Real URLs. Every page is linkable, bookmarkable, deep-linkable. No in-memory SPA state hiding behind a single /.
  • Git-native and self-hosted. Plain text in a repo. Version history, search, collaboration, offline access, no subscriptions.
  • Progressive complexity. Standard markdown → declarative attributes → Go templates. Each step builds on the last without rewriting. See the Progressive Complexity Guide.
  • Disposable-software friendly. The admin panel for this sprint. The tracker for that hiring round. The dashboard for the incident retro. Things you'd never scaffold a React app for, but that earn their keep for days or weeks.

Why not just ask Claude for an HTML file?

That works — until you want to edit it. Generated HTML is opaque to hand-editing; the next change means another full re-prompt instead of a five-second text edit. And the data is whatever was true when the file was generated.

Tinkerdown keeps the source you edit (markdown + frontmatter + a small attribute vocabulary) separate from the page you ship (themed HTML, live data, WebSocket reactivity). You — or your agent — change the markdown. Everything else updates.

Quick Start

# Install
go install github.com/livetemplate/tinkerdown/cmd/tinkerdown@latest

# Create a new app
tinkerdown new myapp
cd myapp

# Run the app
tinkerdown serve
# Open http://localhost:8080

What You Can Build

Write a markdown file with a YAML source definition and a standard markdown table. Tinkerdown infers that the "Tasks" heading matches the "tasks" source and auto-generates an interactive table with add, edit, and delete:

---
title: Task Manager
sources:
  tasks:
    type: sqlite
    db: ./tasks.db
    table: tasks
    readonly: false
---

# Task Manager

## Tasks
| Title | Status | Due Date |
|-------|--------|----------|

Run tinkerdown serve and get a fully interactive app with database persistence — no HTML needed:

The same primitive scales to other artifacts:

Artifact What it looks like
Dashboard / status report Frontmatter pulls from PostgreSQL or REST; tables, computed totals, and Mermaid diagrams render inline. (examples/markdown-data-dashboard)
Literate doc / runnable explainer Prose alongside live widgets, real source cited by line range, and your deployed app embedded inline — the doc is the working code. (Literate Authoring guide, examples/literate-counter-include)
Triage / standup board Action buttons mutate a shared SQLite or PostgreSQL source; every teammate's tab stays in sync over WebSocket. (examples/standup-bot, examples/team-tasks)
Throwaway admin panel Point at a table you already have. Edit/delete/add for free. (examples/auto-table-sqlite)

Literate authoring. When the page itself is a tutorial, the same markdown can show real source and run it. The snippet below shows two of the three primitives: include= cites line ranges from your .go / .tmpl source files, and embed-lvt drops the running app inline. The third — show-source — pairs an inline ```lvt block with its highlighted template. No copy-pasted snippets to drift out of date.

```go include="./_app/counter.go" lines="5-8"
```

```go include="./_app/counter.go" lines="13-35" highlight="20"
```

```embed-lvt path="/apps/counter/" upstream="http://127.0.0.1:9090"
```

A literate doc in the wild: livetemplate.fly.dev/recipes/counter — prose, source listings, and a live counter on one page, all from a single markdown file.

See the Literate Authoring guide for the full primitive set (line ranges, named regions, line highlights, source-link footers) and examples/literate-counter-include for the canonical pattern.

Need more control? Tinkerdown has five complexity tiers. Each builds on the previous — nothing rewrites; start at the lowest tier that works:

  • Tier 0 — pure markdown. Standard checklists become interactive; toggles and adds persist to the file.
    - [ ] Buy milk
    - [x] Email Sarah
    
  • Tier 1 — markdown + YAML sources. Frontmatter declares data; markdown tables auto-bind. (Shown above.)
  • Tier 2 — lvt-* attributes. Explicit HTML binding when auto-rendering isn't enough — custom forms, confirmation dialogs, datatables, cross-source selects.
    <table lvt-source="tasks" lvt-columns="title,status" lvt-datatable lvt-actions="Complete,Delete">
    </table>
    
  • Tier 3 — Go templates. Conditionals, loops, and custom layouts inside ```lvt blocks; full access to .Data, .Error, .Errors.
    ```lvt
    {{range .Data}}
    <div class="card"><h3>{{.Title}}</h3></div>
    {{end}}
    ```
    
  • Tier 4 — WASM sources. Write a custom data source in TinyGo when built-ins don't fit; the module exports a fetch function and runs server-side.

See the Progressive Complexity Guide for full examples and the escape hatches between tiers.

Key Features

  • Single-file apps: Everything in one markdown file with frontmatter
  • 9 data sources: SQLite, JSON, CSV, REST APIs, PostgreSQL, exec scripts, markdown, WASM, computed
  • Auto-rendering: Tables, selects, and lists generated from data
  • Real-time updates: WebSocket-powered reactivity
  • Zero config: tinkerdown serve just works
  • Hot reload: Changes reflect immediately

Data Sources

Define sources in your page's frontmatter:

---
sources:
  tasks:
    type: sqlite
    path: ./tasks.db
    query: SELECT * FROM tasks

  users:
    type: rest
    from: https://api.example.com/users

  config:
    type: json
    path: ./_data/config.json
---
Type Description Example
sqlite SQLite databases lvt-source-sqlite-test
json JSON files lvt-source-file-test
csv CSV files lvt-source-file-test
rest REST APIs lvt-source-rest-test
pg PostgreSQL lvt-source-pg-test
exec Shell commands lvt-source-exec-test
markdown Markdown files markdown-data-todo
wasm WASM modules lvt-source-wasm-test
computed Derived/aggregated data computed-source

Auto-Rendering

Generate HTML automatically from data sources:

<!-- Table with actions -->
<table lvt-source="tasks" lvt-columns="title,status" lvt-actions="Edit,Delete">
</table>

<!-- Select dropdown -->
<select lvt-source="categories" lvt-value="id" lvt-label="name">
</select>

<!-- List -->
<ul lvt-source="items" lvt-field="name">
</ul>

See Auto-Rendering Guide for full details.

Interactive Attributes

Attribute Description
lvt-source Connect element to a data source
name (on button) Handle click events
name (on form) Handle form submissions
lvt-on:change Handle input changes
data-confirm Show confirmation dialog before action
data-* Pass data with actions

See lvt-* Attributes Reference for the complete list.

Configuration

Recommended: Configure in frontmatter (single-file apps):

---
title: My App
sources:
  tasks:
    type: sqlite
    path: ./tasks.db
    query: SELECT * FROM tasks
styling:
  theme: clean
---

For complex apps: Use tinkerdown.yaml for shared configuration:

# tinkerdown.yaml - for multi-page apps with shared sources
server:
  port: 3000
sources:
  shared_data:
    type: rest
    from: ${API_URL}
    cache:
      ttl: 5m

See Configuration Reference for when to use each approach.

AI-Assisted Development

Tinkerdown's surface area is small on purpose: a fixed lvt-* attribute vocabulary, frontmatter with a small set of well-defined fields, and a single file that contains the whole app. That gives an LLM very few ways to be wrong.

Describe what you want, and the agent drafts the markdown:

Create a task manager with SQLite storage,
a table showing tasks with title/status/due date,
a form to add tasks, and delete buttons on each row.

The output is a .md file you can read, diff, and hand-edit. No component tree, no build config, no node_modules to reason about.

See AI Generation Guide for tips on Claude Code, Cursor, and other agents.

Documentation

Getting Started:

Guides:

Reference:

Planning:

Development

git clone https://github.com/livetemplate/tinkerdown.git
cd tinkerdown
go mod download
go test ./...
go build -o tinkerdown ./cmd/tinkerdown

License

MIT

Contributing

Contributions welcome! See ROADMAP.md for planned features and current priorities.

Acknowledgements

The framing in this README was sharpened by Thariq Shihipar's The Unreasonable Effectiveness of HTML and the Hacker News discussion around it. Tinkerdown is our attempt at a structured answer to the markdown-vs-HTML tension that piece mapped out.

Documentation

Overview

Package tinkerdown provides the core library for building interactive documentation with markdown files and embedded executable code blocks.

Index

Constants

This section is empty.

Variables

View Source
var DefaultSourceRef string

DefaultSourceRef is the git ref used to construct GitHub source links from `LANG include="..."` blocks when the page's frontmatter doesn't pin one explicitly via `source_ref`. The tinkerdown binary sets this at startup to its build-time version (via `cmd/tinkerdown/main.go`'s ldflags-injected version), so released docs always link at the matching tag.

For `dev` builds (no ldflags) and library callers that don't set it, the fallback in renderSourceFooter resolves to "main".

Concurrency contract: this is a single-writer, set-once-at-startup global. Production code only writes it before any goroutines start, during main() init. Tests must not mutate it from parallel test bodies — if a test needs a different ref, pass it through the page's frontmatter `source_ref` field instead.

Functions

func ParseMarkdown

func ParseMarkdown(content []byte) (*Frontmatter, []*CodeBlock, string, error)

ParseMarkdown parses a markdown file and extracts frontmatter and code blocks. Treats the input as trusted (file-based) content — raw HTML in the markdown body is preserved. For untrusted input use ParseMarkdownWithPartials with allowRawHTML=false.

func ParseMarkdownWithPartials

func ParseMarkdownWithPartials(content []byte, baseDir string, allowRawHTML bool) (*Frontmatter, []*CodeBlock, string, error)

ParseMarkdownWithPartials parses markdown with partial file support. baseDir is used to resolve relative paths in {{partial "file.md"}} directives.

allowRawHTML controls whether <script>, <iframe>, etc. in the markdown body pass through to the rendered output. Pass true for trusted file-based or programmatic content; pass false when the input came from a user (playground via ParseString). See newGoldmarkParser for the threat-model rationale.

func ParseTimeout added in v0.2.0

func ParseTimeout(s string) time.Duration

ParseTimeout parses a duration string like "2s" / "1500ms" and returns the duration; falls back to defaultEmbedTimeout for empty or invalid input. Exported so other tinkerdown packages (e.g. tests, future proxy integration) can share the same parsing rules.

func ProcessEmbedLvt added in v0.2.0

func ProcessEmbedLvt(htmlStr string, req *http.Request) string

ProcessEmbedLvt scans rendered page HTML for `embed-lvt` placeholders and replaces each with the wrapper HTML returned by the upstream LiveTemplate app. Forwards the docs reader's Cookie and Accept-Language headers so cookie-based auth keeps working when apps share a cookie domain.

On error or timeout, the placeholder becomes a small "live demo unavailable" badge and the page renders without the embed.

Same-origin embeds (path-only, no `server` attribute) resolve against the request's host so the docs site can embed apps proxied through itself with zero CORS or origin configuration.

func ProcessPartials

func ProcessPartials(content []byte, baseDir string, seen map[string]bool) ([]byte, error)

ProcessPartials recursively processes {{partial "file.md"}} directives in content. baseDir is the directory to resolve relative paths from. seen tracks already-included files to prevent circular dependencies.

func SetMermaidRenderer added in v0.1.3

func SetMermaidRenderer(r DiagramRenderer)

SetMermaidRenderer registers a renderer that will be used to pre-render ```mermaid fenced blocks during ParseMarkdown. Pass nil to disable. Safe to call once during server initialization.

Types

type Action

type Action struct {
	Kind      string              `yaml:"kind"`                // Action kind: "sql", "http", "exec"
	Source    string              `yaml:"source,omitempty"`    // For sql: source name to execute against
	Statement string              `yaml:"statement,omitempty"` // For sql: SQL statement with :param placeholders
	URL       string              `yaml:"url,omitempty"`       // For http: request URL (supports template expressions)
	Method    string              `yaml:"method,omitempty"`    // For http: HTTP method (default: POST)
	Body      string              `yaml:"body,omitempty"`      // For http: request body template
	Cmd       string              `yaml:"cmd,omitempty"`       // For exec: command to run
	Params    map[string]ParamDef `yaml:"params,omitempty"`    // Parameter definitions
	Confirm   string              `yaml:"confirm,omitempty"`   // Confirmation message (triggers dialog)
}

Action defines a custom action that can be triggered via button name routing or lvt-on:click.

type BlocksConfig

type BlocksConfig struct {
	AutoID          bool   `yaml:"auto_id"`
	IDFormat        string `yaml:"id_format"`
	ShowLineNumbers bool   `yaml:"show_line_numbers"`
}

BlocksConfig represents code block display configuration.

type ChartOptions

type ChartOptions struct {
	Colors     []string `yaml:"colors,omitempty" json:"colors,omitempty"`
	Stacked    bool     `yaml:"stacked,omitempty" json:"stacked,omitempty"`
	Horizontal bool     `yaml:"horizontal,omitempty" json:"horizontal,omitempty"`
	Legend     *bool    `yaml:"legend,omitempty" json:"legend,omitempty"`
}

ChartOptions holds per-chart customization from frontmatter.

type CodeBlock

type CodeBlock struct {
	Type     string            // "server", "wasm", "lvt"
	Language string            // "go", etc.
	Flags    []string          // "readonly", "editable"
	Metadata map[string]string // id, state, etc.
	Content  string
	Line     int // Line number in source file
}

CodeBlock represents a code block extracted from markdown.

type DiagramRenderer added in v0.1.3

type DiagramRenderer interface {
	Render(source []byte) ([]byte, error)
}

DiagramRenderer renders diagram source to bytes (typically inline SVG). The interface deliberately mirrors internal/diagrams.Renderer so the concrete implementation can live in that subpackage without forcing callers of tinkerdown to import it.

type EmbedRoute added in v0.2.0

type EmbedRoute struct {
	Path     string
	Upstream string
}

EmbedRoute pairs a docs-side path with an upstream HTTP origin. The server registers a reverse-proxy at `Path` forwarding to `Upstream` for both HTTP and WebSocket upgrades, so the embed-lvt block's browser-side connection can reach the deployed app via the docs origin.

type FeaturesConfig

type FeaturesConfig struct {
	HotReload bool `yaml:"hot_reload"`
	Sidebar   bool `yaml:"sidebar"` // Show navigation sidebar
}

FeaturesConfig represents feature flags.

type Frontmatter

type Frontmatter struct {
	// Page metadata
	Title       string      `yaml:"title"`
	Description string      `yaml:"description,omitempty"` // Used for <meta name="description"> + og:description
	Image       string      `yaml:"image,omitempty"`       // Path/URL used for og:image (falls back to site logo)
	Type        string      `yaml:"type"`                  // tutorial, guide, reference, playground
	Persist     PersistMode `yaml:"persist"`               // none, localstorage, server
	Steps       int         `yaml:"steps"`

	// Top-level convenience options
	Sidebar *bool `yaml:"sidebar,omitempty"` // Show navigation sidebar (overrides features.sidebar)

	// Layout selects the page shell. "" or "docs" (default) renders the full
	// docs chrome (sidebar, breadcrumbs, opinionated typography). "landing"
	// renders a minimal full-bleed shell for bespoke marketing pages — no
	// sidebar, no content-wrapper clamp, no docs typography — while still
	// loading the client JS (so embed-lvt demos work) and any styling.custom_css.
	// Unknown values fall back to the docs layout.
	Layout string `yaml:"layout,omitempty"`

	// LvtShowSource toggles the page-level default for ` “`lvt ` block source
	// display. When true, every ` “`lvt ` block on the page renders both its
	// template source as a syntax-highlighted code listing AND the live
	// interactive widget. Per-block `show-source` / `hide-source` flags
	// override this default. Defaults to nil/false to preserve existing
	// behavior — opt-in for documentation pages.
	LvtShowSource *bool `yaml:"lvt_show_source,omitempty"`

	// Source provenance — used to render an "Edit this page" link that
	// points at the canonical source file in its origin repo. Useful when
	// a page was synced from another repo and the docs site is not the
	// canonical home of the content. Both default to "" (use site-level
	// repository + the page's own relative path).
	SourceRepo string `yaml:"source_repo,omitempty"` // e.g. "https://github.com/livetemplate/livetemplate"
	SourcePath string `yaml:"source_path,omitempty"` // e.g. "docs/guides/progressive-complexity.md"
	// SourceRef pins the git ref used in include source-link footers
	// (tag/branch/commit). Resolution order: this field if set;
	// otherwise tinkerdown.DefaultSourceRef (populated from the
	// binary's release version by cmd/tinkerdown/main.go); otherwise
	// "main".
	SourceRef string `yaml:"source_ref,omitempty"`
	// SourceCommit records the exact upstream commit at sync time. It is
	// displayed as provenance in site chrome when present.
	SourceCommit string `yaml:"source_commit,omitempty"`

	// Chart customization (keyed by heading slug)
	Charts map[string]ChartOptions `yaml:"charts,omitempty"`

	// Config options (can override livemdtools.yaml)
	Sources  map[string]SourceConfig `yaml:"sources,omitempty"`
	Actions  map[string]Action       `yaml:"actions,omitempty"`
	Styling  *StylingConfig          `yaml:"styling,omitempty"`
	Blocks   *BlocksConfig           `yaml:"blocks,omitempty"`
	Features *FeaturesConfig         `yaml:"features,omitempty"`

	// Computed expressions found in the markdown content (populated during parsing)
	// Map of expression ID to expression string (e.g., "expr-1" -> "count(tasks where done)")
	Expressions map[string]string `yaml:"-"`

	// Schedule tokens found in the markdown content (populated during parsing)
	Schedules []*schedule.Token `yaml:"-"`

	// Imperative commands (Notify, Run action) found in the markdown (populated during parsing)
	Imperatives []*schedule.Imperative `yaml:"-"`

	// Schedule parsing warnings (populated during parsing)
	ScheduleWarnings []schedule.ParseWarning `yaml:"-"`

	// HasCharts indicates the page has {chart:...} annotations (populated during parsing)
	HasCharts bool `yaml:"-"`

	// HasMermaid indicates the page has “`mermaid fenced blocks (populated during parsing)
	HasMermaid bool `yaml:"-"`
}

Frontmatter represents the YAML frontmatter at the top of a markdown file.

type InteractiveBlock

type InteractiveBlock struct {
	ID       string
	StateRef string // References a ServerBlock ID
	Template *livetemplate.Template
	Store    interface{} // State object with action methods (uses method dispatch)
	Content  string      // Template content
	Metadata map[string]string
}

InteractiveBlock represents a live UI component powered by server state. Each block is a mini livetemplate instance.

type MessageEnvelope

type MessageEnvelope struct {
	BlockID string          `json:"blockID"`
	Action  string          `json:"action"`
	Data    json.RawMessage `json:"data"`
}

MessageEnvelope wraps messages with block ID routing information.

type MessageRouter

type MessageRouter struct {
	// contains filtered or unexported fields
}

MessageRouter routes messages to appropriate blocks based on blockID.

func NewMessageRouter

func NewMessageRouter(ps *PageState) *MessageRouter

NewMessageRouter creates a new message router for a page state.

func (*MessageRouter) Route

func (mr *MessageRouter) Route(envelope *MessageEnvelope) (*ResponseEnvelope, error)

Route routes an incoming message to the appropriate handler.

type Page

type Page struct {
	ID                string
	Title             string
	Description       string // From frontmatter; used for <meta description> + og:description
	Image             string // From frontmatter; used for og:image
	Type              string // tutorial, guide, reference, playground
	SourceFile        string // Absolute path to source .md file (for error messages)
	SourceRepo        string // Origin GitHub repo URL from frontmatter (for edit link)
	SourcePath        string // Path within origin repo from frontmatter (for edit link)
	SourceRef         string // Git ref from frontmatter, when known
	SourceCommit      string // Immutable source commit from frontmatter, when known
	Sidebar           *bool  // nil = use default, true/false = explicit override
	Layout            string // From frontmatter; "" / "docs" = default shell, "landing" = minimal full-bleed shell
	Config            PageConfig
	StaticHTML        string
	ServerBlocks      map[string]*ServerBlock
	WasmBlocks        map[string]*WasmBlock
	InteractiveBlocks map[string]*InteractiveBlock
	// Expressions maps expression IDs to expression strings (e.g., "expr-0" -> "count(tasks where done)")
	// These are computed expressions found in inline code spans like `=count(tasks where done)`
	Expressions map[string]string

	// Schedules contains parsed schedule tokens found in the markdown content
	Schedules []*schedule.Token

	// Imperatives contains parsed imperative commands (Notify, Run action) from the markdown
	Imperatives []*schedule.Imperative

	// ScheduleWarnings contains any warnings generated during schedule parsing
	ScheduleWarnings []schedule.ParseWarning

	// HasCharts indicates the page has chart annotations requiring Chart.js
	HasCharts bool

	// HasMermaid indicates the page contains “`mermaid fenced blocks
	// requiring the Mermaid runtime (~3.3MB). Used by the server to
	// conditionally inject the script and avoid penalising pages without
	// any diagrams.
	HasMermaid bool

	// EmbedRoutes is the set of (path, upstream) pairs declared by
	// ` “`embed-lvt path="..." upstream="..." ` blocks on this page.
	// The server walks every page after Discover() and turns these into
	// auto-registered reverse-proxy routes, so authors don't have to
	// duplicate the upstream coordinates in tinkerdown.yaml.
	EmbedRoutes []EmbedRoute

	// IncludedFiles holds the absolute paths of files referenced by
	// ` “`LANG include="..." ` fences on this page. The watcher uses
	// this list to broadcast a reload when any included file changes,
	// so docs stay in sync with the real source they cite.
	IncludedFiles []string
}

Page represents a parsed tinkerdown tutorial/guide/playground.

func BuildPage

func BuildPage(id, sourceFile string, content []byte) (*Page, error)

BuildPage creates a Page from raw content with a specified ID and source file. This is useful for testing and programmatic page creation. The content should be valid markdown with optional frontmatter.

Treats input as trusted (programmatic/test callers control the content) — raw HTML in the markdown body is preserved.

func New

func New(id string) *Page

New creates a new Page with the given ID.

func ParseFile

func ParseFile(path string) (*Page, error)

ParseFile parses a markdown file and creates a Page. Includes (`include="..."` fence attributes) are confined to the markdown file's own directory tree.

To allow includes to reach files elsewhere within a site (e.g. a recipes/ page citing a shared `_app/` directory), use ParseFileInSite — which widens the include-confinement root to the site directory while still rejecting paths that escape it.

func ParseFileInSite added in v0.2.1

func ParseFileInSite(path, siteRoot string) (*Page, error)

ParseFileInSite parses a markdown file as part of a multi-page site rooted at siteRoot. Includes (`include="..."`) may reference files anywhere within siteRoot, not just the page's own directory subtree; paths that would escape siteRoot are still rejected.

`path` must be located within siteRoot (otherwise every include — including local ones in the page's own directory — would fail confinement against a root that isn't an ancestor of the page). This is enforced upfront with a clear error rather than producing confusing per-include warnings later.

This is the entry point used by `tinkerdown serve` so cross-page references like include="../recipes/counter/_app/counter.go" resolve correctly. Library callers that don't want the wider scope (e.g. rendering a single standalone .md file) should use ParseFile.

If siteRoot is empty, behaves identically to ParseFile (no path-under-root check).

func ParseString

func ParseString(content string) (*Page, error)

ParseString parses markdown content from a string and creates a Page. This is useful for the playground where content comes from user input. Raw HTML in the body is omitted to prevent XSS — playground submissions are untrusted.

func (*Page) ParseFiles

func (p *Page) ParseFiles(files ...string) error

ParseFiles parses markdown files and updates the Page.

type PageConfig

type PageConfig struct {
	// Page behavior
	Persist   PersistMode
	MultiStep bool
	StepCount int

	// Effective config (merged from frontmatter + site config)
	Sources  map[string]SourceConfig
	Actions  map[string]Action
	Styling  StylingConfig
	Blocks   BlocksConfig
	Features FeaturesConfig
}

PageConfig contains configuration for a page.

func (*PageConfig) MergeFromFrontmatter

func (pc *PageConfig) MergeFromFrontmatter(fm *Frontmatter)

MergeFromFrontmatter applies frontmatter config options to PageConfig. Frontmatter values take precedence over any existing values.

type PageState

type PageState struct {

	// Current step for multi-step tutorials
	CurrentStep int

	// Interactive block states (stored as generic interfaces)
	// State objects use method dispatch for action handling
	InteractiveStates map[string]interface{}

	// Code edits for WASM blocks (blockID -> code)
	CodeEdits map[string]string

	// Completed steps tracking
	CompletedSteps []int
	// contains filtered or unexported fields
}

PageState manages the runtime state of a livemdtools session.

func NewPageState

func NewPageState(page *Page) *PageState

NewPageState creates a new page state for a session.

func (*PageState) HandleAction

func (ps *PageState) HandleAction(action string, data map[string]interface{}) error

HandleAction processes page-level actions.

type ParamDef

type ParamDef struct {
	Type     string `yaml:"type,omitempty"`     // Parameter type: "string", "number", "date", "bool"
	Required bool   `yaml:"required,omitempty"` // Whether the parameter is required
	Default  string `yaml:"default,omitempty"`  // Default value
}

ParamDef defines a parameter for an action.

type ParseError

type ParseError struct {
	File    string // Source file path
	Line    int    // Line number (1-indexed)
	Column  int    // Column number (1-indexed, optional)
	Message string // Error message
	Code    string // Offending code snippet
	Hint    string // Helpful suggestion
	Related string // Related information (e.g., "State 'foo' defined at line 20")
}

ParseError represents a detailed parsing error with context.

func NewParseError

func NewParseError(file string, line int, message string) *ParseError

NewParseError creates a new ParseError.

func (*ParseError) Error

func (e *ParseError) Error() string

Error implements the error interface.

func (*ParseError) Format

func (e *ParseError) Format() string

Format returns a nicely formatted error message with context.

func (*ParseError) WithColumn

func (e *ParseError) WithColumn(col int) *ParseError

WithColumn adds column information to the error.

func (*ParseError) WithHint

func (e *ParseError) WithHint(hint string) *ParseError

WithHint adds a helpful hint to the error.

func (*ParseError) WithRelated

func (e *ParseError) WithRelated(related string) *ParseError

WithRelated adds related information to the error.

type PersistMode

type PersistMode string

PersistMode determines how tutorial state is persisted.

const (
	PersistNone         PersistMode = "none"
	PersistLocalStorage PersistMode = "localstorage"
	PersistServer       PersistMode = "server"
)

type ResponseEnvelope

type ResponseEnvelope struct {
	BlockID string                 `json:"blockID"`
	Tree    map[string]interface{} `json:"tree,omitempty"`
	Meta    map[string]interface{} `json:"meta"`
}

ResponseEnvelope wraps responses with block ID routing information.

type ServerBlock

type ServerBlock struct {
	ID       string
	Language string // Currently only "go"
	Content  string
	Metadata map[string]string
}

ServerBlock represents author-written server-side code. This code is trusted, pre-compiled, and powers interactive blocks.

type SourceConfig

type SourceConfig struct {
	Type        string                 `yaml:"type"`                   // exec, pg, rest, csv, json, markdown, sqlite, wasm
	Cmd         string                 `yaml:"cmd,omitempty"`          // For exec type
	Query       string                 `yaml:"query,omitempty"`        // For pg type
	From        string                 `yaml:"from,omitempty"`         // For rest type: API endpoint URL
	File        string                 `yaml:"file,omitempty"`         // For csv/json/markdown types
	Anchor      string                 `yaml:"anchor,omitempty"`       // For markdown: section anchor (e.g., "#todos")
	DB          string                 `yaml:"db,omitempty"`           // For sqlite: database file path
	Table       string                 `yaml:"table,omitempty"`        // For sqlite: table name
	Path        string                 `yaml:"path,omitempty"`         // For wasm: path to .wasm file
	QueryFile   string                 `yaml:"query_file,omitempty"`   // For graphql: path to .graphql file
	Variables   map[string]interface{} `yaml:"variables,omitempty"`    // For graphql: query variables
	Headers     map[string]string      `yaml:"headers,omitempty"`      // For rest: HTTP headers (env vars expanded)
	QueryParams map[string]string      `yaml:"query_params,omitempty"` // For rest: URL query parameters
	ResultPath  string                 `yaml:"result_path,omitempty"`  // For rest: dot-path to extract array (e.g., "data.items")
	Readonly    *bool                  `yaml:"readonly,omitempty"`     // For markdown/sqlite: read-only mode (default: true)
	Options     map[string]string      `yaml:"options,omitempty"`
	Manual      bool                   `yaml:"manual,omitempty"`    // For exec: require Run button click
	Format      string                 `yaml:"format,omitempty"`    // For exec: output format (json, lines, csv)
	Delimiter   string                 `yaml:"delimiter,omitempty"` // For exec CSV: field delimiter (default ",")
	Env         map[string]string      `yaml:"env,omitempty"`       // For exec: environment variables (env vars expanded)
	Timeout     string                 `yaml:"timeout,omitempty"`   // For exec/rest: timeout (e.g., "30s", "1m")
	AutoBind    *bool                  `yaml:"auto_bind,omitempty"` // Set to false to exclude from auto-table matching

	// For computed sources
	GroupBy   string            `yaml:"group_by,omitempty"`  // Field to group by
	Aggregate map[string]string `yaml:"aggregate,omitempty"` // Field → aggregation expression
	Filter    string            `yaml:"filter,omitempty"`    // Optional filter expression
}

SourceConfig represents a data source configuration for lvt-source blocks.

type StatusType

type StatusType string

StatusType represents the type of status banner

const (
	StatusSuccess StatusType = "success"
	StatusWarning StatusType = "warning"
	StatusError   StatusType = "error"
	StatusInfo    StatusType = "info"
)

type StylingConfig

type StylingConfig struct {
	Theme        string `yaml:"theme"`
	PrimaryColor string `yaml:"primary_color"`
	Font         string `yaml:"font"`
}

StylingConfig represents styling/theme configuration.

type Tab

type Tab struct {
	Name   string // Display name of the tab
	Filter string // Filter expression (empty for "All" tab)
}

Tab represents a single tab definition parsed from a heading.

type WasmBlock

type WasmBlock struct {
	ID            string
	Language      string // Currently only "go"
	DefaultCode   string
	ShowRunButton bool
	Metadata      map[string]string
}

WasmBlock represents student-editable code that runs in the browser. This code is untrusted and never sent to the server.

Directories

Path Synopsis
cmd
tinkerdown command
Command tinkerdown is the CLI tool for creating and serving interactive documentation.
Command tinkerdown is the CLI tool for creating and serving interactive documentation.
docs
research/examples/custom-sources/go command
Change freeze checker for tinkerdown Checks if deploys are allowed based on calendar rules
Change freeze checker for tinkerdown Checks if deploys are allowed based on calendar rules
examples
lvt-source-wasm-test/sources command
Example WASM source module for tinkerdown.
Example WASM source module for tinkerdown.
internal
assets
Package assets embeds the client JavaScript, CSS, and vendor libraries
Package assets embeds the client JavaScript, CSS, and vendor libraries
blocks
Package blocks provides block type registry and management.
Package blocks provides block type registry and management.
cache
Package cache provides caching functionality for data sources.
Package cache provides caching functionality for data sources.
diagrams
Package diagrams provides server-side renderers for diagram-as-code blocks (currently mermaid via Kroki) so tinkerdown sites can ship pre-rendered SVGs and skip the heavyweight client-side runtime.
Package diagrams provides server-side renderers for diagram-as-code blocks (currently mermaid via Kroki) so tinkerdown sites can ship pre-rendered SVGs and skip the heavyweight client-side runtime.
include
Package include resolves and slices files referenced by the `include="..."` fence-block attribute in tinkerdown markdown.
Package include resolves and slices files referenced by the `include="..."` fence-block attribute in tinkerdown markdown.
markdown
Package markdown provides markdown parsing functionality for livemdtools.
Package markdown provides markdown parsing functionality for livemdtools.
output
Package output provides notification outputs for tinkerdown.
Package output provides notification outputs for tinkerdown.
runtime
Package runtime provides expression evaluation for computed expressions.
Package runtime provides expression evaluation for computed expressions.
schedule
Package schedule provides parsing and execution of schedule tokens.
Package schedule provides parsing and execution of schedule tokens.
security
Package security provides shared security validation functions.
Package security provides shared security validation functions.
server
Package server provides HTTP handlers for the tinkerdown server.
Package server provides HTTP handlers for the tinkerdown server.
slug
Package slug provides heading-to-anchor conversion (GitHub-style slugification).
Package slug provides heading-to-anchor conversion (GitHub-style slugification).
source
Package source provides data source implementations for lvt-source blocks.
Package source provides data source implementations for lvt-source blocks.
state
Package state provides state management for livemdtools.
Package state provides state management for livemdtools.
wasm
Package wasm provides WASM-based data source support for tinkerdown.
Package wasm provides WASM-based data source support for tinkerdown.
pkg
embedded
Package embedded provides functionality for running tinkerdown apps from embedded filesystems.
Package embedded provides functionality for running tinkerdown apps from embedded filesystems.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL