diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40dd8fe..304b970 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,33 @@ jobs: . .venv/bin/activate python -m pytest -q + canary: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + + - name: Install + shell: bash + run: | + python -m venv .venv + . .venv/bin/activate + python -m pip install --upgrade pip + python -m pip install -e ".[test]" + + - name: Canary deck suite + shell: bash + run: | + . .venv/bin/activate + python -m pytest -q tests/test_canary_decks.py + lint: runs-on: ubuntu-latest diff --git a/README.md b/README.md index b95fd1e..3b8b59f 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,8 @@ python -m pytest -q pyright clean_slides/ ``` +Release gate checklist: [docs/RELEASE-CHECKLIST.md](docs/RELEASE-CHECKLIST.md) + ## Why this exists I spent six years writing slides in consulting. Most AI slide tools focus on imagery and visual polish — which has its place, but isn't what makes a consulting slide useful. A good slide is a unit of structured argumentation that happens to be visual. You figure out the narrative first, decompose it into pages, then fill each page with the component parts of your argument. diff --git a/docs/CHART-CELLS.md b/docs/CHART-CELLS.md index c9a66b4..2cb2006 100644 --- a/docs/CHART-CELLS.md +++ b/docs/CHART-CELLS.md @@ -1,6 +1,6 @@ # Chart Cells — Design Document -> **Status**: Implemented (alpha). Behavior and schema may still evolve. +> **Status**: Implemented and covered by regression/canary tests. ## Summary @@ -14,6 +14,16 @@ consistent output. Rows with chart cells get equal height. Columns with chart cells get equal width. The tool enforces the constraint; the author focuses on the data. +### Current behavior highlights + +- Horizontal chart-cell labels use manual offsets (`dLblPos=ctr` + manual layout) + to preserve visible spacing from bar ends. +- Waterfall chart-cells support connector overlays and formatted value labels. +- Grouped row headers support `sub` text on a second line, rendered non-bold and + in default body color. +- Chart template copy mode (`chart_template_copy`) is supported through the + chart-cell rendering path when bar template options are set. + --- ## Motivating example @@ -300,55 +310,28 @@ table: --- -## Implementation plan - -### Phase 1: Parsing and validation - -1. **`spec.py`** — Parse `charts:` top-level key into `ChartDef` dataclass. - Detect `chartname-N` references in cells during `TableSpec.from_dict()`. - Store as `ChartRef(name, index)` in the cell grid. - -2. **`placeholder.py`** — Skip chart-ref cells when filling placeholders. - -3. **`validate.py`** — Add all checks from the validation table above. +## Implementation notes -### Phase 2: Sizing +The chart-cell pipeline is fully integrated in the standard YAML flow: -4. **`sizing.py`** — When chart refs are present in a column, that column - participates in equal-width sizing. When chart refs span rows, those rows - participate in equal-height sizing. +- Parsing and validation: `clean_slides/spec.py` +- Placeholder handling: `clean_slides/placeholder.py` +- Layout/sizing constraints: `clean_slides/sizing.py` +- Rendering + chart placement: `clean_slides/renderer.py` +- Chart internals and overlays: `clean_slides/chart_engine/*` -5. **`measure.py`** — Chart cells have zero text width (no text to measure). - Min width comes from the label format string at the configured font size. +The behavior is covered by both targeted regressions and canary tests: -### Phase 3: Rendering - -6. **`renderer.py`** — After placing all text boxes, iterate chart groups. - For each group of merged chart refs: - - Compute bounding box from the cell positions - - Create a python-pptx chart shape (`slide.shapes.add_chart`) - - Configure: hide axes, set gap width, add data labels, apply fill color - - Position and size to the bounding box - -### Phase 4: Integration - -7. **`cli.py`** — No changes needed; `pptx generate` and `pptx validate` - pick up chart cells automatically through the existing pipeline. - -8. **Tests** — Unit tests for parsing, validation, sizing, and rendering. - Integration test generating a full chart-cell slide and verifying shape - count and positions. +- `tests/test_chart_cells.py` +- `tests/test_chart_cells_golden.py` +- `tests/test_chart_engine_smoke.py` +- `tests/test_canary_decks.py` --- -## Out of scope (for now) - -- **Waterfall charts in cells** — waterfall bar positions must align with row - boundaries, requiring coordination between the chart's internal layout and - the table's row heights. Complex; defer to a later phase. - -- **Stacked bars in cells** — each cell currently maps to one bar. Stacked - bars (multiple values per cell) would need a different cell reference syntax. +## Current known limits -- **Chart-only slides** — this design is for charts embedded in tables. The - existing `pptx charts` command handles full-slide charts from JSON. +- **No multi-value cell syntax for stacked segments** — each chart-cell ref + still maps to a single point (`chart-name-index`). +- **Chart-cells are table-embedded only** — full-slide charts should use + `pptx charts` JSON specs. diff --git a/docs/CHARTS.md b/docs/CHARTS.md index 3397de1..086a0ce 100644 --- a/docs/CHARTS.md +++ b/docs/CHARTS.md @@ -1,9 +1,11 @@ -# Charts (JSON) — alpha +# Charts (JSON) -> **Alpha**: the chart generator works but the JSON schema and CLI flags may change. +The `pptx charts` command generates native PowerPoint charts from JSON specs. -The `pptx charts` command generates bar/stacked/waterfall charts from JSON specs using -python-pptx plus optional overlay labels. +Supported chart families: +- clustered bars/columns +- stacked bars/columns +- waterfall (overlay-driven) ## Usage @@ -31,25 +33,45 @@ The chart engine is bundled with `clean-slides`; no external module path is requ "categories": ["A", "B", "C"], "series": [ {"name": "BU1", "values": [1, 2, 3], "color": "#4472C4"}, - {"name": "BU2", "values": [4, 5, 6], "color": "#ED7D31"} + {"name": "BU2", "values": [4, 5, 6], "color": "accent2"} ], "show_data_labels": true, "add_overlay_labels": true } ``` -## Waterfall config +## Bar options + +```json +"bar": { + "orientation": "horizontal", + "chart_template": "templates/chart-style.pptx", + "chart_template_copy": true, + "chart_template_slide": 1, + "chart_template_chart_index": 0 +} +``` + +Notes: +- `orientation` can be `horizontal` or `vertical`. +- `chart_template_copy: true` applies an OPC-level chart XML/relationship replacement, + preserving template internals. +- Template paths are resolved from the spec base directory when relative. + +## Waterfall options ```json "waterfall": { + "orientation": "horizontal", "decrease_categories": ["Costs"], "total_categories": ["Net"], "total_series": ["Totals"], "range_series": ["Range"], "reuse_start_base": true, "label_gap": 25600, - "connector_style": "gap", - "connector_value": "totals", + "connector_style": "gap", // "gap" | "step" + "connector_dash_style": "long_dash", // "solid" | "long_dash" | "dot" + "connector_value": "totals", // "totals" | "tops" "connector_overlap": 6000, "connector_inset": 10000, "total_override": false @@ -57,19 +79,12 @@ The chart engine is bundled with `clean-slides`; no external module path is requ ``` Notes: -- `range_series` marks series that render visually but do **not** affect running totals. -- Set `connector_value: "tops"` for legacy connector anchoring. -- `total_override: true` restores legacy behavior where any series value in a total category overrides - computed totals. - -## Horizontal charts - -```json -"bar": { "orientation": "horizontal" } -"waterfall": { "orientation": "horizontal" } -``` +- `range_series` renders visually but does **not** affect running totals. +- `connector_value: "tops"` restores legacy connector anchoring. +- `total_override: true` restores legacy behavior where explicit values in total + categories override computed totals. -## Multi‑chart decks +## Multi-chart decks Use a top-level `charts` list to generate multiple charts in one deck: diff --git a/docs/INPUT-SCHEMA.md b/docs/INPUT-SCHEMA.md index 0840555..8308582 100644 --- a/docs/INPUT-SCHEMA.md +++ b/docs/INPUT-SCHEMA.md @@ -77,7 +77,7 @@ table: col_headers: # Optional, used if has_col_header=true (body cols only) - "Header 1" - - "Header 2" + - { text: "Header 2", sub: "(units)" } # optional second line, non-bold - "Header 3" - "Header 4" @@ -127,6 +127,7 @@ Paragraph object fields: ```yaml - text: "Paragraph text" + sub: "(optional second line)" # optional subtitle/unit line in default body color lvl: 0 # 0 = no bullet, 1 = bullet, 2 = nested size: 14 # font size in pt color: accent1 # theme name (tx1, accent1, dk2, ...) or hex (#RRGGBB / RRGGBB) @@ -136,6 +137,12 @@ Paragraph object fields: underline: false ``` +`sub` behavior: +- rendered on a new line (line break inside the same paragraph block) +- keeps `lvl`/`font`/`size` +- forced non-bold +- uses default body color (`tx1`) + Example with bullets and overrides: ```yaml @@ -238,7 +245,7 @@ Each entry uses the same format as cell content: plain strings, paragraph object ## Row Groups (Superheaders) -Use `row_groups` instead of `rows` + `row_headers` to create category-grouped tables with bold superheader rows spanning the full width. Each group has a `header` label and a list of `rows` beneath it. +Use `row_groups` instead of `rows` + `row_headers` to create category-grouped tables with bold superheader rows spanning the full width. Each group has a `header` label and a list of `rows` beneath it. `header` can be either a string or an object like `{ text: "Group", sub: "(units)" }`. When using `row_groups`, omit `rows` and `row_headers` — the row count and row-header column are derived from the groups. You still need `cols` (total columns including the superheader column). diff --git a/docs/RELEASE-CHECKLIST.md b/docs/RELEASE-CHECKLIST.md new file mode 100644 index 0000000..c9494d4 --- /dev/null +++ b/docs/RELEASE-CHECKLIST.md @@ -0,0 +1,57 @@ +# Release Checklist + +Use this checklist before cutting a release or tagging a stable snapshot. + +## 1) Local quality gate + +Run from repo root: + +```bash +.venv/bin/ruff check clean_slides tests +.venv/bin/pyright +.venv/bin/pytest -q +.venv/bin/pre-commit run --all-files +``` + +Expected: +- no lint/type errors +- all tests green +- no formatting drift + +## 2) Canary behavior gate + +Run the dedicated canary suite: + +```bash +.venv/bin/pytest -q tests/test_canary_decks.py +``` + +This verifies representative end-to-end behavior: +- mixed YAML deck generation +- waterfall chart-cell generation with connectors +- multi-chart JSON deck generation +- chart template copy roundtrip (`chart_template_copy`) + +## 3) CI gate + +Ensure GitHub Actions `ci` is green: +- `test (3.9)` +- `test (3.12)` +- `canary` +- `lint` + +## 4) Manual openability check + +Generate at least one deck from YAML and one from JSON charts, then open in PowerPoint. + +Confirm: +- no repair dialog +- expected chart counts and labels +- expected connector overlays on waterfall canaries + +## 5) Docs sanity + +Verify docs are consistent with shipped behavior: +- `docs/INPUT-SCHEMA.md` +- `docs/CHART-CELLS.md` +- `docs/CHARTS.md`