tickets: triage TODO files → nbd tickets
Root TODO.md: - 82df74: Add justfiles for all projects (lint/check/build/release) - 09cda0: Add contact footer to all service front-ends nbd/TODO.md: - 39b2cf: Add --xml output format to nbd edu/TODO.md: - 59c122: Deploy edu mdbook to Cloudflare Pages (vibebooks.elijah.run) - 8618e4: Write chapter on co-op worker-owned business structure - 8f14c6: Write Machine Learning chapter (self-play game AI) - 389d8d: Write chapter on creating and training a simple LLM - 0fbe1a: Write chapter on shader programming Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>main
parent
ba6425793a
commit
aa4e9fdbfe
@ -0,0 +1,64 @@
|
|||||||
|
+++
|
||||||
|
title = "Add contact footer to all service front-ends"
|
||||||
|
priority = 3
|
||||||
|
status = "todo"
|
||||||
|
ticket_type = "task"
|
||||||
|
dependencies = []
|
||||||
|
+++
|
||||||
|
## Background
|
||||||
|
|
||||||
|
The root `TODO.md` calls for a footer on every service front-end with contact info:
|
||||||
|
|
||||||
|
> Contact: `<site>@elijah.run`
|
||||||
|
|
||||||
|
Where `<site>` depends on the service.
|
||||||
|
|
||||||
|
## Target services and email addresses
|
||||||
|
|
||||||
|
| Service | Front-end type | Contact email |
|
||||||
|
|---|---|---|
|
||||||
|
| `quotesdb` | Yew (Rust/Wasm) | `quotes@elijah.run` |
|
||||||
|
| `edu` | mdbook static site | `vibedbooks@elijah.run` |
|
||||||
|
| Blog editor | Yew (not yet built) | `editor@elijah.run` |
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### quotesdb/ui
|
||||||
|
|
||||||
|
Add a `Footer` component to the Yew app:
|
||||||
|
|
||||||
|
1. Create `quotesdb/ui/src/components/footer.rs` (or add inline to `app.rs`).
|
||||||
|
2. Render a `<footer>` element at the bottom of every page with the text:
|
||||||
|
`Contact: quotes@elijah.run`
|
||||||
|
3. Use an `<a href="mailto:quotes@elijah.run">` tag so it is clickable.
|
||||||
|
4. Include the footer in the top-level layout so every route shows it.
|
||||||
|
|
||||||
|
### edu (mdbook)
|
||||||
|
|
||||||
|
mdbook supports injecting HTML before/after the content via the `[output.html]` table in `book.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[output.html]
|
||||||
|
additional-js = []
|
||||||
|
additional-css = []
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use the `before-content` / `after-content` HTML templates. More precisely, create `edu/theme/footer.hbs` with the footer markup and register it in `book.toml`.
|
||||||
|
|
||||||
|
Alternatively (simpler): add a `theme/` directory with an `index.hbs` that extends the default template and appends the footer. The mdbook docs for custom theming describe this approach.
|
||||||
|
|
||||||
|
Simplest option: add `footer.html` fragment via `[output.html] additional-js` hack, or just add a sentence at the bottom of each page — but that is not scalable.
|
||||||
|
|
||||||
|
**Recommended approach for mdbook:** Use mdbook's `theme/index.hbs` override (copy the default from the mdbook source) and add the footer HTML before the closing `</body>` tag:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<footer style="text-align:center;padding:1em;opacity:0.6">
|
||||||
|
Contact: <a href="mailto:vibedbooks@elijah.run">vibedbooks@elijah.run</a>
|
||||||
|
</footer>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files involved
|
||||||
|
|
||||||
|
- `edu/theme/index.hbs` (new — copy from mdbook default, add footer)
|
||||||
|
- `edu/book.toml` (may need `[output.html] theme = "theme"` entry)
|
||||||
|
- `quotesdb/ui/src/components/footer.rs` (new) or similar
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
+++
|
||||||
|
title = "Add justfiles for all projects with lint, check, build, build-release, and release recipes"
|
||||||
|
priority = 5
|
||||||
|
status = "todo"
|
||||||
|
ticket_type = "task"
|
||||||
|
dependencies = []
|
||||||
|
+++
|
||||||
|
## Background
|
||||||
|
|
||||||
|
The root `TODO.md` calls for each project in the mono-repo to have a `justfile` with standard recipe names so that any contributor can run the same commands regardless of project type. `just` is a command runner similar to Make but with cleaner syntax.
|
||||||
|
|
||||||
|
## Recipes to add per project
|
||||||
|
|
||||||
|
| Recipe | What it runs |
|
||||||
|
|---|---|
|
||||||
|
| `lint` | Auto-format: `cargo fmt`, `tofu fmt`, `mdbook build` (edu), etc. |
|
||||||
|
| `check` | Static checks: `cargo check && cargo clippy`, `tofu validate && tofu plan`, etc. |
|
||||||
|
| `build` | Dev build: `cargo build`, `trunk build`, `mdbook build` |
|
||||||
|
| `build-release` | Optimised binary or bundle: `cargo build --release`, `trunk build --release` |
|
||||||
|
| `release` | Deploy/publish: `tofu apply`, `wrangler deploy` (where applicable) |
|
||||||
|
|
||||||
|
Not every project needs every recipe; stub missing ones with an error or a no-op comment.
|
||||||
|
|
||||||
|
## Per-project breakdown
|
||||||
|
|
||||||
|
### `nbd/justfile`
|
||||||
|
- `lint`: `cargo fmt`
|
||||||
|
- `check`: `cargo check && cargo clippy && cargo test`
|
||||||
|
- `build`: `cargo build`
|
||||||
|
- `build-release`: `cargo build --release`
|
||||||
|
- `release`: n/a (CLI tool, no deployment)
|
||||||
|
|
||||||
|
### `edu/justfile`
|
||||||
|
- `lint`: `mdbook build` (build validates the content)
|
||||||
|
- `check`: `mdbook build`
|
||||||
|
- `build`: `mdbook build`
|
||||||
|
- `build-release`: same as `build` (mdbook has no separate release mode)
|
||||||
|
- `release`: TBD — CF Pages auto-deploys from main branch once infra is configured (see edu deployment ticket)
|
||||||
|
|
||||||
|
### `quotesdb/api/justfile`
|
||||||
|
- `lint`: `cargo fmt`
|
||||||
|
- `check`: `cargo check && cargo clippy`
|
||||||
|
- `build`: `cargo build`
|
||||||
|
- `build-release`: `cargo build --release`
|
||||||
|
- `release`: `wrangler deploy` (or `tofu apply` from `infra/`)
|
||||||
|
|
||||||
|
### `quotesdb/ui/justfile`
|
||||||
|
- `lint`: `cargo fmt`
|
||||||
|
- `check`: `cargo check && cargo clippy`
|
||||||
|
- `build`: `trunk build`
|
||||||
|
- `build-release`: `trunk build --release`
|
||||||
|
- `release`: CF Pages auto-deploys from main branch once infra is configured
|
||||||
|
|
||||||
|
## Files to create
|
||||||
|
|
||||||
|
- `nbd/justfile`
|
||||||
|
- `edu/justfile`
|
||||||
|
- `quotesdb/api/justfile`
|
||||||
|
- `quotesdb/ui/justfile`
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- Recipe names must be consistent across all projects (`lint`, `check`, `build`, `build-release`, `release`).
|
||||||
|
- Commands are run from the directory containing the `justfile`, so paths are relative to the project directory.
|
||||||
|
- Add a comment above each recipe explaining what it does.
|
||||||
|
- Check whether the `just` package is already in each project's Nix dev shell (`flake.nix`); add it if missing.
|
||||||
@ -1,7 +0,0 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
version = 4
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "vibed"
|
|
||||||
version = "0.1.0"
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "vibed"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2024"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
|
||||||
# It is not intended for manual editing.
|
|
||||||
version = 4
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "common"
|
|
||||||
version = "0.1.0"
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "common"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
license = "MIT OR Apache-2.0"
|
|
||||||
description = "Shared types, utilities, and definitions for Vibesville services"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
//! Shared types, utilities, and definitions for Vibesville services.
|
|
||||||
//!
|
|
||||||
//! This crate provides common building blocks used across multiple services
|
|
||||||
//! in the Vibesville mono-repo. Services depend on it via a path dependency:
|
|
||||||
//!
|
|
||||||
//! ```toml
|
|
||||||
//! [dependencies]
|
|
||||||
//! common = { path = "../common" }
|
|
||||||
//! ```
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
||||||
@ -1 +0,0 @@
|
|||||||
//! Unit tests for the common crate.
|
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
+++
|
||||||
|
title = "edu: write chapter on shader programming"
|
||||||
|
priority = 3
|
||||||
|
status = "todo"
|
||||||
|
ticket_type = "task"
|
||||||
|
dependencies = []
|
||||||
|
+++
|
||||||
|
## Background
|
||||||
|
|
||||||
|
From `edu/TODO.md`: Hands-on: Shader programming.
|
||||||
|
|
||||||
|
A practical introduction to GPU shaders, written with Rust as the host language. The course covers the graphics pipeline conceptually and has hands-on exercises writing WGSL shaders driven by `wgpu`.
|
||||||
|
|
||||||
|
## Content outline (suggested)
|
||||||
|
|
||||||
|
### Part 1 — The GPU and the Graphics Pipeline
|
||||||
|
1. CPU vs GPU: parallel execution model
|
||||||
|
2. The programmable pipeline: vertex shaders, fragment shaders, compute shaders
|
||||||
|
3. What is WGSL? (WebGPU Shading Language) — syntax overview
|
||||||
|
|
||||||
|
### Part 2 — Setting Up with wgpu
|
||||||
|
4. What is `wgpu`? Cross-platform graphics API in Rust
|
||||||
|
5. Exercise 1: Create a window and clear it to a colour (the GPU 'hello world')
|
||||||
|
6. The render loop: swap chains, frames, command encoders
|
||||||
|
|
||||||
|
### Part 3 — Vertex and Fragment Shaders
|
||||||
|
7. Vertices, buffers, and the vertex shader
|
||||||
|
8. Interpolation and the fragment shader
|
||||||
|
9. Exercise 2: Draw a coloured triangle
|
||||||
|
10. Exercise 3: Animate the triangle using a time uniform
|
||||||
|
|
||||||
|
### Part 4 — Textures and Samplers
|
||||||
|
11. Texture coordinates (UVs), texture creation, sampler config
|
||||||
|
12. Exercise 4: Render a textured quad
|
||||||
|
|
||||||
|
### Part 5 — Compute Shaders
|
||||||
|
13. Compute pipelines: dispatching work groups
|
||||||
|
14. Storage buffers and read/write access from WGSL
|
||||||
|
15. Exercise 5: GPU-accelerate a particle simulation
|
||||||
|
|
||||||
|
### Part 6 — Going Further
|
||||||
|
16. Post-processing effects (bloom, blur) — conceptual overview
|
||||||
|
17. Signed Distance Fields for font rendering
|
||||||
|
18. Resources: Learn WGPU tutorial, Shadertoy, The Book of Shaders
|
||||||
|
|
||||||
|
## File to create
|
||||||
|
|
||||||
|
- `edu/src/shaders.md`
|
||||||
|
- Add to `edu/src/SUMMARY.md` under a `# Graphics` section
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
+++
|
||||||
|
title = "edu: write chapter on creating and training a simple LLM"
|
||||||
|
priority = 3
|
||||||
|
status = "todo"
|
||||||
|
ticket_type = "task"
|
||||||
|
dependencies = []
|
||||||
|
+++
|
||||||
|
## Background
|
||||||
|
|
||||||
|
From `edu/TODO.md`: Hands-on: Creating and training a simple LLM.
|
||||||
|
|
||||||
|
A practical course on building a small language model from scratch in Rust, covering tokenisation, the Transformer architecture, and a training loop. The goal is deep understanding rather than production scale.
|
||||||
|
|
||||||
|
## Content outline (suggested)
|
||||||
|
|
||||||
|
### Part 1 — What is a Language Model?
|
||||||
|
1. Predicting the next token: the core task
|
||||||
|
2. Tokenisation: BPE, byte-level, character-level — pick character-level for simplicity
|
||||||
|
3. Exercise 1: Build a character-level tokeniser in Rust
|
||||||
|
|
||||||
|
### Part 2 — The Transformer Architecture
|
||||||
|
4. Embeddings and positional encoding
|
||||||
|
5. Self-attention: queries, keys, values — the attention formula
|
||||||
|
6. Multi-head attention
|
||||||
|
7. Feed-forward sublayers, residual connections, layer norm
|
||||||
|
8. Exercise 2: Implement a single attention head in Rust (no ML framework)
|
||||||
|
|
||||||
|
### Part 3 — Assembling the Model
|
||||||
|
9. Stacking Transformer blocks into a decoder-only LM
|
||||||
|
10. Using `candle` (Hugging Face's Rust ML framework) for tensor ops and autodiff
|
||||||
|
11. Exercise 3: Define a small GPT-like model in `candle`
|
||||||
|
|
||||||
|
### Part 4 — Training
|
||||||
|
12. Cross-entropy loss for next-token prediction
|
||||||
|
13. The training loop: forward pass, loss, backward pass, optimizer step
|
||||||
|
14. Exercise 4: Train on a small text corpus (e.g., Shakespeare or a short book)
|
||||||
|
15. Exercise 5: Sample from the model and observe output quality vs. training steps
|
||||||
|
|
||||||
|
### Part 5 — Reflection
|
||||||
|
16. What limits this model? Scale, data, compute
|
||||||
|
17. Pointers to real LLM training (GPT-2, LLaMA, Mistral)
|
||||||
|
18. Further reading
|
||||||
|
|
||||||
|
## File to create
|
||||||
|
|
||||||
|
- `edu/src/llm-from-scratch.md`
|
||||||
|
- Add to `edu/src/SUMMARY.md` under the `# Machine Learning` section
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
+++
|
||||||
|
title = "Deploy edu mdbook to Cloudflare Pages at vibebooks.elijah.run"
|
||||||
|
priority = 5
|
||||||
|
status = "todo"
|
||||||
|
ticket_type = "project"
|
||||||
|
dependencies = []
|
||||||
|
+++
|
||||||
|
## Background
|
||||||
|
|
||||||
|
From `edu/TODO.md`:
|
||||||
|
- Host the mdbook on Cloudflare Pages
|
||||||
|
- Host on vibebooks.elijah.run
|
||||||
|
- Create an `infra/` directory containing opentofu configs for the above
|
||||||
|
- Add a big disclaimer about the content being AI-generated
|
||||||
|
|
||||||
|
## Sub-tasks
|
||||||
|
|
||||||
|
### 1. Add AI-generated disclaimer
|
||||||
|
|
||||||
|
Add a prominent disclaimer page or preface to the mdbook. The disclaimer should say (in the user's words):
|
||||||
|
> these [chapters] are AI generated and not intended to be definitive, trustworthy, or even good, just an experiment in generating tailored educational content about topics I am interested in but not sure where to start, and with a practical focus on exercises with Rust since that is the language I use most often
|
||||||
|
|
||||||
|
Placement options:
|
||||||
|
- A dedicated `src/disclaimer.md` page added first in `src/SUMMARY.md`
|
||||||
|
- Or a callout block at the top of every chapter (requires mdbook preprocessor)
|
||||||
|
- Recommended: `src/disclaimer.md` as the first page, with a short note in each chapter's preamble
|
||||||
|
|
||||||
|
### 2. Create `edu/infra/` with OpenTofu configs
|
||||||
|
|
||||||
|
Follow the pattern from `quotesdb/infra/` (Cloudflare provider, tofu modules).
|
||||||
|
|
||||||
|
Minimal resources:
|
||||||
|
- `cloudflare_pages_project` — create the Pages project named `vibedbooks`
|
||||||
|
- `cloudflare_pages_domain` — bind `vibebooks.elijah.run` to the Pages project
|
||||||
|
- `cloudflare_record` — CNAME DNS record pointing `vibebooks` at the Pages subdomain
|
||||||
|
|
||||||
|
File layout:
|
||||||
|
```
|
||||||
|
edu/infra/
|
||||||
|
├── main.tf # provider config, cloudflare_pages_project
|
||||||
|
├── dns.tf # cloudflare_record and cloudflare_pages_domain
|
||||||
|
├── variables.tf # cloudflare_account_id, cloudflare_zone_id, etc.
|
||||||
|
├── outputs.tf # pages subdomain URL
|
||||||
|
└── .gitignore # *.tfstate, .terraform/
|
||||||
|
```
|
||||||
|
|
||||||
|
All `resource` and `data` blocks must have a comment explaining their purpose (per CLAUDE.md conventions).
|
||||||
|
|
||||||
|
### 3. Configure mdbook build for CF Pages
|
||||||
|
|
||||||
|
Cloudflare Pages can auto-build mdbook sites if given the right build command and output directory. Set in the Pages project config:
|
||||||
|
- Build command: `mdbook build`
|
||||||
|
- Build output directory: `book`
|
||||||
|
- Root directory: `edu/`
|
||||||
|
|
||||||
|
Alternatively, use a CI/CD pipeline (GitHub Actions / Gitea Actions) to build and push to the Pages project via `wrangler pages deploy`.
|
||||||
|
|
||||||
|
### 4. Add edu/justfile
|
||||||
|
|
||||||
|
Add a `release` recipe to `edu/justfile` (see justfiles ticket) that triggers the Pages deployment once infra is configured.
|
||||||
|
|
||||||
|
## Relevant files
|
||||||
|
|
||||||
|
- `edu/book.toml` — mdbook configuration
|
||||||
|
- `edu/src/SUMMARY.md` — add disclaimer page
|
||||||
|
- `edu/src/disclaimer.md` (new)
|
||||||
|
- `edu/infra/` (new directory)
|
||||||
|
- Cloudflare dashboard: Pages project, DNS zone `elijah.run`
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
- `tofu validate && tofu plan` from `edu/infra/`
|
||||||
|
- `mdbook build` from `edu/` — builds without errors
|
||||||
|
- After deploy: `curl -I https://vibebooks.elijah.run` returns 200
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
+++
|
||||||
|
title = "edu: write chapter on co-op worker-owned business structure"
|
||||||
|
priority = 3
|
||||||
|
status = "todo"
|
||||||
|
ticket_type = "task"
|
||||||
|
dependencies = []
|
||||||
|
+++
|
||||||
|
## Background
|
||||||
|
|
||||||
|
From `edu/TODO.md`: write a chapter about how to structure a co-op profit-sharing worker-owned business.
|
||||||
|
|
||||||
|
This is a conceptual/informational chapter, not a Rust hands-on. It fits naturally in the Vibed Learning book as a practical guide for engineers who might want to start or join a worker cooperative.
|
||||||
|
|
||||||
|
## Content outline (suggested)
|
||||||
|
|
||||||
|
1. **What is a worker cooperative?** — definition, historical examples, how they differ from traditional businesses
|
||||||
|
2. **Legal structures** — co-op incorporation options by jurisdiction (US LCA/LLC, UK LLP, etc.), relevant legislation
|
||||||
|
3. **Profit sharing models** — patronage dividends, equal shares, labor-hour weighted, hybrid approaches
|
||||||
|
4. **Governance** — one-member-one-vote, board structure, decision-making processes
|
||||||
|
5. **Practical startup steps** — founding documents, initial capital, operating agreements
|
||||||
|
6. **Case studies** — Mondragon, REI, Cooperative Home Care Associates, tech co-ops (Loomio, etc.)
|
||||||
|
7. **Resources and further reading**
|
||||||
|
|
||||||
|
## File to create
|
||||||
|
|
||||||
|
- `edu/src/co-op.md`
|
||||||
|
- Add to `edu/src/SUMMARY.md` under a new `# Business` section (or `# Other Topics`)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
This chapter is explicitly not intended to be authoritative legal or financial advice — add the standard AI-generated disclaimer.
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
+++
|
||||||
|
title = "edu: write Machine Learning chapter (self-play game AI, Alpha Go Zero style)"
|
||||||
|
priority = 3
|
||||||
|
status = "todo"
|
||||||
|
ticket_type = "task"
|
||||||
|
dependencies = []
|
||||||
|
+++
|
||||||
|
## Background
|
||||||
|
|
||||||
|
From `edu/TODO.md`: Hands-on: Machine Learning; training a computer to play a game by playing against itself (a-la Alpha Go Zero).
|
||||||
|
|
||||||
|
A self-play reinforcement learning course. The practical focus is implementing a simplified version of the MCTS + neural network self-play loop in Rust, targeting a simple deterministic two-player game (e.g., Tic-Tac-Toe or Connect Four).
|
||||||
|
|
||||||
|
## Content outline (suggested)
|
||||||
|
|
||||||
|
### Part 1 — Foundations
|
||||||
|
1. What is reinforcement learning? (state, action, reward, policy, value)
|
||||||
|
2. Monte Carlo Tree Search (MCTS) — algorithm explained step by step
|
||||||
|
3. Why self-play? The AlphaGo Zero insight
|
||||||
|
|
||||||
|
### Part 2 — The Game
|
||||||
|
4. Choosing a simple game: Tic-Tac-Toe as the learning vehicle
|
||||||
|
5. Representing game state in Rust
|
||||||
|
6. Exercise 1: Implement the game logic (move generation, win detection, terminal states)
|
||||||
|
|
||||||
|
### Part 3 — MCTS
|
||||||
|
7. Implementing MCTS in Rust (selection, expansion, simulation, backpropagation)
|
||||||
|
8. Exercise 2: Play Tic-Tac-Toe with pure MCTS (no neural network)
|
||||||
|
|
||||||
|
### Part 4 — Neural Network Policy/Value Head
|
||||||
|
9. Overview of the network architecture (shared trunk + policy head + value head)
|
||||||
|
10. Integrating a neural network crate (e.g., `tch-rs` or `candle`)
|
||||||
|
11. Exercise 3: Train the network on MCTS-generated data
|
||||||
|
12. Exercise 4: Replace MCTS simulation with the learned value function
|
||||||
|
|
||||||
|
### Part 5 — Self-Play Loop
|
||||||
|
13. The full Alpha Go Zero training loop: generate data → train → evaluate → repeat
|
||||||
|
14. Exercise 5: Run 1000 self-play games and observe the policy improving
|
||||||
|
|
||||||
|
## File to create
|
||||||
|
|
||||||
|
- `edu/src/ml-self-play.md`
|
||||||
|
- Add to `edu/src/SUMMARY.md` under a `# Machine Learning` section
|
||||||
@ -0,0 +1,104 @@
|
|||||||
|
+++
|
||||||
|
title = "Add --xml output format that wraps each ticket section in XML tags"
|
||||||
|
priority = 4
|
||||||
|
status = "todo"
|
||||||
|
ticket_type = "feature"
|
||||||
|
dependencies = []
|
||||||
|
+++
|
||||||
|
## Background
|
||||||
|
|
||||||
|
From `nbd/TODO.md`:
|
||||||
|
|
||||||
|
> Add a `--xml` output format that prints tickets with XML around each section to make it easier to parse metadata.
|
||||||
|
|
||||||
|
Unlike `--json` (which is a complete, structured parse), the XML format adds lightweight tags around each metadata field in the human-readable output. This makes it trivial for scripts to extract specific fields using simple text tools (e.g., `grep`, `sed`, XPath) without pulling in a full JSON parser.
|
||||||
|
|
||||||
|
## Intended XML format
|
||||||
|
|
||||||
|
For a single ticket (`nbd read <id> --xml`):
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<ticket>
|
||||||
|
<id>a3f9c2</id>
|
||||||
|
<title>Fix login bug</title>
|
||||||
|
<priority>8</priority>
|
||||||
|
<status>in_progress</status>
|
||||||
|
<type>bug</type>
|
||||||
|
<dependencies>
|
||||||
|
<dep>b7d41e</dep>
|
||||||
|
<dep>c9e823</dep>
|
||||||
|
</dependencies>
|
||||||
|
<body>Users cannot log in with email addresses containing +</body>
|
||||||
|
</ticket>
|
||||||
|
```
|
||||||
|
|
||||||
|
For a list (`nbd list --xml`):
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<tickets>
|
||||||
|
<ticket>...</ticket>
|
||||||
|
<ticket>...</ticket>
|
||||||
|
</tickets>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### `src/main.rs`
|
||||||
|
|
||||||
|
Add a global `--xml` flag to the `Cli` struct, parallel to `--json`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// Output XML instead of a human-readable table.
|
||||||
|
#[arg(long, global = true)]
|
||||||
|
xml: bool,
|
||||||
|
```
|
||||||
|
|
||||||
|
Precedence: if both `--json` and `--xml` are supplied, `--json` wins (or return an error — TBD, but JSON-first is simpler).
|
||||||
|
|
||||||
|
Pass `cli.xml` through `dispatch` to every command handler, alongside `cli.json`.
|
||||||
|
|
||||||
|
Each command handler signature gains an `xml: bool` parameter. When `xml` is true and `json` is false, call the new `display::print_ticket_xml` / `display::print_list_xml` functions.
|
||||||
|
|
||||||
|
### `src/display.rs`
|
||||||
|
|
||||||
|
Add:
|
||||||
|
|
||||||
|
- `format_ticket_xml(ticket: &Ticket) -> String` — serialises a single ticket as XML
|
||||||
|
- `print_ticket_xml(ticket: &Ticket)` — wraps `format_ticket_xml` + `println!`
|
||||||
|
- `format_list_xml(tickets: &[Ticket]) -> String` — wraps list in `<tickets>`
|
||||||
|
- `print_list_xml(tickets: &[Ticket])`
|
||||||
|
|
||||||
|
XML escaping: at minimum, escape `&`, `<`, `>`, `"`, `'` in field values. Use a small helper `xml_escape(s: &str) -> String` rather than pulling in an XML crate.
|
||||||
|
|
||||||
|
For `dependencies`: render each as a `<dep>` child element.
|
||||||
|
|
||||||
|
### `src/tests.rs`
|
||||||
|
|
||||||
|
Add unit tests:
|
||||||
|
- `format_ticket_xml` with a ticket that has special characters in the title and body (`&`, `<`, `>`)
|
||||||
|
- `format_ticket_xml` with empty dependencies
|
||||||
|
- `format_ticket_xml` with multiple dependencies
|
||||||
|
- `format_list_xml` with zero and multiple tickets
|
||||||
|
|
||||||
|
### `tests/integration.rs`
|
||||||
|
|
||||||
|
Add integration tests:
|
||||||
|
- `nbd read <id> --xml` returns valid XML containing the ticket's fields
|
||||||
|
- `nbd list --xml` returns valid XML wrapping multiple tickets
|
||||||
|
- `nbd create ... --xml` returns the created ticket as XML
|
||||||
|
- `nbd update ... --xml` returns the updated ticket as XML
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
All commands that support `--json` should also support `--xml`:
|
||||||
|
- `nbd create`
|
||||||
|
- `nbd read`
|
||||||
|
- `nbd list`
|
||||||
|
- `nbd update`
|
||||||
|
- `nbd ready`
|
||||||
|
- `nbd next`
|
||||||
|
- `nbd archive`
|
||||||
|
- `nbd migrate`
|
||||||
|
- `nbd graph` (the JSON graph format has a defined structure; XML should mirror it)
|
||||||
|
- `nbd claude-md` (wrap snippet in `<snippet>` tag)
|
||||||
|
- `nbd init` (wrap root path in `<init>`)
|
||||||
@ -1,3 +0,0 @@
|
|||||||
fn main() {
|
|
||||||
println!("Hello, world!");
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue