Skip to content

feat(tables): row_count/column_count/dimensions + sizing round-trip lock — Tables 2.0 Phase 4 (closes epic)#42

Merged
MHoroszowski merged 1 commit into
masterfrom
feature/tables-phase4
May 8, 2026
Merged

feat(tables): row_count/column_count/dimensions + sizing round-trip lock — Tables 2.0 Phase 4 (closes epic)#42
MHoroszowski merged 1 commit into
masterfrom
feature/tables-phase4

Conversation

@MHoroszowski
Copy link
Copy Markdown
Owner

Phase 4 of issue #12 (Tables 2.0): closing PR — sizing & ergonomics

Closes the Tables 2.0 epic. Phase 1 (PR #37) shipped row/column add/remove. Phase 2 (PR #40) shipped the table style API. Phase 3 (PR #41) shipped range-style merge_cells/split_cells with Cell inspection accessors. This PR adds the last two unchecked sub-features on issue #12 and closes the issue entirely.

Public surface added (additive — no existing API changed)

table.row_count       # int — equivalent to len(table.rows), no _RowCollection instantiation
table.column_count    # int — equivalent to len(table.columns), no _ColumnCollection instantiation
table.dimensions      # (row_count, column_count) tuple — symmetrical with TcRange.dimensions

All three are read-only (AttributeError on assignment). They're already reactive: table.rows.add() increments row_count, table.columns.remove(0) decrements column_count, etc.

Round-trip regression-lock for sizing

_Row.height and _Column.width setters already wrote to <a:tr>/@h and <a:gridCol>/@w correctly, and a smoke test confirmed round-trip through save+reload already worked on master. This PR pins that down with five regression tests covering single-row height, single-column width, mixed-height row sets, mixed-width column sets, and count-property preservation through round-trip — so any future change that breaks EMU preservation fails CI immediately.

Out of scope (deferred to follow-ups, not omitted from issue #12)

  • NumPy-style table[r, c] indexing — table.cell(r, c) is canonical; adding __getitem__ would conflict with _RowCollection.__getitem__ on table.rows[i] and confuse the data model.
  • Bulk getter helpers (row_heights / column_widths returning lists) — list-comprehension is one line; no API value-add.
  • Constraint or auto-fit logic — PowerPoint's job at render time.

Tests

  • 22 new pytest cases in tests/test_tables_phase4.py covering each new count property (including reactivity to .rows.add() / .columns.remove() etc.), the read-only setter checks, the round-trip preservation matrix, and Phase-1/2/3 regression checks. Full suite: 3426 passed.
  • 7 new behave scenarios in features/tbl-sizing.feature: row_count read, column_count read, dimensions read, count-after-add, count-after-remove, height round-trip via stream, width round-trip via stream. Full behave: 1036 scenarios passed, 0 failed (baseline 1029 + 7 new).
  • Ruff clean: ruff check src tests → All checks passed; ruff format --check → no diff.

Reporting contract (CLAUDE.md §7)

$ python3 -m pytest tests/ -q | tail -3
........................................................................ [ 98%]
..........................................                               [100%]
3426 passed in 4.79s

$ python3 -m ruff check src tests | tail -3
All checks passed!

$ python3 -m behave features/ --no-color 2>&1 | tail -3
1036 scenarios passed, 0 failed, 0 skipped
3120 steps passed, 0 failed, 0 skipped
Took 0min 1.603s

UAT

  • uat_tables_phase4.py (untracked per CLAUDE.md §6) at repo root.
  • Builds two tables: a 4×4 with explicit graduated row heights (1.31" → 0.55") and monotonically widening column widths (0.98" → 3.39"), and a 3×4 grown into a 4×4 visually-square grid via the new count properties driving the resize loop.
  • Surfaces a real PowerPoint quirk worth documenting: PowerPoint enforces a content-driven minimum row height (~0.4" for default padding + font line height). Row heights below that floor are visually clamped at render time, but the EMU value still round-trips byte-stable in the XML — meaning future XML probes / automation pipelines see your exact value even when PowerPoint paints something taller.
  • Visual UAT signed off by maintainer.

Tables 2.0 epic accounting (all eight sub-features now shipped)

# Sub-feature Phase PR
1 Table.apply_style(name_or_id) Phase 2 #40
2 Table.rows.add() / .remove() Phase 1 #37
3 Table.columns.add() / .remove() Phase 1 #37
4 first_row / banded_rows / last_row etc. bound to a real tableStyleId Phase 2 #40 (toggles already existed; now have public style API)
5 Table.merge_cells / split_cells (idempotent) Phase 3 #41
6 Cell.gridSpan / rowSpan / hMerge / vMerge read-only Phase 3 #41 (snake_case on the Python API)
7 Per-row height / per-column width round-trip Phase 4 this PR (regression-locked)
8 Table.row_count / column_count ergonomics Phase 4 this PR

Closes #12

…ock — Tables 2.0 Phase 4 (closes epic)

Issue: #12 (Phase 4 — closing PR)

Closes the Tables 2.0 epic. Phase 1 (PR #37) shipped row/column add/remove.
Phase 2 (PR #40) shipped the table style API. Phase 3 (PR #41) shipped
range-style merge_cells/split_cells with Cell inspection accessors. This
PR closes the last two unchecked sub-features on issue #12: ergonomic
count properties on Table, and a regression-lock on per-row height /
per-column width round-trip preservation.

Public surface added (additive — no existing API changed):

`Table` (read-only convenience properties)
- `Table.row_count` — number of rows; equivalent to `len(table.rows)` but
  doesn't instantiate the `_RowCollection`.
- `Table.column_count` — number of columns; equivalent to
  `len(table.columns)` but doesn't instantiate the `_ColumnCollection`.
- `Table.dimensions` — `(row_count, column_count)` tuple. Symmetrical
  with `TcRange.dimensions`; rows-first per the dominant 2D-array
  convention.

Round-trip regression-lock for per-row height / per-column width
- `_Row.height` and `_Column.width` setters already wrote to `<a:tr>/@h`
  and `<a:gridCol>/@w` correctly, and a smoke test confirmed round-trip
  through save+reload already worked. This PR pins that down with five
  regression tests covering single-row height, single-column width,
  mixed-height row sets, mixed-width column sets, and count-property
  preservation through round-trip — so any future change that breaks
  EMU preservation fails CI immediately.

Out of scope (deferred to follow-ups, not omitted from issue #12)
- NumPy-style `table[r, c]` indexing — `table.cell(r, c)` is canonical;
  adding `__getitem__` would conflict with `_RowCollection.__getitem__`
  on `table.rows[i]` and confuse the data model.
- Bulk getter helpers (`row_heights` / `column_widths` returning lists) —
  list-comprehension is one line; no API value-add.
- Constraint or auto-fit logic — PowerPoint's job at render time.

Tests
- 22 new pytest cases in `tests/test_tables_phase4.py` covering each new
  count property (including reactivity to `.rows.add()` / `.columns.remove()`
  etc.), the read-only setter checks, the round-trip preservation matrix,
  and Phase-1/2/3 regression checks. Full suite: `3426 passed`.
- 7 new behave scenarios in `features/tbl-sizing.feature`: row_count,
  column_count, dimensions, count-after-add, count-after-remove, height
  round-trip via stream, width round-trip via stream. Full behave:
  `1036 scenarios passed, 0 failed` (baseline 1029 + 7 new).
- Ruff: `ruff check src tests` → All checks passed; `ruff format --check`
  → no diff.

UAT
- `uat_tables_phase4.py` (untracked per CLAUDE.md §6) at repo root.
  Builds two tables — a 4×4 with explicit graduated row heights and
  column widths, and a 3×4 that's grown into a 4×4 by using the count
  properties to drive the loop. All round-trip assertions pass
  programmatically; visual UAT in PowerPoint or Keynote pending
  maintainer signoff.

Issue #12 closes with this merge — all eight sub-features now shipped.

Closes #12
@MHoroszowski MHoroszowski merged commit edf9d56 into master May 8, 2026
12 checks passed
@MHoroszowski MHoroszowski deleted the feature/tables-phase4 branch May 8, 2026 17:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Epic] Tables 2.0 — row/col CRUD, table styles, advanced merge

1 participant