diff --git a/README.md b/README.md index bb025ce..ae60274 100644 --- a/README.md +++ b/README.md @@ -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. +- **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. @@ -36,7 +36,7 @@ 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 --- @@ -44,27 +44,35 @@ title: Task Manager sources: tasks: type: sqlite - path: ./tasks.db - query: SELECT * FROM tasks + 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. +Run `tinkerdown serve` and get a fully interactive app with database persistence — no HTML needed: + +

+ 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 +

+ +**Need more control?** Use HTML attributes for explicit binding: + +```html + +
+``` ## 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 - **Real-time updates**: WebSocket-powered reactivity - **Zero config**: `tinkerdown serve` just works @@ -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 @@ -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) diff --git a/docs/assets/auto-table-demo.png b/docs/assets/auto-table-demo.png new file mode 100644 index 0000000..0f4ce93 Binary files /dev/null and b/docs/assets/auto-table-demo.png differ diff --git a/docs/guides/auto-rendering.md b/docs/guides/auto-rendering.md index 9123fe3..2f23eef 100644 --- a/docs/guides/auto-rendering.md +++ b/docs/guides/auto-rendering.md @@ -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 ` + + + + + +``` + +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 +
+ {{if .Error}} +
{{.Error}}
+ {{else}} + {{range .Data}} +
+

{{.Description}}

+ {{.Category}} + ${{.Amount}} + +
+ {{end}} + {{end}} +
+``` +```` + +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. diff --git a/docs/reference/frontmatter.md b/docs/reference/frontmatter.md index c9c018b..e83c5fb 100644 --- a/docs/reference/frontmatter.md +++ b/docs/reference/frontmatter.md @@ -73,13 +73,24 @@ All source types can be defined in frontmatter: | Type | Example | |------|---------| -| `sqlite` | `type: sqlite`
`path: ./data.db`
`query: SELECT * FROM tasks` | +| `sqlite` | `type: sqlite`
`db: ./data.db`
`table: tasks` | | `rest` | `type: rest`
`from: https://api.example.com/data` | | `json` | `type: json`
`path: ./_data/data.json` | | `csv` | `type: csv`
`path: ./_data/data.csv` | | `exec` | `type: exec`
`command: uname -a` | | `markdown` | `type: markdown`
`path: ./_data/posts/` | | `wasm` | `type: wasm`
`module: ./custom.wasm` | +| `computed` | `type: computed`
`from: expenses`
`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. diff --git a/docs/sources/computed.md b/docs/sources/computed.md new file mode 100644 index 0000000..b6d0b92 --- /dev/null +++ b/docs/sources/computed.md @@ -0,0 +1,125 @@ +# Computed Source + +The `computed` source type derives data from another source by applying grouping, aggregation, and filtering operations. It's read-only and automatically refreshes when its parent source changes. + +## Configuration + +```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() + filter: "status = active" # optional +``` + +| Field | Required | Description | +|-------|----------|-------------| +| `from` | Yes | Name of the parent source to derive from | +| `group_by` | No | Field to group rows by. If omitted, produces a single aggregate row | +| `aggregate` | Yes | Map of output field name to aggregation expression | +| `filter` | No | Filter expression applied before grouping (e.g., `"status = active"`) | + +## Aggregation Functions + +| Function | Description | Example | +|----------|-------------|---------| +| `count()` | Count of rows | `count: count()` | +| `sum(field)` | Sum of numeric field | `total: sum(amount)` | +| `avg(field)` | Average of numeric field (skips nil/non-numeric) | `average: avg(amount)` | +| `min(field)` | Minimum value | `lowest: min(amount)` | +| `max(field)` | Maximum value | `highest: max(amount)` | + +## Examples + +### Category Breakdown + +```yaml +sources: + expenses: + type: sqlite + db: ./data.db + table: expenses + readonly: false + by_category: + type: computed + from: expenses + group_by: category + aggregate: + total: sum(amount) + count: count() +``` + +Output rows: +``` +[ + {"category": "Food", "total": 101.70, "count": 3}, + {"category": "Transport", "total": 72.00, "count": 2}, + {"category": "Housing", "total": 1500.00, "count": 1} +] +``` + +### Summary Statistics (No Group By) + +```yaml +sources: + summary: + type: computed + from: expenses + aggregate: + total: sum(amount) + average: avg(amount) + count: count() + min: min(amount) + max: max(amount) +``` + +Produces a single row with all aggregates. + +### Filtered Aggregation + +```yaml +sources: + active_stats: + type: computed + from: tasks + filter: "status = active" + aggregate: + count: count() + total_points: sum(points) +``` + +Only aggregates rows where `status` equals `active` (case-insensitive). + +## Filter Operators + +Filters require the full `field operator value` form. Bare truthy checks (like `done` or `not done`) are not supported — use `done = true` instead. + +| Operator | Description | +|----------|-------------| +| `=` | Equal (case-insensitive) | +| `!=` | Not equal (case-insensitive) | +| `<` | Less than (numeric) | +| `>` | Greater than (numeric) | +| `<=` | Less than or equal | +| `>=` | Greater than or equal | + +## Behavior + +- **Auto-refresh:** When the parent source is modified (add, update, delete), all computed sources that depend on it are automatically refreshed. +- **Deterministic ordering:** Group results are sorted alphabetically by group key. Aggregate columns are sorted alphabetically by output field name. +- **Nil handling:** `sum`, `avg`, `min`, `max` skip nil and non-numeric values. `avg` divides by the count of valid values, not total rows. +- **Read-only:** Computed sources cannot be written to. They always reflect the current state of their parent. + +## Limitations + +- **No chaining:** A computed source cannot reference another computed source as its parent. This is validated at startup. +- **No custom expressions:** Aggregation is limited to the five built-in functions. For complex transformations, use a WASM source. +- **Single parent:** Each computed source derives from exactly one parent source.