From d0a34ef28a1c20f637f7005120963e1313246c97 Mon Sep 17 00:00:00 2001 From: Julien Calixte Date: Thu, 2 Jul 2026 22:04:06 +0200 Subject: [PATCH] docs(format): define the portable Macroplan TOML format v1 Document the schema, value types, and render semantics so a Macroplan .toml can be produced and consumed outside this app's renderer. Ships a JSON Schema (served from public/ for editor use) and a reference file. --- README.md | 13 +-- docs/format.md | 154 +++++++++++++++++++++++++++++++++++ docs/macroplan.example.toml | 79 ++++++++++++++++++ public/macroplan.schema.json | 108 ++++++++++++++++++++++++ 4 files changed, 348 insertions(+), 6 deletions(-) create mode 100644 docs/format.md create mode 100644 docs/macroplan.example.toml create mode 100644 public/macroplan.schema.json diff --git a/README.md b/README.md index 41a052e..2da6b81 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,10 @@ Dashboard ┣━━━━━◯ 🔴 n ## Documentation -| Document | What it covers | -| ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| [CONTEXT.md](CONTEXT.md) | **Ubiquitous language** — the glossary: Macroplan, Feature, Original Estimate, Re-estimate, Delivery, Milestone, Week, Now line, Learning, Status, and the symbol legend | -| [DESIGN.md](DESIGN.md) | **Goal-driven design (QFD)** — goals, functions, the Goal→Function→How→Component cascade, the House/Roof matrices + rendered House of Quality, critical performance budget, trade-offs, and watched tensions | -| [docs/adr/0001-original-estimate-as-baseline.md](docs/adr/0001-original-estimate-as-baseline.md) | Why on-time/late is judged against the **Original Estimate**, never a re-estimate | -| [docs/adr/0002-local-first-no-backend.md](docs/adr/0002-local-first-no-backend.md) | Why the app is **local-first with no backend**, and TOML is the portable source of truth | +| Document | What it covers | +| ------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [docs/format.md](docs/format.md) | **The Macroplan TOML format (v1)** — the portable, tool-independent schema: fields, value types, the `macroplan_version` marker, and the render semantics a consumer must honor. Ships a [JSON Schema](public/macroplan.schema.json) and a [reference `.toml`](docs/macroplan.example.toml). | +| [CONTEXT.md](CONTEXT.md) | **Ubiquitous language** — the glossary: Macroplan, Feature, Original Estimate, Re-estimate, Delivery, Milestone, Week, Now line, Learning, Status, and the symbol legend | +| [DESIGN.md](DESIGN.md) | **Goal-driven design (QFD)** — goals, functions, the Goal→Function→How→Component cascade, the House/Roof matrices + rendered House of Quality, critical performance budget, trade-offs, and watched tensions | +| [docs/adr/0001-original-estimate-as-baseline.md](docs/adr/0001-original-estimate-as-baseline.md) | Why on-time/late is judged against the **Original Estimate**, never a re-estimate | +| [docs/adr/0002-local-first-no-backend.md](docs/adr/0002-local-first-no-backend.md) | Why the app is **local-first with no backend**, and TOML is the portable source of truth | diff --git a/docs/format.md b/docs/format.md new file mode 100644 index 0000000..281ae5c --- /dev/null +++ b/docs/format.md @@ -0,0 +1,154 @@ +# Macroplan TOML Format — v1 + +The portable, tool-independent definition of a **Macroplan**: a week-by-week +delivery plan that records what a team promised, every time an estimate slipped, +and when work actually shipped — judged against the **first** estimate, never a +moved goalpost. + +This document is the contract. Any tool can produce or consume a `.toml` file +that conforms to it and render the same plan. The [macroplan app](../README.md) +is one such consumer; nothing here is specific to it. + +- **Machine-readable schema:** [`macroplan.schema.json`](../public/macroplan.schema.json), + served at `https://macroplan.apoena.dev/macroplan.schema.json` (JSON Schema + 2020-12; validates the TOML-decoded structure). +- **Reference file:** [`macroplan.example.toml`](macroplan.example.toml) + (exercises every field and state; a conformance sample). +- **Serialization:** [TOML v1.0.0](https://toml.io). Values use TOML's native + types — dates are **bare date literals**, not quoted strings (see _Dates_). + +## Versioning + +A file MAY declare the format version at the top level: + +```toml +macroplan_version = 1 +``` + +- **Optional.** An absent marker means the current version (`1`). Every existing + file therefore stays valid. +- **Integer**, monotonically increasing. This document defines version **1**. +- A consumer that understands version _N_ **MUST reject** a file declaring a + version **greater than** _N_ (rather than silently mis-rendering it), and + SHOULD accept any version ≤ _N_ it still supports. +- The version is bumped only for a **breaking** change to the schema or render + semantics below — a new optional field does not require a bump. + +Producers SHOULD emit `macroplan_version` so a file self-describes its target. + +## Top-level fields + +| Key | Required | Type | Default | Meaning | +| ------------------- | -------- | ------------- | ---------------------- | ------------------------------------------------------------------------------------- | +| `macroplan_version` | no | integer (`1`) | `1` | Format version (see above). | +| `title` | no | string | `"Untitled Macroplan"` | Display name of the plan. | +| `start` | no | date | auto-fit | Left edge of the plan's week span. **Only extends** the span; never clips a feature. | +| `end` | no | date | auto-fit | Right edge of the plan's week span. **Only extends** the span; never clips a feature. | + +Zero or more `[[feature]]` blocks and zero or more `[[milestone]]` blocks follow. + +## `[[feature]]` + +A unit of work, read left-to-right as a story: where it started, what was first +promised, every slip, and when it shipped. + +| Key | Required | Type | Default | Meaning | +| ------------- | -------- | -------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `name` | **yes** | non-empty string | — | Identifies the feature; the **join key** for a milestone's `requires`. | +| `start` | **yes** | date | — | The week work began. | +| `original` | **yes** | date | — | The **Original Estimate** — the immovable baseline all lateness is judged against ([ADR-0001](adr/0001-original-estimate-as-baseline.md)). | +| `reestimates` | no | array of dates | `[]` | Each later week the estimate slipped to (a slip, `△`). Order-independent. | +| `delivered` | no | date | — | The week the feature actually shipped. Absent → still in flight. | +| `learning` | no | string | — | A post-delivery takeaway. | +| `status` | no | `"on-track"` \| `"at-risk"` \| `"off-track"` | — | In-flight confidence. Meaningful only while undelivered. | +| `note` | no | string | — | A short note accompanying `status`. | + +**Constraint — unique names:** feature `name` values MUST be unique within a +plan. Because milestone `requires` resolves by name, a duplicate would make a +requirement ambiguous. Conforming consumers reject duplicates. + +## `[[milestone]]` + +A dated checkpoint that depends on a set of features being delivered by then. + +| Key | Required | Type | Default | Meaning | +| ---------- | -------- | ---------------- | ------- | ------------------------------------------------------------------- | +| `name` | **yes** | non-empty string | — | Display name of the milestone. | +| `week` | **yes** | date | — | When the milestone falls due. | +| `requires` | no | array of strings | `[]` | Feature `name`s that must be delivered on/before `week` to meet it. | + +A `requires` entry that names no existing feature counts as **unmet** (it can +never be satisfied) — useful for catching typos. + +## Value types + +### Dates → weeks + +Every date field is a **calendar date**, written as a TOML bare date literal: + +```toml +original = 2026-06-15 +reestimates = [2026-06-29, 2026-07-13] +``` + +- **Canonical form is the bare date literal.** A quoted `"2026-06-15"` string is + _tolerated_ on input for convenience, but producers SHOULD emit bare literals + so the same plan always serializes identically. +- **Any day snaps to its ISO-week Monday.** A Macroplan's unit is the week, keyed + by the Monday (yyyy-mm-dd) of the ISO week containing the date. `2026-06-17` + (a Wednesday) and `2026-06-15` (that Monday) denote the **same week**. Every + conforming renderer MUST apply this snapping, or dates land in the wrong column. + +### Status + +The enum `on-track` \| `at-risk` \| `off-track`. No other value is valid. Lateness +is **derived, never authored** — there is deliberately no "late" status. + +## Render semantics (the contract a renderer must honor) + +Two tools should draw the same plan from the same file. These rules are as much +a part of the format as the fields: + +1. **On-time vs. late is judged only against `original`** — never a re-estimate + ([ADR-0001](adr/0001-original-estimate-as-baseline.md)). `delivered ≤ original` + (by week) is on time; `delivered > original` is late. +2. **Marker vocabulary** per feature: + - `◯` **original** — the Original Estimate week, while undelivered. + - `△` **re-estimate** — one per `reestimates` entry. + - `◉` **delivered on time** — a delivery on/before `original`. It **subsumes** + the `◯` (no separate original marker is drawn). + - `▲` **delivered late** — a delivery after `original`; the `◯` baseline is + **kept** alongside it. +3. **Bar extent.** A feature's bar runs from `start` to its furthest marker. A + delivered bar ends at its delivery. An **overdue** feature (undelivered, and + already past its furthest estimate relative to "now") keeps running to the + current week. +4. **Plan span.** The rendered week range runs from the earliest to the latest + week among all features' markers and all milestones. Optional `start`/`end` + only widen this range with lead-in / trailing weeks; a marker outside them is + never clipped. +5. **Milestone met/unmet.** A required feature is **unmet** at a milestone if it + is undelivered, delivered _after_ the milestone `week`, or names no existing + feature. +6. **"Now".** The current week (Monday of today) drives overdue extension and the + "now" line. It is contextual, not stored in the file. + +## Notes for implementers + +- **Unknown keys.** The JSON Schema is strict (`additionalProperties: false`) so + editors flag typos like `titel`. The reference parser is more lenient — it + ignores unrecognized top-level keys. Producers SHOULD NOT emit unknown keys. +- **Empty plan.** A file with no features and no milestones is valid; with an + authored `start`/`end` it renders as an empty grid across that span. +- **Editor integration.** Point [Taplo](https://taplo.tamasfe.dev)-based editors + at the schema with a directive on the first line of a plan file: + `#:schema https://macroplan.apoena.dev/macroplan.schema.json`. + +## See also + +- [CONTEXT.md](../CONTEXT.md) — the ubiquitous language (Feature, Original + Estimate, Re-estimate, Delivery, Milestone, Week, Status, Learning). +- [ADR-0001](adr/0001-original-estimate-as-baseline.md) — why lateness is judged + against the Original Estimate. +- [ADR-0002](adr/0002-local-first-no-backend.md) — why the `.toml` file is the + portable source of truth. diff --git a/docs/macroplan.example.toml b/docs/macroplan.example.toml new file mode 100644 index 0000000..7629962 --- /dev/null +++ b/docs/macroplan.example.toml @@ -0,0 +1,79 @@ +#:schema https://macroplan.apoena.dev/macroplan.schema.json +# +# Reference Macroplan — a conformance sample exercising every field and state. +# The authoritative definition of this format is docs/format.md. + +# Format version. Optional; the current version is 1. Omitting it means "1". +macroplan_version = 1 + +title = "Q3 — Checkout revamp" + +# Optional plan span: pad the plan with lead-in / trailing weeks. +# Rule: `start` only ever extends the span left, `end` extends it right; +# neither clips a feature. Omit both to auto-fit the columns. +start = 2026-05-25 +end = 2026-08-03 + +# Dates are TOML date literals. Any day snaps to that week's Monday. + +# On-time delivery: delivered on/before the Original Estimate → ◉ (subsumes ◯). +[[feature]] +name = "Auth" +start = 2026-06-01 +original = 2026-06-15 +delivered = 2026-06-15 +learning = "Spiking the OAuth flow first paid off — do discovery spikes earlier." + +# Late delivery with slips: ◯ baseline kept, one △ per re-estimate, then ▲. +# Late because delivery (07-20) is after `original` (06-15) — the re-estimates +# never move the goalpost (ADR-0001). +[[feature]] +name = "Payments" +start = 2026-06-01 +original = 2026-06-15 +reestimates = [2026-06-29, 2026-07-13] +delivered = 2026-07-20 +learning = "Vendor lead time was the real constraint — derisk vendors up front." + +# In-flight and overdue: undelivered, `original` already in the past → ◯ only, +# bar runs to "now". `status` + `note` express confidence while it's open. +[[feature]] +name = "Dashboard" +start = 2026-06-01 +original = 2026-06-08 +status = "off-track" +note = "No recovery plan yet — needs an owner." + +# In-flight, re-estimated once: ◯ + △, still open. +[[feature]] +name = "Search" +start = 2026-06-08 +original = 2026-06-22 +reestimates = [2026-07-06] +status = "at-risk" +note = "Third-party search API is flaky; spike a fallback." + +# In-flight, on track: the minimal feature — only the three required fields +# plus a status. +[[feature]] +name = "Notifications" +start = 2026-06-22 +original = 2026-07-06 +status = "on-track" + +# Milestones tie a due week to the features that must ship by then. +[[milestone]] +name = "Code freeze" +week = 2026-06-29 +requires = ["Auth", "Payments"] + +[[milestone]] +name = "MVP go-live" +week = 2026-07-06 +requires = ["Auth", "Payments", "Dashboard"] + +# A milestone with no requirements is valid too. +[[milestone]] +name = "Beta launch" +week = 2026-07-13 +requires = ["Search", "Notifications"] diff --git a/public/macroplan.schema.json b/public/macroplan.schema.json new file mode 100644 index 0000000..f2c50e9 --- /dev/null +++ b/public/macroplan.schema.json @@ -0,0 +1,108 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://macroplan.apoena.dev/macroplan.schema.json", + "title": "Macroplan", + "description": "A week-by-week delivery plan. Portable TOML format v1 — see docs/format.md. Validates the TOML-decoded structure; dates are TOML date literals, surfaced to JSON Schema as date-formatted strings.", + "type": "object", + "additionalProperties": false, + "properties": { + "macroplan_version": { + "description": "Format version. Omit for the current version (1). A newer version must be rejected by a consumer that only understands 1.", + "type": "integer", + "const": 1 + }, + "title": { + "description": "Display name of the plan. Defaults to \"Untitled Macroplan\" when absent.", + "type": "string" + }, + "start": { + "description": "Optional left edge of the plan's week span. Only extends the span; never clips a feature. Snapped to its ISO-week Monday.", + "type": "string", + "format": "date" + }, + "end": { + "description": "Optional right edge of the plan's week span. Only extends the span; never clips a feature. Snapped to its ISO-week Monday.", + "type": "string", + "format": "date" + }, + "feature": { + "description": "The features of the plan. Written as [[feature]] blocks. Names must be unique.", + "type": "array", + "items": { "$ref": "#/$defs/feature" } + }, + "milestone": { + "description": "Dated checkpoints. Written as [[milestone]] blocks.", + "type": "array", + "items": { "$ref": "#/$defs/milestone" } + } + }, + "$defs": { + "date": { + "description": "A calendar date (TOML date literal, e.g. 2026-06-15). Snapped to its ISO-week Monday.", + "type": "string", + "format": "date" + }, + "feature": { + "type": "object", + "additionalProperties": false, + "required": ["name", "start", "original"], + "properties": { + "name": { + "description": "Identifies the feature; the join key for a milestone's `requires`. Must be unique within the plan.", + "type": "string", + "minLength": 1 + }, + "start": { + "description": "The week work began.", + "$ref": "#/$defs/date" + }, + "original": { + "description": "The Original Estimate — the immovable baseline all lateness is judged against (ADR-0001).", + "$ref": "#/$defs/date" + }, + "reestimates": { + "description": "Each later week the estimate slipped to. Order-independent.", + "type": "array", + "items": { "$ref": "#/$defs/date" } + }, + "delivered": { + "description": "The week the feature actually shipped. Absent means still in flight.", + "$ref": "#/$defs/date" + }, + "learning": { + "description": "A post-delivery takeaway.", + "type": "string" + }, + "status": { + "description": "In-flight confidence. Meaningful only while undelivered. Lateness is derived, never authored.", + "enum": ["on-track", "at-risk", "off-track"] + }, + "note": { + "description": "A short note accompanying `status`.", + "type": "string" + } + } + }, + "milestone": { + "type": "object", + "additionalProperties": false, + "required": ["name", "week"], + "properties": { + "name": { + "description": "Display name of the milestone.", + "type": "string", + "minLength": 1 + }, + "week": { + "description": "When the milestone falls due.", + "$ref": "#/$defs/date" + }, + "requires": { + "description": "Feature names that must be delivered on/before `week` to meet the milestone.", + "type": "array", + "items": { "type": "string" } + } + } + } + } +}