dsh-office-kit 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/assets/skills/excel-live-control/SKILL.md +120 -148
  2. package/assets/skills/excel-live-control/container_tools/live_excel.py +9 -0
  3. package/assets/skills/excel-live-control/domain_guidance/financial_models.md +42 -58
  4. package/assets/skills/excel-live-control/domain_guidance/healthcare.md +36 -42
  5. package/assets/skills/excel-live-control/domain_guidance/marketing_advertising.md +33 -34
  6. package/assets/skills/excel-live-control/domain_guidance/scientific_research.md +34 -36
  7. package/assets/skills/excel-live-control/references/office-automation.md +127 -153
  8. package/assets/skills/excel-live-control/references/read_only_qna.md +40 -45
  9. package/assets/skills/excel-live-control/style_guidelines.md +56 -64
  10. package/assets/skills/excel-live-control/workflows/create_workflows.md +40 -47
  11. package/assets/skills/excel-live-control/workflows/edit_workflows.md +37 -45
  12. package/assets/skills/office-documents/SKILL.md +24 -60
  13. package/assets/skills/office-documents/examples/end_to_end_smoke_test.md +5 -5
  14. package/assets/skills/office-documents/ooxml/comments.md +8 -8
  15. package/assets/skills/office-documents/ooxml/hyperlinks_and_fields.md +10 -10
  16. package/assets/skills/office-documents/ooxml/rels_and_content_types.md +7 -7
  17. package/assets/skills/office-documents/ooxml/tracked_changes.md +12 -12
  18. package/assets/skills/office-documents/references/docx-structure.md +11 -13
  19. package/assets/skills/office-documents/references/template-elicitation.md +11 -13
  20. package/assets/skills/office-documents/references/writing-quality.md +20 -20
  21. package/assets/skills/office-documents/tasks/accessibility_a11y.md +16 -22
  22. package/assets/skills/office-documents/tasks/captions_crossrefs.md +23 -26
  23. package/assets/skills/office-documents/tasks/clean_tracked_changes.md +17 -22
  24. package/assets/skills/office-documents/tasks/comments_manage.md +9 -11
  25. package/assets/skills/office-documents/tasks/compare_diff.md +10 -16
  26. package/assets/skills/office-documents/tasks/create_edit.md +14 -16
  27. package/assets/skills/office-documents/tasks/equations.md +17 -28
  28. package/assets/skills/office-documents/tasks/fields_update.md +14 -16
  29. package/assets/skills/office-documents/tasks/fixtures_edge_cases.md +19 -33
  30. package/assets/skills/office-documents/tasks/footnotes_endnotes.md +12 -14
  31. package/assets/skills/office-documents/tasks/forms_content_controls.md +16 -20
  32. package/assets/skills/office-documents/tasks/headings_numbering.md +17 -22
  33. package/assets/skills/office-documents/tasks/images_figures.md +15 -25
  34. package/assets/skills/office-documents/tasks/multi_doc_merge.md +13 -23
  35. package/assets/skills/office-documents/tasks/navigation_internal_links.md +18 -21
  36. package/assets/skills/office-documents/tasks/privacy_scrub_metadata.md +16 -22
  37. package/assets/skills/office-documents/tasks/protection_restrict_editing.md +19 -21
  38. package/assets/skills/office-documents/tasks/read_review.md +9 -20
  39. package/assets/skills/office-documents/tasks/redaction_anonymization.md +17 -18
  40. package/assets/skills/office-documents/tasks/sections_layout.md +16 -23
  41. package/assets/skills/office-documents/tasks/style_lint_normalize.md +18 -22
  42. package/assets/skills/office-documents/tasks/tables_spreadsheets.md +21 -31
  43. package/assets/skills/office-documents/tasks/templates_style_packs.md +17 -21
  44. package/assets/skills/office-documents/tasks/toc_workflow.md +10 -14
  45. package/assets/skills/office-documents/tasks/verify_render.md +11 -23
  46. package/assets/skills/office-documents/tasks/watermarks_background.md +11 -15
  47. package/assets/skills/office-documents/troubleshooting/libreoffice_headless.md +9 -9
  48. package/assets/skills/office-documents/troubleshooting/run_splitting.md +12 -12
  49. package/assets/skills/office-pdf/SKILL.md +36 -51
  50. package/assets/skills/office-pdf/references/form-fields.md +20 -20
  51. package/assets/skills/office-pdf/references/geometry.md +10 -10
  52. package/assets/skills/office-pdf/references/layout-quality.md +13 -13
  53. package/assets/skills/office-pdf/references/limits.md +12 -56
  54. package/assets/skills/office-pdf/tasks/annotations.md +5 -5
  55. package/assets/skills/office-pdf/tasks/create.md +6 -9
  56. package/assets/skills/office-pdf/tasks/edit-pages.md +14 -14
  57. package/assets/skills/office-pdf/tasks/forms.md +15 -15
  58. package/assets/skills/office-pdf/tasks/read.md +9 -9
  59. package/assets/skills/office-pdf/tasks/redact.md +18 -18
  60. package/assets/skills/office-pdf/tasks/stamp.md +10 -10
  61. package/assets/skills/office-presentations/SKILL.md +71 -91
  62. package/assets/skills/office-presentations/container_tools/render_slides.py +1 -1
  63. package/assets/skills/office-presentations/domain_guidance/finance.md +16 -16
  64. package/assets/skills/office-presentations/recipes/README.md +24 -35
  65. package/assets/skills/office-presentations/recipes/autolayout.md +12 -13
  66. package/assets/skills/office-presentations/recipes/connectors.md +11 -12
  67. package/assets/skills/office-presentations/recipes/images.md +11 -12
  68. package/assets/skills/office-presentations/recipes/master-and-layouts.md +18 -18
  69. package/assets/skills/office-presentations/recipes/native-charts.md +8 -8
  70. package/assets/skills/office-presentations/recipes/rich-text-runs.md +13 -13
  71. package/assets/skills/office-presentations/recipes/speaker-notes.md +7 -7
  72. package/assets/skills/office-presentations/recipes/tables.md +14 -14
  73. package/assets/skills/office-presentations/recipes/textbox-at-coordinate.md +9 -9
  74. package/assets/skills/office-presentations/recipes/theme-tokens.md +7 -8
  75. package/assets/skills/office-presentations/references/authoring-workflow.md +38 -43
  76. package/assets/skills/office-presentations/references/finalization.md +10 -12
  77. package/assets/skills/office-presentations/references/native-bullets.md +14 -18
  78. package/assets/skills/office-presentations/references/native-evidence.md +18 -32
  79. package/assets/skills/office-presentations/references/style-guidelines.md +27 -32
  80. package/assets/skills/office-presentations/references/templates.md +31 -60
  81. package/assets/skills/office-presentations/routing/google_slides.md +4 -4
  82. package/assets/skills/office-presentations/tasks/apply-annotations.md +17 -17
  83. package/assets/skills/office-presentations/tasks/charts-tables-notes.md +20 -23
  84. package/assets/skills/office-presentations/tasks/create-deck.md +19 -19
  85. package/assets/skills/office-presentations/tasks/edit-deck.md +14 -16
  86. package/assets/skills/office-presentations/tasks/inspect-and-verify.md +31 -34
  87. package/assets/skills/office-presentations/tasks/template-following.md +23 -28
  88. package/assets/skills/office-spreadsheets/SKILL.md +32 -39
  89. package/assets/skills/office-spreadsheets/recipes/charts.md +8 -26
  90. package/assets/skills/office-spreadsheets/recipes/conditional-formats.md +9 -26
  91. package/assets/skills/office-spreadsheets/recipes/data-tables.md +7 -23
  92. package/assets/skills/office-spreadsheets/recipes/named-ranges.md +8 -21
  93. package/assets/skills/office-spreadsheets/recipes/number-formats.md +7 -23
  94. package/assets/skills/office-spreadsheets/recipes/sparklines.md +7 -20
  95. package/assets/skills/office-spreadsheets/references/charts.md +15 -34
  96. package/assets/skills/office-spreadsheets/references/domain/financial-models.md +10 -25
  97. package/assets/skills/office-spreadsheets/references/domain/healthcare.md +8 -19
  98. package/assets/skills/office-spreadsheets/references/domain/marketing.md +8 -22
  99. package/assets/skills/office-spreadsheets/references/domain/research.md +8 -19
  100. package/assets/skills/office-spreadsheets/references/formula-patterns.md +20 -50
  101. package/assets/skills/office-spreadsheets/references/style-guidelines.md +19 -44
  102. package/assets/skills/office-spreadsheets/references/workbook-structure.md +12 -35
  103. package/assets/skills/office-spreadsheets/routing/google-sheets.md +11 -26
  104. package/assets/skills/office-spreadsheets/tasks/analyze-workbook.md +10 -25
  105. package/assets/skills/office-spreadsheets/tasks/annotations.md +12 -30
  106. package/assets/skills/office-spreadsheets/tasks/create-workbook.md +5 -5
  107. package/assets/skills/office-spreadsheets/tasks/diff-workbooks.md +2 -2
  108. package/assets/skills/office-spreadsheets/tasks/edit-workbook.md +5 -5
  109. package/assets/skills/office-spreadsheets/tasks/inspect-workbook.md +9 -9
  110. package/assets/skills/office-spreadsheets/tasks/recalc-and-check.md +10 -30
  111. package/assets/skills/shared/annotation-recipes.md +98 -120
  112. package/assets/skills/shared/common-workflow.md +70 -80
  113. package/dist/index.js +105 -29
  114. package/package.json +1 -1
  115. package/postinstall.mjs +58 -17
  116. package/python/office_cli.py +70 -7
@@ -1,7 +1,7 @@
1
1
  # Task: inspect and verify a deck
2
2
 
3
- Verification is reading the artefact back and looking at it. The tools below
4
- print one JSON object each; `passed` is the decision.
3
+ Verification is reading the file back and looking at it. Each tool prints one
4
+ JSON object; `passed` is the decision.
5
5
 
6
6
  ## 1. What is in the file
7
7
 
@@ -10,8 +10,7 @@ print one JSON object each; `passed` is the decision.
10
10
  ```
11
11
 
12
12
  Per slide: title, all text, tables as rows, charts with type/title/categories/
13
- series, image count. This is the cheapest sanity check that the content you
14
- meant to write is the content that was written.
13
+ series, image count. Cheapest check that what was written is what was intended.
15
14
 
16
15
  ## 2. Is the package coherent
17
16
 
@@ -19,10 +18,10 @@ meant to write is the content that was written.
19
18
  "<python>" "<skill>/container_tools/inspect_presentation_package_integrity.py" --input deck.pptx
20
19
  ```
21
20
 
22
- Checks required parts, slide/master presence, dangling relationships, XML that
23
- does not parse, slide count drift and duplicate parts, then reports part counts
24
- (`slides`, `layouts`, `masters`, `charts`, `notesSlides`, `media`,
25
- `embeddings`). A chart with an `embeddings` count of zero has lost its data.
21
+ Checks required parts, slide/master presence, dangling relationships, unparseable
22
+ XML, slide-count drift, duplicate parts; reports part counts (`slides`, `layouts`,
23
+ `masters`, `charts`, `notesSlides`, `media`, `embeddings`). Zero `embeddings` on a
24
+ chart means lost data.
26
25
 
27
26
  ## 3. Does the layout hold
28
27
 
@@ -30,13 +29,12 @@ does not parse, slide count drift and duplicate parts, then reports part counts
30
29
  "<python>" "<skill>/container_tools/inspect_presentation_layout_geometry.py" --input deck.pptx --json-out geometry.json
31
30
  ```
32
31
 
33
- `fail` findings (block a delivery): shape outside the slide, degenerate size,
34
- text that cannot fit its frame, table outside the slide, chart or table covered
35
- by another shape. `review` findings (read them, then decide): text overlap,
36
- empty placeholder, title under 28 pt, body under 14 pt, heading ending in a full
37
- stop, connector across text, a chart duplicating a table's labels.
38
-
39
- Add `--strict` to make review findings blocking for a formal deck.
32
+ `fail` blocks delivery: shape outside the slide, degenerate size, text that
33
+ cannot fit its frame, table outside the slide, chart or table covered by another
34
+ shape. `review` needs a decision: text overlap, empty placeholder, title under
35
+ 28 pt, body under 14 pt, heading ending in a full stop, connector across text, a
36
+ chart duplicating a table's labels. `--strict` makes review blocking for a formal
37
+ deck.
40
38
 
41
39
  ## 4. Are the numbers real
42
40
 
@@ -51,11 +49,11 @@ Add `--strict` to make review findings blocking for a formal deck.
51
49
  - Quantitative charts: every declared chart must be a real chart part with a
52
50
  readable series, and in the default `portable` mode must carry its embedded
53
51
  workbook.
54
- - Table arithmetic: only declared totals are enforced, compared against the
55
- displayed rounding; columns whose header is not additive (unit price, ratio,
56
- percent) are skipped rather than summed wrongly.
52
+ - Table arithmetic: only declared totals are enforced, against displayed
53
+ rounding; non-additive columns (unit price, ratio, percent) are skipped rather
54
+ than summed wrongly.
57
55
 
58
- `totals.json` names the contract explicitly:
56
+ `totals.json` names the contract:
59
57
 
60
58
  ```json
61
59
  { "contracts": [{ "slide": 4, "table": 1, "total_row": 6, "columns": [1, 2] }] }
@@ -67,32 +65,31 @@ Add `--strict` to make review findings blocking for a formal deck.
67
65
  "<python>" "<skill>/container_tools/render_slides.py" --input deck.pptx --out-dir preview
68
66
  ```
69
67
 
70
- Writes `slide-NN.png` plus `render.json`. Then `read_image` every PNG — an
71
- unlooked-at render is not verification.
68
+ Writes `slide-NN.png` plus `render.json`; `read_image` every PNG — an unlooked-at
69
+ render is not verification.
72
70
 
73
- To see the whole deck at once:
71
+ Whole deck at once:
74
72
 
75
73
  ```bash
76
74
  "<python>" "<skill>/template_following_scripts/make_contact_sheet.py" --dir preview --out sheet.png --columns 3
77
75
  ```
78
76
 
79
- or `"<skill>/container_tools/create_montage.py` with an explicit file list.
77
+ Or `"<skill>/container_tools/create_montage.py` with an explicit file list.
80
78
 
81
- Look for: clipped text, overlapping blocks, a title that reflowed, a chart whose
82
- point is invisible at thumbnail size, a photograph stretched, a slide with no
83
- title.
79
+ Look for: clipped text, overlapping blocks, a reflowed title, a chart point
80
+ invisible at thumbnail size, a stretched photograph, a slide with no title.
84
81
 
85
82
  ## 6. When nothing can render
86
83
 
87
- With the DSH LibreOffice kit missing (`office_status` → `renderers.kit` /
88
- `renderers.can_render`), `render_slides.py` falls back to `--engine draft`, a
89
- PyMuPDF geometry preview. It shows placement, overflow and overlap — not
90
- typography or theme chrome. Say so when reporting: "verified as a geometry
91
- preview" is honest, "looks great" from an approximate render is not.
84
+ Without the DSH LibreOffice kit (`office_status` → `renderers.available: false`,
85
+ with a `remedy`), `render_slides.py` falls back to `--engine draft`, a
86
+ PyMuPDF geometry preview: placement, overflow, overlap — not typography or theme
87
+ chrome. Say so: "verified as a geometry preview" is honest; "looks great" from an
88
+ approximate render is not.
92
89
 
93
- If even the preview cannot run, the delivery is unverified: report the failing
94
- command, the file's slide count from `pptx read`, and the gate results, and tell
95
- the user which renderer to install for a visual check.
90
+ If even the preview cannot run, report the failing command, the slide count from
91
+ `pptx read`, and the gate results; name the renderer to install for a visual
92
+ check.
96
93
 
97
94
  ## Gate summary
98
95
 
@@ -1,8 +1,7 @@
1
1
  # Task: follow a supplied template
2
2
 
3
3
  Use this when the user hands over a `.pptx` and says "use our template" or "match
4
- this deck". The design is already decided; your job is to put content into its
5
- own slides.
4
+ this deck". The design is decided; put content into its own slides.
6
5
 
7
6
  ## 1. Inspect
8
7
 
@@ -10,16 +9,14 @@ own slides.
10
9
  "<python>" "<skill>/template_following_scripts/inspect_template_deck.py" --template brand.pptx --out-dir template-inspect
11
10
  ```
12
11
 
13
- Outputs:
14
-
15
12
  - `template-inspect.ndjson` — one JSON line per slide: layout name, layout
16
13
  placeholders, slide placeholders (index, kind, rect in inches, text), shape
17
- counts, text excerpt, whether the slide has full-bleed background art.
14
+ counts, text excerpt, full-bleed background art flag.
18
15
  - `template-manifest.json` — slide size, aspect ratio, layout names, theme font
19
- names, and `backgroundArtSlides`.
16
+ names, `backgroundArtSlides`.
20
17
 
21
- Read `backgroundArtSlides` before adding anything: those slides already carry
22
- artwork, so a new opaque panel will cover it.
18
+ Read `backgroundArtSlides` first: those slides already carry artwork, so a new
19
+ opaque panel covers it.
23
20
 
24
21
  ## 2. Plan
25
22
 
@@ -65,13 +62,13 @@ Write `frame-map.json`:
65
62
 
66
63
  Allowed actions: `rewrite`, `rewrite-and-reposition`, `replace`, `delete`,
67
64
  `keep`, `fill-placeholder`. `target.zone` is `[x, y, w, h]` in 0–1 slide
68
- fractions, used with `rewrite-and-reposition`. Elements whose `role` is
69
- `brand`, `logo`, `bumper`, `divider`, `section`, `chrome` or `footer` must stay
70
- `keep` — they are the template.
65
+ fractions, used with `rewrite-and-reposition`. Elements whose `role` is `brand`,
66
+ `logo`, `bumper`, `divider`, `section`, `chrome` or `footer` must stay `keep` —
67
+ they are the template.
71
68
 
72
- Adding new shapes is opt-in: set `newPrimitives.allowed` and write a
73
- justification. The validator rejects shapes added without it, because "one extra
74
- box" is how a branded deck stops matching brand.
69
+ New shapes are opt-in: set `newPrimitives.allowed` and justify it. The validator
70
+ rejects additions without that, because "one extra box" is how a branded deck
71
+ stops matching brand.
75
72
 
76
73
  ## 3. Validate
77
74
 
@@ -79,10 +76,10 @@ box" is how a branded deck stops matching brand.
79
76
  "<python>" "<skill>/template_following_scripts/validate_template_plan.py" --plan frame-map.json
80
77
  ```
81
78
 
82
- Rejects: unknown action, preserved role edited, zone outside 0–1, non-sequential
79
+ Rejects unknown action, edited preserved role, zone outside 0–1, non-sequential
83
80
  slide numbers, missing justification for new primitives, and duplicate source
84
- slides (a warning — it means two output slides share artwork, usually fine for a
85
- repeated section divider, usually a copy-paste mistake otherwise).
81
+ slides (a warning: shared artwork — fine for a repeated divider, usually a
82
+ copy-paste mistake otherwise).
86
83
 
87
84
  ## 4. Build
88
85
 
@@ -90,12 +87,11 @@ repeated section divider, usually a copy-paste mistake otherwise).
90
87
  "<python>" "<skill>/template_following_scripts/prepare_template_starter_deck.py" --template brand.pptx --plan frame-map.json --out starter.pptx
91
88
  ```
92
89
 
93
- The starter deck keeps only the mapped source slides, in plan order, and applies
94
- the actions. It refuses duplicate source slides (it reuses slides rather than
95
- cloning parts). The report lists applied actions and any placeholder left empty.
96
-
97
- From here, extend with `container_tools/add_slides.py` or the recipes when the
98
- template has no family for a slide you need.
90
+ Keeps only the mapped source slides in plan order and applies the actions. It
91
+ refuses duplicate source slides (reuses slides rather than cloning parts). The
92
+ report lists applied actions and any empty placeholder. Extend with
93
+ `container_tools/add_slides.py` or the recipes when the template has no family
94
+ for a slide you need.
99
95
 
100
96
  ## 5. Check fidelity
101
97
 
@@ -118,8 +114,8 @@ template has no family for a slide you need.
118
114
  }
119
115
  ```
120
116
 
121
- Both print `passed`. With no requirements, the fidelity gate is a deliberate
122
- no-op pass — pass a requirements file or you have not checked anything.
117
+ Both print `passed`. No requirements → deliberate no-op pass: pass a file or you
118
+ have checked nothing.
123
119
 
124
120
  ## 6. Look at it
125
121
 
@@ -127,6 +123,5 @@ no-op pass — pass a requirements file or you have not checked anything.
127
123
  "<python>" "<skill>/container_tools/render_slides.py" --input starter.pptx --out-dir starter-preview
128
124
  ```
129
125
 
130
- Compare against the source slides. A filled placeholder that overflows, a title
131
- in the wrong family, or a panel over the corner logo is exactly what this step
132
- exists to catch.
126
+ Compare with the source slides: this catches an overflowed placeholder, a title
127
+ in the wrong family, a panel over the corner logo.
@@ -1,24 +1,24 @@
1
1
  ---
2
- description: Create, inspect, edit, recalculate, and verify Excel workbooks (.xlsx/.csv) using openpyxl recipes and the bundled Office helper CLI.
2
+ description: Create, inspect, edit, recalculate, render, and verify Excel spreadsheets (.xlsx/.csv) with openpyxl recipes, formula patterns, and the bundled Office helper CLI.
3
3
  ---
4
4
 
5
5
  # Spreadsheets (.xlsx)
6
6
 
7
- A workbook is cells: each one holds a value, a format, and possibly a formula *string*. openpyxl writes all three and evaluates none of them, so a formula you just wrote has no cached result and `xlsx read` shows `=SUM(B2:B9)` where the user expects `1,950,400`. The calculation happens in an engine — the DSH LibreOffice via `xlsx recalc` (cross-platform), or whatever app the user opens the file in. Everything below follows from that: write values and formulas, then verify structure by reading back, then verify numbers by recalculating, then verify appearance by rendering.
7
+ A cell holds a value, a format, and possibly a formula string. openpyxl writes all three and evaluates none: a new formula has no cached result, so `xlsx read` shows `=SUM(B2:B9)` where the user expects `1,950,400`. Only an engine calculates — `xlsx recalc` through the DSH LibreOffice kit (cross-platform), or the app the user opens the file in. Work in that order: write values and formulas, read back for structure, recalculate for numbers, render for appearance.
8
8
 
9
- | User job | What is really happening | Fast path |
9
+ | User job | What is happening | Fast path |
10
10
  | --- | --- | --- |
11
- | Build a new workbook | Cells written top-left at A1; `=`-prefixed strings become formulas; styles are per-cell objects; sheets get names, widths, freeze panes, merges | `xlsx create` spec, `tasks/create-workbook.md` |
11
+ | Build a new workbook | Cells written from A1; `=`-prefixed strings become formulas; styles are per-cell; sheets get names, widths, freeze panes, merges | `xlsx create` spec, `tasks/create-workbook.md` |
12
12
  | Change cells in an existing file | Only the cells you name change; a re-save can drop objects openpyxl does not model (pivots, foreign charts) | `container_tools/set_cells.py` and friends, `tasks/edit-workbook.md` |
13
- | The workbook is open in Excel/WPS and the user wants it changed live | The target is the window, not the file on disk: unsaved user edits are in play and a COM write is not transactional | `../excel-live-control/SKILL.md` |
14
- | Inventory a file you did not write | Iterate sheets, used range, formulas vs values, merged ranges, conditional formats, defined names, charts | `container_tools/inspect_workbook.py` |
15
- | Compare two versions | Cell-by-cell walk with formula-aware, format-aware, structure-aware levels | `container_tools/diff_workbooks.py`, `tasks/diff-workbooks.md` |
16
- | Prove the numbers are real | Recalculate in Excel, then check every formula cell has a value, no error, no dangling sheet | `xlsx recalc` + `container_tools/recalc_gate.py`, `tasks/recalc-and-check.md` |
17
- | Format numbers, conditions, names, charts, tables | Number formats, conditional-formatting rules, defined names, native chart objects, Excel Tables | `recipes/*.md` (openpyxl patterns, runnable) |
13
+ | Workbook open in Excel/WPS must change live | The target is the window, not the file: unsaved user edits are in play and a COM write is not transactional. **Windows only** — on macOS/Linux the live tool exits 3 with a remedy; edit the file instead | `../excel-live-control/SKILL.md` |
14
+ | Inventory a file you did not write | Sheets, used range, formulas vs values, merged ranges, conditional formats, defined names, charts | `container_tools/inspect_workbook.py` |
15
+ | Compare two versions | Cell-by-cell walk at formula-, format-, and structure-aware levels | `container_tools/diff_workbooks.py`, `tasks/diff-workbooks.md` |
16
+ | Prove the numbers are real | Recalculate, then check every formula cell has a value, no error, no dangling sheet | `xlsx recalc` + `container_tools/recalc_gate.py`, `tasks/recalc-and-check.md` |
17
+ | Format numbers, conditions, names, charts, tables | Number formats, conditional-formatting rules, defined names, native chart objects, Excel Tables | `recipes/*.md` (runnable openpyxl patterns) |
18
18
  | Answer a question about a file | Read values, not formulas; quote the cells you read | `tasks/analyze-workbook.md` |
19
19
  | Spreadsheet lives in Google Sheets | Not this runtime — see `routing/google-sheets.md` | — |
20
20
 
21
- Sheet and range semantics matter before any edit: `B4:D9` is a rectangle, `'Build'!E14` is a cross-sheet reference whose sheet name needs quotes when it contains spaces, and a merged range is one value plus a lot of emptiness. Number formats are display only — the stored value keeps full precision, so never round data to make it look right.
21
+ Sheet and range semantics come before any edit: `B4:D9` is a rectangle, `'Build'!E14` is a cross-sheet reference whose sheet name needs quotes when it contains spaces, and a merged range is one value plus a lot of emptiness. Number formats are display only — the stored value keeps full precision, so never round data to make it look right.
22
22
 
23
23
  ## Before any file work
24
24
 
@@ -29,54 +29,47 @@ Sheet and range semantics matter before any edit: `B4:D9` is a rectangle, `'Buil
29
29
 
30
30
  ## Delivery
31
31
 
32
- Always hand the finished file to the user with the `present` tool, then summarise in one sentence what changed. A bare path is not delivery: the chat transcript renders `.docx`/`.pptx`/`.xlsx`/`.pdf` links as "二进制文件,无法显示改动" and the user cannot open them.
32
+ Hand the finished file to the user with the `present` tool, then summarise in one sentence what changed. A bare path is not delivery: the chat transcript renders `.docx`/`.pptx`/`.xlsx`/`.pdf` links as "二进制文件,无法显示改动" and the user cannot open them.
33
33
 
34
34
  - Creating a file: write it into the user's workspace, verify it, then `present`.
35
- - Editing an existing file: never overwrite the original. Write `<original>-edited.<ext>` (or a name the user asked for), verify it, `present` it, and state both paths.
35
+ - Editing an existing file: never overwrite the original. Write `<original>-edited.<ext>` (or the name the user asked for), verify it, `present` it, and state both paths.
36
36
 
37
37
  ## Annotations
38
38
 
39
- The user may reply with an `office-annotations` block: `[{ page, text, requirement, region: { left, top, width, height } }]`. `region` is normalised 0–1 against the rendered preview page the user drew on; `requirement` is free-form natural language. Treat it as a change request, not as editor state: locate what the region covers, apply the smallest edit that satisfies `requirement`, re-render, and confirm the region now looks right.
39
+ The user may reply with an `office-annotations` block: `[{ page, text, requirement, region: { left, top, width, height } }]`. `region` is normalised 0–1 against the rendered preview page; `requirement` is free-form natural language. Treat it as a change request, not editor state.
40
40
 
41
- When the payload cannot be mapped to a concrete object (the region is empty or the file has no geometry), do not guess silently — say what you could not locate.
42
-
43
- For xlsx the payload is `{ target, fileName, text: "", requirement, region, imageName, unmapped: true }`: no `page`, no `sheet`, no `range`. The preview client cannot resolve a cell from the drag and refuses to fake one. So:
44
-
45
- 1. `read_image` the attachment named `imageName` — it is a screenshot of the area the user marked. Note visible headers, row labels, and values.
46
- 2. Run `"<python>" "<cli>" xlsx read --input "<file>" --json-out "<dump>.json"` and match those values to sheets and A1 addresses; `container_tools/inspect_workbook.py` adds formulas, merged ranges, and names when the match is ambiguous.
47
- 3. State the resolved sheet and range back to the user before editing ("`Pipeline!D2:D13`, the Amount column"), and ask if two sheets or columns match.
48
- 4. Never convert region pixels into a cell address. Full recipe: `tasks/annotations.md` and `"<shared>/annotation-recipes.md"`.
41
+ For xlsx the payload is `{ target, fileName, text: "", requirement, region, imageName, unmapped: true }`: no `page`, no `sheet`, no `range`, because the preview client cannot resolve a cell from the drag and refuses to fake one. So: `read_image` the attachment named `imageName`, match the visible values to sheets and A1 addresses with `"<python>" "<cli>" xlsx read --input "<file>" --json-out "<dump>.json"`, state the resolved sheet and range before editing ("`Pipeline!D2:D13`, the Amount column"), ask when two sheets or columns match, then apply the smallest edit that satisfies `requirement`, re-render, and confirm the region now looks right. Never convert region pixels into a cell address. When the payload cannot be mapped to a concrete object, do not guess silently — say what you could not locate. Full recipe: `tasks/annotations.md` and `"<shared>/annotation-recipes.md"`.
49
42
 
50
43
  ## Verify
51
44
 
52
45
  Three checks, in this order, after every mutation:
53
46
 
54
- 1. **Structure and values.** `"<python>" "<cli>" xlsx read --input "<file>"` proves the cells you wrote are where you wrote them; `container_tools/inspect_workbook.py` proves charts, tables, conditional formats, names, and merges survived.
55
- 2. **Numbers.** If any formula changed or was added, run `"<python>" "<cli>" xlsx recalc --input "<file>" --out "<file>"` and then `container_tools/recalc_gate.py --input "<file>"`. The kit recalculates on every platform; only when the kit itself is missing does the recalc fail, and `recalc_gate.py` then reports `unverified` — say that plainly instead of presenting an empty cache as a result.
56
- 3. **Appearance.** `"<python>" "<cli>" xlsx render --input "<file>" --out-dir "<render-dir>"`, then `read_image` each PNG. Check titles and labels, `###` columns, clipped numbers, charts cut off by the print area, plots with no categories, legends naming `Series 1`, conditional formatting that never fired, and blank pages. The render goes through the DSH LibreOffice kit; when it is missing the command fails explicitly: report the file as structurally verified but visually unchecked, and offer to rebuild from a spec.
47
+ 1. **Structure and values.** `"<python>" "<cli>" xlsx read --input "<file>"` proves the cells you wrote are where you wrote them. `container_tools/inspect_workbook.py` proves charts, tables, conditional formats, names, and merges survived.
48
+ 2. **Numbers.** If a formula changed or was added, run `"<python>" "<cli>" xlsx recalc --input "<file>" --out "<file>"`, then `container_tools/recalc_gate.py --input "<file>"`. Recalc fails only when the kit is missing, and `recalc_gate.py` then reports `unverified` — say that plainly instead of presenting an empty cache as a result. Flags, exit codes, and what the gate reports: `tasks/recalc-and-check.md`.
49
+ 3. **Appearance.** `"<python>" "<cli>" xlsx render --input "<file>" --out-dir "<render-dir>"`, then `read_image` each PNG. Check titles and labels, `###` columns, clipped numbers, charts cut off by the print area, plots with no categories, legends naming `Series 1`, conditional formatting that never fired, and blank pages. When the LibreOffice kit is missing the command fails explicitly: report the file as structurally verified but visually unchecked, and offer to rebuild from a spec.
57
50
 
58
51
  Two or three render/fix passes are normal. If a defect survives three, say what still looks wrong.
59
52
 
60
53
  ## Mutation tools
61
54
 
62
- The CLI is read-only for `.xlsx`: `create`, `read`, `render`, `status`, `recalc` and nothing else. Every change to an existing workbook goes through these openpyxl tools. Each one reads `--input`, writes `<input>-edited.xlsx` unless you pass `--out`, prints one JSON object on stdout, and exits non-zero with a JSON `error` on stderr when it refuses.
55
+ The CLI is read-only for `.xlsx`: `create`, `read`, `render`, `status`, `recalc`, and nothing else. Every change to an existing workbook goes through these openpyxl tools. Each reads `--input`, writes `<input>-edited.xlsx` unless you pass `--out`, prints one JSON object on stdout, and exits non-zero with a JSON `error` on stderr when it refuses.
63
56
 
64
- | Tool | Does | Does not |
65
- | --- | --- | --- |
66
- | `sheet_ops.py` | `--add NAME`, `--rename OLD=NEW`, `--delete NAME`, `--order "A,B,C"` | rename the formulas that point at a renamed sheet; deleting the last sheet is refused |
67
- | `set_cells.py` | `--sheet S --set B2=42 --set C3="=SUM(C2:C9)"`, or `--spec cells.json` | parse dates for you — write `2026-01-31` and format the cell, or pass a serial |
68
- | `append_rows.py` | `--sheet S --rows '[[...],[...]]'`, or `--rows-file rows.json` | extend an Excel Table, copy the row above's formula, or grow charts and conditional formats |
69
- | `style_cells.py` | `--sheet S --range A1:D1 --style '{"bold":true}'`, or `--spec styles.json` | autofit columns or restyle cells outside the range |
70
- | `add_chart.py` | `--sheet S --type line --data-min-col ... --cat-min-row ...`, or `--spec chart.json` | set axis number formats, series colours, or a legend position; a one-row data range is treated as one series of many points |
71
- | `add_image.py` | `--sheet S --path logo.png --anchor B2 [--width 480]` | update an embedded image when the file on disk changes |
57
+ - `sheet_ops.py` — `--add NAME`, `--rename OLD=NEW`, `--delete NAME`, `--order "A,B,C"`. Renaming a sheet does not update the formulas that point at it; deleting the last sheet is refused.
58
+ - `set_cells.py` — `--sheet S --set B2=42 --set C3="=SUM(C2:C9)"`, or `--spec cells.json`. It does not parse dates for you: write `2026-01-31` and format the cell, or pass a serial.
59
+ - `append_rows.py` — `--sheet S --rows '[[...],[...]]'`, or `--rows-file rows.json`. It does not extend an Excel Table, copy the row above's formula, or grow charts and conditional formats.
60
+ - `style_cells.py` — `--sheet S --range A1:D1 --style '{"bold":true}'`, or `--spec styles.json`. No autofit, and it will not restyle cells outside the range.
61
+ - `add_chart.py` — `--sheet S --type line --data-min-col ... --cat-min-row ...`, or `--spec chart.json`. It cannot set axis number formats, series colours, or a legend position, and a one-row data range is treated as one series of many points.
62
+ - `add_image.py` — `--sheet S --path logo.png --anchor B2 [--width 480]`. It cannot update an embedded image when the file on disk changes.
63
+
64
+ Chain by naming outputs: `set_cells.py --input book.xlsx --out step1.xlsx`, then `style_cells.py --input step1.xlsx --out step2.xlsx`; without `--out` each tool writes `book-edited.xlsx` next to the input and the next step would read the unedited original.
72
65
 
73
- Chain them by naming the output: `set_cells.py --input book.xlsx --out step1.xlsx`, then `style_cells.py --input step1.xlsx --out step2.xlsx`. Without `--out` each tool writes `book-edited.xlsx` next to the input and the next step would read the unedited original.
66
+ Values: numbers, booleans, `null`, and JSON arrays parse; anything else is text, and a leading `=` is stored as a formula. `--set B3=` stores an empty string, which is not a blank cell — leave a cell out of the assignment to keep it blank.
74
67
 
75
- Values: numbers, booleans, `null`, and JSON arrays parse; anything else is text, and a leading `=` is stored as a formula. `--set B3=` stores an empty string, which is not the same as a blank cell — leave a cell out of the assignment to keep it blank.
68
+ Runnable invocations with real paths: `tasks/edit-workbook.md` `## Mutation tools`.
76
69
 
77
70
  ## Formulas you will write
78
71
 
79
- Write the formula text a person would type; openpyxl stores it verbatim and never evaluates it.
72
+ Write the formula text a person would type. openpyxl stores it verbatim and never evaluates it.
80
73
 
81
74
  - Anchoring: `C8` moves when filled, `C$4` pins the row (a period header), `$A8` pins the column (an item label), `$B$3` pins one cell (a rate or case selector). `=SUMIFS(Amount,Month,C$4,Item,$A8)` is the shape a grid needs.
82
75
  - Aggregation: `SUM`, `SUMIFS`, `COUNTIFS`, `AVERAGEIFS`, `SUMPRODUCT` for quantity × rate pairs, `PRODUCT` for chained factors (it silently skips blanks, so a missing input looks valid).
@@ -85,7 +78,7 @@ Write the formula text a person would type; openpyxl stores it verbatim and neve
85
78
  - No `LET`, no dynamic arrays, no `LAMBDA`: the engines that read these files disagree on them.
86
79
  - Cross-sheet references quote the name: `='Build'!E14`, `='Case A'!$B$4`.
87
80
 
88
- After writing formulas, recalculate before anyone reads a number — see `## Verify`.
81
+ Recalculate before anyone reads a number — see `## Verify`. Patterns and dates: `references/formula-patterns.md`.
89
82
 
90
83
  ## Tasks
91
84
 
@@ -107,8 +100,8 @@ Background: `references/workbook-structure.md` (tab roles and layout), `referenc
107
100
 
108
101
  ## Edit discipline
109
102
 
110
- An existing workbook is the reference: its structure, formula style, formats, and terminology are what the user expects to keep seeing. Change the cells the request names and the formulas those cells feed. Everything else — other sheets, pivot sources, shared formulas, named ranges, conditional formats, data validation, protection, hidden rows, freeze panes, chart contents — stays untouched unless the request or a real dependency forces it. Never flatten formulas to values because it is easier, never autofit whole sheets for a visual tweak, and never quietly repair errors the user did not ask about: list them and ask. Details and failure modes: `tasks/edit-workbook.md`.
103
+ An existing workbook is the reference: its structure, formula style, formats, and terminology are what the user expects to keep seeing. Change the cells the request names and the formulas those cells feed. Everything else — other sheets, pivot sources, shared formulas, named ranges, conditional formats, data validation, protection, hidden rows, freeze panes, chart contents — stays untouched unless the request or a real dependency forces it. Never flatten formulas to values because it is easier, never autofit whole sheets for a visual tweak, and never quietly repair errors the user did not ask about: list them and ask. Failure modes: `tasks/edit-workbook.md`.
111
104
 
112
105
  ## Shared workflow
113
106
 
114
- Follow `"<shared>/common-workflow.md"` for the create-or-edit decision, the failure ladder, and the edit naming rule, and `"<shared>/annotation-recipes.md"` for the payload shapes the client sends.
107
+ `"<shared>/common-workflow.md"` owns the create-or-edit decision, the failure ladder, and the edit naming rule. `"<shared>/annotation-recipes.md"` owns the payload shapes the client sends.
@@ -1,8 +1,6 @@
1
1
  # Native charts
2
2
 
3
- A native chart is an object bound to cell ranges: the numbers stay in cells, the
4
- chart reads them, and editing a cell moves the bar. That is the whole reason to
5
- add one instead of pasting an image.
3
+ A native chart is an object bound to cell ranges: the numbers stay in cells, the chart reads them, and editing a cell moves the bar. That is the whole reason to add one instead of pasting an image.
6
4
 
7
5
  ```python
8
6
  import sys
@@ -30,37 +28,21 @@ wb.save(sys.argv[2])
30
28
 
31
29
  ## Orientation is the bug people hit
32
30
 
33
- A one-row source is **one series of many points**, not many series of one point.
34
- `Reference` cannot express that; `add_data(..., from_rows=True)` can. Without it
35
- a monthly row becomes twelve invisible one-point series and the chart renders
36
- blank. The mirror case is a one-column source added with `from_rows=True`: you
37
- get one series per cell row, so only pass it when the data really is row-wise.
31
+ A one-row source is **one series of many points**, not many series of one point. `Reference` cannot express that; `add_data(..., from_rows=True)` can. Without it a monthly row becomes twelve invisible one-point series and the chart renders blank. The mirror case is a one-column source added with `from_rows=True`: you get one series per cell row, so pass it only when the data really is row-wise.
38
32
 
39
33
  ## What the helper CLI gives you
40
34
 
41
- `xlsx create` accepts `charts[]` with `type`, `title`, `anchor`,
42
- `height`/`width`, `data {min_col,min_row,max_col,max_row,titles_from_data}` and
43
- `categories`. `container_tools/add_chart.py` adds the same chart to an existing
44
- file and handles the row-oriented case. Neither sets axis number formats, series
45
- colours, legend position, data labels, or gap width — if the chart needs those,
46
- use a snippet like the one above.
35
+ `xlsx create` accepts `charts[]` with `type`, `title`, `anchor`, `height`/`width`, `data {min_col,min_row,max_col,max_row,titles_from_data}` and `categories`. `container_tools/add_chart.py` adds the same chart to an existing file and handles the row-oriented case. Neither sets axis number formats, series colours, legend position, data labels, or gap width — if the chart needs those, use a snippet like the one above.
47
36
 
48
37
  ## Checks before you present
49
38
 
50
39
  - Series count matches the intent (one line per series, not one per point).
51
40
  - Categories are labels, not values and not an empty axis with `1, 2, 3, …`.
52
- - Bar, column, and area axes start at zero; line and scatter may narrow the
53
- range, but the scale must be visible and stated.
54
- - No default legend on a single series; name each series in `titles_from_data`
55
- or the legend reads `Series 1`.
56
- - Axis number formats are set on the chart, not inherited from cells — a source
57
- column formatted as `0.0%` does not format the axis.
58
- - The chart sits near its data, over empty cells, and is not clipped by the
59
- print area when the sheet is printed.
41
+ - Bar, column, and area axes start at zero; line and scatter may narrow the range, but the scale must be visible and stated.
42
+ - No default legend on a single series; name each series in `titles_from_data` or the legend reads `Series 1`.
43
+ - Axis number formats are set on the chart, not inherited from cells — a source column formatted as `0.0%` does not format the axis.
44
+ - The chart sits near its data, over empty cells, and is not clipped by the print area when the sheet is printed.
60
45
 
61
46
  ## Editing a chart in an existing file
62
47
 
63
- Read the object first (`inspect_workbook.py` lists type, anchor, and series
64
- refs), change the smallest property, and re-render. Moving a chart a few cells is
65
- fine; rebuilding it with different styling when the user asked for one number to
66
- be added is not.
48
+ Read the object first (`inspect_workbook.py` lists type, anchor, and series refs), change the smallest property, and re-render. Moving a chart a few cells is fine; rebuilding it with different styling when the user asked for one number to be added is not.
@@ -1,9 +1,6 @@
1
1
  # Conditional formats
2
2
 
3
- Conditional formatting paints a cell from a rule, not from a value. The rule is
4
- stored separately from the cell format, so a cell can look red while its stored
5
- fill stays default — and a reader who copies the value out gets the number, not
6
- the colour.
3
+ Conditional formatting paints a cell from a rule, not from a value. The rule is stored separately from the cell format, so a cell can look red while its stored fill stays default — and a reader who copies the value out gets the number, not the colour.
7
4
 
8
5
  ```python
9
6
  import sys
@@ -39,31 +36,17 @@ ws.conditional_formatting.add(
39
36
  wb.save(sys.argv[2])
40
37
  ```
41
38
 
42
- - `ColorScaleRule`/`DataBarRule` take a range, not a cell; one rule covers the
43
- rectangle and scales against the values inside it.
44
- - `CellIsRule` compares one cell: `operator` in `lessThan`, `greaterThan`,
45
- `between`, `equal`, `notEqual`, …
46
- - `FormulaRule` evaluates a formula with the range's **top-left** cell, so write
47
- the reference for that cell (`$F2` with row relative) and Excel fills the rest.
48
- - A whole-column range (`D:D`) is cheap to write and slow to render; bound the
49
- range to the current data (`D2:D13`) and extend it when rows are added.
50
- - Rules stack in priority order; later rules win only when the earlier ones do
51
- not match. `stopIfTrue=True` ends the chain — use it when a second rule must
52
- not repaint an already-flagged cell.
53
- - Do not also set a static fill on the same cells: the static fill shows through
54
- where no rule matches and the sheet looks half-formatted.
39
+ - `ColorScaleRule`/`DataBarRule` take a range, not a cell; one rule covers the rectangle and scales against the values inside it.
40
+ - `CellIsRule` compares one cell: `operator` in `lessThan`, `greaterThan`, `between`, `equal`, `notEqual`, …
41
+ - `FormulaRule` evaluates a formula with the range's **top-left** cell, so write the reference for that cell (`$F2` with row relative) and Excel fills the rest.
42
+ - A whole-column range (`D:D`) is cheap to write and slow to render; bound the range to the current data (`D2:D13`) and extend it when rows are added.
43
+ - Rules stack in priority order; later rules win only when the earlier ones do not match. `stopIfTrue=True` ends the chain — use it when a second rule must not repaint an already-flagged cell.
44
+ - Do not also set a static fill on the same cells: the static fill shows through where no rule matches and the sheet looks half-formatted.
55
45
 
56
46
  ## Editing an existing file
57
47
 
58
- Replace a rule, do not add a near-duplicate: iterate
59
- `ws.conditional_formatting` and rebuild the collection for that range. Two rules
60
- covering `D2:D13` with slightly different thresholds give a result nobody can
61
- explain. Keep the rule count per sheet small — a dashboard with forty rules is a
62
- compatibility minefield in other engines.
48
+ Replace a rule; do not add a near-duplicate. Iterate `ws.conditional_formatting` and rebuild the collection for that range — two rules covering `D2:D13` with slightly different thresholds give a result nobody can explain. Keep the rule count per sheet small: a dashboard with forty rules is a compatibility minefield in other engines.
63
49
 
64
50
  ## Verify
65
51
 
66
- Read the file back with `container_tools/inspect_workbook.py`; it lists each
67
- range and rule type. Then render and look: a data bar that never appears is
68
- usually a range that does not overlap the data, and a rule that fires on the
69
- wrong cells is almost always an absolute/relative mistake in `FormulaRule`.
52
+ Read the file back with `container_tools/inspect_workbook.py`; it lists each range and rule type. Then render and look: a data bar that never appears is usually a range that does not overlap the data, and a rule that fires on the wrong cells is almost always an absolute/relative mistake in `FormulaRule`.
@@ -1,10 +1,6 @@
1
1
  # Data tables (Excel Tables)
2
2
 
3
- An Excel Table (`ListObject`) is the object behind `Insert > Table`: a name, a
4
- structured reference, banded rows, an optional totals row, and header filters.
5
- It is not a pivot table and not a formatted range. Use it when the user asks for
6
- a table, when a filter row helps, or when formulas should read
7
- `Sales[Amount]` instead of `$D$2:$D$900`.
3
+ An Excel Table (`ListObject`) is the object behind `Insert > Table`: a name, a structured reference, banded rows, an optional totals row, and header filters. It is not a pivot table and not a formatted range. Use it when the user asks for a table, when a filter row helps, or when formulas should read `Sales[Amount]` instead of `$D$2:$D$900`.
8
4
 
9
5
  ```python
10
6
  import sys
@@ -23,19 +19,14 @@ wb.save(sys.argv[2])
23
19
 
24
20
  Rules that survive contact with the user:
25
21
 
26
- - `displayName` has no spaces, no hyphens, and must be unique in the workbook — it
27
- becomes the structured reference name (`PipelineTable[Amount]`).
28
- - `ref` must start at the header row and match the real data rectangle. A table
29
- whose `ref` is off by one row shows a phantom header and breaks the filter.
22
+ - `displayName` has no spaces, no hyphens, and must be unique in the workbook — it becomes the structured reference name (`PipelineTable[Amount]`).
23
+ - `ref` must start at the header row and match the real data rectangle. A table whose `ref` is off by one row shows a phantom header and breaks the filter.
30
24
  - One table per rectangle, one header row, no merged cells inside it.
31
- - Give it a style from `TableStyleLight*`/`Medium*`/`Dark*`; a bare table with no
32
- style looks like an unstyled range and the user asks why the stripes went away.
25
+ - Give it a style from `TableStyleLight*`/`Medium*`/`Dark*`; a bare table with no style looks like an unstyled range and the user asks why the stripes went away.
33
26
 
34
27
  ## Appending rows to a table
35
28
 
36
- Rows written below the last table row are outside the table until you widen
37
- `ref`. Widen it in the same pass, or the new records will not filter, will not
38
- band, and structured references will not see them.
29
+ Rows written below the last table row are outside the table until you widen `ref`. Widen it in the same pass, or the new records will not filter, will not band, and structured references will not see them.
39
30
 
40
31
  ```python
41
32
  table = ws.tables["PipelineTable"]
@@ -44,15 +35,8 @@ new_last = first_row + added
44
35
  table.ref = f"A1:G{new_last}" # same columns, one row longer
45
36
  ```
46
37
 
47
- Copy the format and any formula from the previous row (`ws.cell(r, c)._style =
48
- ws.cell(r - 1, c)._style`) before widening, so the new row does not appear as a
49
- plain block inside a banded table.
38
+ Copy the format and any formula from the previous row (`ws.cell(r, c)._style = ws.cell(r - 1, c)._style`) before widening, so the new row does not appear as a plain block inside a banded table.
50
39
 
51
40
  ## Totals row
52
41
 
53
- `table.totalsRowCount = 1` declares a totals row, and `totalsRowFunction` on a
54
- column sets `sum`, `count`, `average`, `min`, `max`, `stdDev`, or `var`. The row
55
- below the last data row must exist and hold the header labels Excel expects; a
56
- totals row declared without that row renders oddly in some engines. If the user
57
- only wants a bold total line under a table, a formula row outside the table is
58
- simpler and less fragile.
42
+ `table.totalsRowCount = 1` declares a totals row, and `totalsRowFunction` on a column sets `sum`, `count`, `average`, `min`, `max`, `stdDev`, or `var`. The row below the last data row must exist and hold the header labels Excel expects; a totals row declared without that row renders oddly in some engines. If the user only wants a bold total line under a table, a formula row outside the table is simpler and less fragile.
@@ -1,8 +1,6 @@
1
1
  # Named ranges
2
2
 
3
- A defined name is a saved reference with a readable name: `Q4Growth` instead of
4
- `Model!$B$6`. Formulas that use names survive row and column insertion, and the
5
- user can select the name box instead of hunting for a cell.
3
+ A defined name is a saved reference with a readable name: `Q4Growth` instead of `Model!$B$6`. Formulas that use names survive row and column insertion, and the user can select the name box instead of hunting for a cell.
6
4
 
7
5
  ```python
8
6
  import sys
@@ -15,27 +13,16 @@ wb.defined_names["PlanRevenue"] = DefinedName("PlanRevenue", attr_text="Model!$B
15
13
  wb.save(sys.argv[2])
16
14
  ```
17
15
 
18
- - The name must be a legal identifier: letters, digits, underscore, not a cell
19
- address (`Q4` alone is a column, so it is refused).
20
- - `attr_text` is a full sheet reference; quote sheet names with spaces:
21
- `'Case A'!$B$4`.
22
- - Workbook-level names live in `wb.defined_names`. To scope a name to one sheet,
23
- put it on the worksheet: `wb["Model"].defined_names["Rate"] = ...` — the same
24
- name can then mean different cells on different sheets, which confuses readers
25
- unless the workbook already works that way.
26
- - Read the existing names with `container_tools/inspect_workbook.py` before
27
- adding one; a duplicate name is a repair prompt in Excel.
28
- - Never point a name at a range whose size changes (`A2:A900`) unless the sheet
29
- really ends there; prefer the exact used range, or a table reference.
16
+ - The name must be a legal identifier: letters, digits, underscore, not a cell address (`Q4` alone is a column, so it is refused).
17
+ - `attr_text` is a full sheet reference; quote sheet names with spaces: `'Case A'!$B$4`.
18
+ - Workbook-level names live in `wb.defined_names`. To scope a name to one sheet, put it on the worksheet: `wb["Model"].defined_names["Rate"] = ...` — the same name can then mean different cells on different sheets, which confuses readers unless the workbook already works that way.
19
+ - Read the existing names with `container_tools/inspect_workbook.py` before adding one; a duplicate name is a repair prompt in Excel.
20
+ - Never point a name at a range whose size changes (`A2:A900`) unless the sheet really ends there; prefer the exact used range, or a table reference.
30
21
 
31
22
  ## Using names in formulas
32
23
 
33
- `=PlanRevenue*Q4Growth` is more readable than `=Model!$B$11*Model!$B$6`, and it
34
- does not break when a row is inserted. Paste the formula text exactly as the user
35
- would type it; openpyxl never resolves the name.
24
+ `=PlanRevenue*Q4Growth` is more readable than `=Model!$B$11*Model!$B$6`, and it does not break when a row is inserted. Paste the formula text exactly as the user would type it; openpyxl never resolves the name.
36
25
 
37
26
  ## Removing a name
38
27
 
39
- Deleting a name leaves every formula that used it as `#NAME?`. Find the users
40
- first (formula text is visible in `inspect_workbook.py` output and
41
- `xlsx read`), then delete, then recalculate and check the gate.
28
+ Deleting a name leaves every formula that used it as `#NAME?`. Find the users first (formula text is visible in `inspect_workbook.py` output and `xlsx read`), then delete, then recalculate and check the gate.