Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 24 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ Tinkerdown replaces typical app scaffolding with a single markdown file — data

- **One file = one app.** Data connections, layout, and interactions all live in one place. No build step, no node_modules, no boilerplate.
- **AI gets it right.** A single declarative file with no component tree or state management means less surface area for LLMs to misconfigure.
- **8 data sources out of the box.** SQLite, PostgreSQL, REST APIs, JSON, CSV, shell commands, markdown, and WASM. Point at existing infrastructure and get a working UI.
- **Start simple, add power as needed.** Plain markdown tables become editable grids. Add YAML frontmatter for databases, or drop to HTML + Go templates for full control.
- **9 data sources out of the box.** SQLite, PostgreSQL, REST APIs, JSON, CSV, shell commands, markdown, WASM, and computed/derived sources. Point at existing infrastructure and get a working UI.
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README claims “9 data sources out of the box” but the codebase also supports a graphql source type (and the docs include it). Either include GraphQL in the list and update the count, or adjust the wording to avoid an incorrect/fragile count.

Suggested change
- **9 data sources out of the box.** SQLite, PostgreSQL, REST APIs, JSON, CSV, shell commands, markdown, WASM, and computed/derived sources. Point at existing infrastructure and get a working UI.
- **10 data sources out of the box.** SQLite, PostgreSQL, REST APIs, GraphQL, JSON, CSV, shell commands, markdown, WASM, and computed/derived sources. Point at existing infrastructure and get a working UI.

Copilot uses AI. Check for mistakes.
- **Progressive complexity.** Write standard markdown — task lists become interactive, tables auto-bind to data sources. Need more control? Add HTML attributes. Need full custom layouts? Drop to Go templates. Each step builds on the last without rewriting. See the [Progressive Complexity Guide](docs/guides/progressive-complexity.md).
- **Git-native and self-hosted.** Plain text in a repo. Version history, search, collaboration, offline access, no subscriptions.
- **Made for disposable software.** The admin panel for this sprint. The tracker for that hiring round. Software you'd never scaffold a React project for, but that's useful for days or weeks.

Expand All @@ -36,35 +36,43 @@ tinkerdown serve

## What You Can Build

Write a single markdown file with frontmatter configuration:
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:

```markdown
---
title: Task Manager
sources:
tasks:
type: sqlite
path: ./tasks.db
query: SELECT * FROM tasks
db: ./tasks.db
table: tasks
readonly: false
Comment on lines 44 to +49
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README now uses db/table for sqlite in the primary example, but later in the same README there’s still a YAML snippet using legacy path/query (and path for JSON) which don’t match the current config keys. For consistency and copy/paste correctness, update the remaining snippets to use db/table (sqlite) and file (json/csv/markdown), etc.

Copilot uses AI. Check for mistakes.
---

# Task Manager

<table lvt-source="tasks" lvt-columns="title,status,due_date" lvt-actions="Complete,Delete">
</table>

<form lvt-submit="AddTask">
<input name="title" placeholder="New task" required>
<button type="submit">Add</button>
</form>
## Tasks
| Title | Status | Due Date |
|-------|--------|----------|
```

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

<p align="center">
<img src="docs/assets/auto-table-demo.png" alt="Screenshot showing an auto-generated expense tracker with data table, edit/delete buttons per row, and an add form — all from a markdown table and YAML source definition" width="720">
</p>

**Need more control?** Use HTML attributes for explicit binding:

```html
<table lvt-source="tasks" lvt-columns="title,status" lvt-datatable lvt-actions="Complete,Delete">
</table>
```

## Key Features

- **Single-file apps**: Everything in one markdown file with frontmatter
- **8 data sources**: SQLite, JSON, CSV, REST APIs, PostgreSQL, exec scripts, markdown, WASM
- **9 data sources**: SQLite, JSON, CSV, REST APIs, PostgreSQL, exec scripts, markdown, WASM, computed
- **Auto-rendering**: Tables, selects, and lists generated from data
Comment on lines 74 to 76
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The “9 data sources” feature list omits the graphql source type that’s supported in code and documented elsewhere. Consider including GraphQL here (and updating the count), or removing the numeric count to avoid it getting out of sync as source types evolve.

Copilot uses AI. Check for mistakes.
- **Real-time updates**: WebSocket-powered reactivity
- **Zero config**: `tinkerdown serve` just works
Expand Down Expand Up @@ -102,6 +110,7 @@ sources:
| `exec` | Shell commands | [lvt-source-exec-test](examples/lvt-source-exec-test) |
| `markdown` | Markdown files | [markdown-data-todo](examples/markdown-data-todo) |
| `wasm` | WASM modules | [lvt-source-wasm-test](examples/lvt-source-wasm-test) |
| `computed` | Derived/aggregated data | [computed-source](examples/computed-source) |

## Auto-Rendering

Expand Down Expand Up @@ -189,6 +198,7 @@ See [AI Generation Guide](docs/guides/ai-generation.md) for tips on using Claude
- [Project Structure](docs/getting-started/project-structure.md)

**Guides:**
- [Progressive Complexity](docs/guides/progressive-complexity.md)
- [Data Sources](docs/guides/data-sources.md)
- [Auto-Rendering](docs/guides/auto-rendering.md)
- [Go Templates](docs/guides/go-templates.md)
Expand Down
Binary file added docs/assets/auto-table-demo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 31 additions & 2 deletions docs/guides/auto-rendering.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,37 @@
# Auto-Rendering Components

Tinkerdown can automatically generate HTML for common UI patterns from data sources. This eliminates boilerplate template code for tables and select dropdowns.
Tinkerdown can automatically generate HTML for common UI patterns from data sources. There are two levels of auto-rendering: inference-based (markdown tables) and attribute-based (HTML elements).

## Select Dropdowns
## Auto-Tables (Inference-Based)

The simplest way to display data: write a standard markdown table under a heading that matches a source name. Tinkerdown infers the binding automatically.

```yaml
---
sources:
users:
type: rest
from: https://jsonplaceholder.typicode.com/users
---
```

```markdown
## Users
| Name | Email | Phone |
|------|-------|-------|
```

The heading "Users" matches the source "users", so the table auto-populates with data. For writable sources, an add form and edit/delete buttons are generated automatically.

For more details, see the [Progressive Complexity Guide](progressive-complexity.md).

---

## HTML-Based Auto-Rendering

For more control over the generated UI, use HTML elements with `lvt-source` and related attributes.

### Select Dropdowns

Transform an empty `<select>` element into a fully populated dropdown by connecting it to a data source.

Expand Down
1 change: 1 addition & 0 deletions docs/guides/data-sources.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ sources:
| [csv](../sources/csv.md) | CSV files | Spreadsheet data, imports |
| [markdown](../sources/markdown.md) | Markdown files | Content management |
| [wasm](../sources/wasm.md) | WebAssembly modules | Custom sources |
| [computed](../sources/computed.md) | Derived/aggregated data | Dashboards, summaries |
Comment on lines 36 to +39
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This guide still shows legacy sqlite configuration keys (path/query) elsewhere in the file, but the current config struct expects db + table for sqlite sources. Since this PR adds a new source type row, it’s a good opportunity to update the existing sqlite examples on this page to the correct keys (and similarly align other source examples if they use outdated field names).

Copilot uses AI. Check for mistakes.

## Frontmatter Configuration (Recommended)

Expand Down
187 changes: 187 additions & 0 deletions docs/guides/progressive-complexity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
# Progressive Complexity

Tinkerdown lets you build increasingly complex apps by graduating through natural complexity tiers. Each tier builds on the previous one — nothing needs rewriting. Start with pure markdown and add capabilities as your needs grow.

## Tier 0: Pure Markdown

Write standard markdown. Task lists become interactive automatically — checkboxes toggle, new items can be added, changes persist to the file.

```markdown
# Shopping List
- [ ] Milk
- [x] Bread
- [ ] Eggs
```

No YAML, no HTML, no configuration. Just markdown.

**What you get:** Interactive checkboxes, add-item form, file persistence.

**Examples:** [auto-tasks](../../examples/auto-tasks/)

---

## Tier 1: Markdown + YAML Sources

Define data sources in YAML frontmatter and write standard markdown tables. Tinkerdown matches heading names to source names and auto-generates the appropriate UI.

```yaml
---
sources:
expenses:
type: sqlite
db: ./expenses.db
table: expenses
readonly: false
---

# Expense Tracker

## Expenses
| Description | Category | Amount |
|-------------|----------|--------|
```

The heading "Expenses" matches the source named "expenses". Since the source is writable (`readonly: false`), Tinkerdown generates a full CRUD interface: data table with edit and delete buttons, plus an add form. Amount gets a number input because schema introspection detects it as a numeric column.

For read-only sources (REST APIs, JSON files), the table auto-populates with a refresh button and no CRUD controls.

### Computed Sources

Derive aggregated data from other sources without writing code:

```yaml
---
sources:
expenses:
type: sqlite
db: ./expenses.db
table: expenses
by_category:
type: computed
from: expenses
group_by: category
aggregate:
total: sum(amount)
count: count()
---

## By Category
| Category | Total | Count |
|----------|-------|-------|
```

Supported aggregation functions: `sum()`, `count()`, `avg()`, `min()`, `max()`. Computed sources auto-refresh when the parent source changes. Note: a computed source cannot reference another computed source as its parent — only real data sources (SQLite, REST, etc.).

### How Source Matching Works

Tinkerdown matches markdown table headings to source names using smart matching:

1. **Exact match:** Heading "Expenses" (slug: `expenses`) matches source `expenses`
2. **Underscore normalization:** Heading "By Category" (slug: `by-category`) also matches source `by_category`
3. **Word containment:** Heading "My Monthly Expenses" matches source `expenses` (the slug `my-monthly-expenses` contains `expenses` at a word boundary)
4. **No match:** The table renders as a normal static markdown table

Exact and underscore-normalized matches take precedence over word containment. If containment is ambiguous (heading matches multiple sources), Tinkerdown skips with a warning. Use `auto_bind: false` on a source to exclude it from matching — useful for generic names like `data` or `items`:

```yaml
sources:
status:
type: rest
from: https://api.example.com/status
auto_bind: false # don't auto-match to heading "Status"
```

**What you get:** Auto-populated tables, CRUD for writable sources, schema-aware form inputs, computed aggregations.

**Examples:** [auto-table-sqlite](../../examples/auto-table-sqlite/), [auto-table-rest](../../examples/auto-table-rest/), [computed-source](../../examples/computed-source/)

---

## Tier 2: HTML + lvt-* Attributes

When auto-inference can't give you what you need — custom layouts, explicit action buttons, confirmation dialogs, datatable features, cross-source selects — use HTML elements with `lvt-*` attributes.

```html
<table lvt-source="tasks" lvt-columns="title,status" lvt-datatable lvt-actions="Complete,Delete">
</table>

<form lvt-submit="Add" lvt-reset-on:success>
<input name="title" placeholder="New task" required>
<select name="priority" lvt-source="priorities" lvt-value="id" lvt-label="name"></select>
<button type="submit">Add</button>
</form>

<button lvt-click="ClearDone" lvt-confirm="Delete all completed tasks?">Clear Done</button>
```

The escape from Tier 1 to Tier 2 is clean: if the auto-generated UI isn't right, replace the markdown table with explicit HTML. The auto-generated template can serve as a starting point.

**What you get:** Explicit data binding, custom forms, action buttons with confirmation, datatable with sorting/pagination, cross-source selects.

**References:** [Auto-Rendering Guide](auto-rendering.md), [lvt-* Attributes Reference](../reference/lvt-attributes.md)

---

## Tier 3: Go Templates

For full control over rendering, use Go template syntax inside `lvt` code blocks:

````markdown
```lvt
<div lvt-source="expenses">
{{if .Error}}
<div class="error">{{.Error}}</div>
{{else}}
{{range .Data}}
<div class="card">
<h3>{{.Description}}</h3>
<span class="badge">{{.Category}}</span>
<span class="amount">${{.Amount}}</span>
<button lvt-click="Delete" lvt-data-id="{{.Id}}">Remove</button>
</div>
{{end}}
{{end}}
</div>
```
````

Go templates give you conditionals, loops, custom HTML structure, and access to the full state object (`.Data`, `.Error`, `.Errors`).

**What you get:** Conditional rendering, custom HTML layouts, multiple data iterations, error handling.

**References:** [Go Templates Guide](go-templates.md)

---

## Tier 4: Custom Data Sources

For data that doesn't fit built-in source types, write a custom source in TinyGo compiled to WASM. The module exports a `fetch` function that returns JSON data:

```yaml
sources:
custom:
type: wasm
path: ./sources/custom.wasm
```

Use `tinkerdown new myapp --template wasm-source` to scaffold a working WASM source project with build scripts and a test app.

**What you get:** Arbitrary data fetching logic, compiled to WASM, runs server-side.

**References:** [WASM Source Docs](../sources/wasm.md)

---

## When to Graduate

| If you need... | Use |
|----------------|-----|
| Interactive task lists | Tier 0 — just write markdown checkboxes |
| Data tables from databases/APIs | Tier 1 — markdown tables + YAML sources |
| Aggregated dashboards | Tier 1 — computed sources |
| Custom form layouts | Tier 2 — HTML + lvt-* attributes |
| Conditional rendering | Tier 3 — Go templates |
| Custom data fetching | Tier 4 — WASM sources |

The key principle: **start at the lowest tier that works.** Each tier adds complexity but also capability. If you're writing HTML when a markdown table would suffice, you're working harder than you need to.
13 changes: 12 additions & 1 deletion docs/reference/frontmatter.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,24 @@ All source types can be defined in frontmatter:

| Type | Example |
|------|---------|
| `sqlite` | `type: sqlite`<br>`path: ./data.db`<br>`query: SELECT * FROM tasks` |
| `sqlite` | `type: sqlite`<br>`db: ./data.db`<br>`table: tasks` |
| `rest` | `type: rest`<br>`from: https://api.example.com/data` |
| `json` | `type: json`<br>`path: ./_data/data.json` |
| `csv` | `type: csv`<br>`path: ./_data/data.csv` |
| `exec` | `type: exec`<br>`command: uname -a` |
| `markdown` | `type: markdown`<br>`path: ./_data/posts/` |
| `wasm` | `type: wasm`<br>`module: ./custom.wasm` |
| `computed` | `type: computed`<br>`from: expenses`<br>`group_by: category` |

#### Source Options

| Field | Applies To | Description |
|-------|-----------|-------------|
| `readonly` | sqlite, markdown | Set to `false` to enable write operations (default: `true`) |
| `auto_bind` | all | Set to `false` to exclude from auto-table heading matching |
| `group_by` | computed | Field to group rows by |
| `aggregate` | computed | Map of output field to aggregation expression (`sum()`, `count()`, `avg()`, `min()`, `max()`) |
| `filter` | computed | Filter expression applied before grouping (e.g., `status = active`) |

See [Data Sources Guide](../guides/data-sources.md) for full details on each type.

Expand Down
Loading
Loading