dsh-office-kit 0.4.2 → 0.4.4

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 (123) hide show
  1. package/assets/skills/excel-live-control/SKILL.md +119 -152
  2. package/assets/skills/excel-live-control/domain_guidance/financial_models.md +42 -58
  3. package/assets/skills/excel-live-control/domain_guidance/healthcare.md +36 -42
  4. package/assets/skills/excel-live-control/domain_guidance/marketing_advertising.md +33 -34
  5. package/assets/skills/excel-live-control/domain_guidance/scientific_research.md +34 -36
  6. package/assets/skills/excel-live-control/references/office-automation.md +127 -153
  7. package/assets/skills/excel-live-control/references/read_only_qna.md +40 -45
  8. package/assets/skills/excel-live-control/style_guidelines.md +56 -64
  9. package/assets/skills/excel-live-control/workflows/create_workflows.md +40 -47
  10. package/assets/skills/excel-live-control/workflows/edit_workflows.md +37 -45
  11. package/assets/skills/office-documents/SKILL.md +24 -60
  12. package/assets/skills/office-documents/examples/end_to_end_smoke_test.md +5 -5
  13. package/assets/skills/office-documents/ooxml/comments.md +8 -8
  14. package/assets/skills/office-documents/ooxml/hyperlinks_and_fields.md +10 -10
  15. package/assets/skills/office-documents/ooxml/rels_and_content_types.md +7 -7
  16. package/assets/skills/office-documents/ooxml/tracked_changes.md +12 -12
  17. package/assets/skills/office-documents/references/docx-structure.md +11 -13
  18. package/assets/skills/office-documents/references/template-elicitation.md +11 -13
  19. package/assets/skills/office-documents/references/writing-quality.md +20 -20
  20. package/assets/skills/office-documents/scripts/add_tracked_replacements.py +4 -3
  21. package/assets/skills/office-documents/scripts/docx_kit.py +68 -27
  22. package/assets/skills/office-documents/scripts/heading_audit.py +3 -4
  23. package/assets/skills/office-documents/scripts/internal_nav.py +3 -1
  24. package/assets/skills/office-documents/scripts/merge_docx_append.py +104 -0
  25. package/assets/skills/office-documents/scripts/table_geometry.py +13 -3
  26. package/assets/skills/office-documents/scripts/watermark_add.py +6 -5
  27. package/assets/skills/office-documents/scripts/watermark_audit_remove.py +6 -4
  28. package/assets/skills/office-documents/tasks/accessibility_a11y.md +16 -22
  29. package/assets/skills/office-documents/tasks/captions_crossrefs.md +23 -26
  30. package/assets/skills/office-documents/tasks/clean_tracked_changes.md +17 -22
  31. package/assets/skills/office-documents/tasks/comments_manage.md +9 -11
  32. package/assets/skills/office-documents/tasks/compare_diff.md +10 -16
  33. package/assets/skills/office-documents/tasks/create_edit.md +14 -16
  34. package/assets/skills/office-documents/tasks/equations.md +17 -28
  35. package/assets/skills/office-documents/tasks/fields_update.md +14 -16
  36. package/assets/skills/office-documents/tasks/fixtures_edge_cases.md +19 -33
  37. package/assets/skills/office-documents/tasks/footnotes_endnotes.md +12 -14
  38. package/assets/skills/office-documents/tasks/forms_content_controls.md +16 -20
  39. package/assets/skills/office-documents/tasks/headings_numbering.md +17 -22
  40. package/assets/skills/office-documents/tasks/images_figures.md +15 -25
  41. package/assets/skills/office-documents/tasks/multi_doc_merge.md +13 -23
  42. package/assets/skills/office-documents/tasks/navigation_internal_links.md +18 -21
  43. package/assets/skills/office-documents/tasks/privacy_scrub_metadata.md +16 -22
  44. package/assets/skills/office-documents/tasks/protection_restrict_editing.md +19 -21
  45. package/assets/skills/office-documents/tasks/read_review.md +9 -20
  46. package/assets/skills/office-documents/tasks/redaction_anonymization.md +17 -18
  47. package/assets/skills/office-documents/tasks/sections_layout.md +16 -23
  48. package/assets/skills/office-documents/tasks/style_lint_normalize.md +18 -22
  49. package/assets/skills/office-documents/tasks/tables_spreadsheets.md +21 -31
  50. package/assets/skills/office-documents/tasks/templates_style_packs.md +17 -21
  51. package/assets/skills/office-documents/tasks/toc_workflow.md +10 -14
  52. package/assets/skills/office-documents/tasks/verify_render.md +11 -23
  53. package/assets/skills/office-documents/tasks/watermarks_background.md +10 -14
  54. package/assets/skills/office-documents/troubleshooting/libreoffice_headless.md +6 -6
  55. package/assets/skills/office-documents/troubleshooting/run_splitting.md +12 -12
  56. package/assets/skills/office-pdf/SKILL.md +36 -51
  57. package/assets/skills/office-pdf/references/form-fields.md +20 -20
  58. package/assets/skills/office-pdf/references/geometry.md +10 -10
  59. package/assets/skills/office-pdf/references/layout-quality.md +13 -13
  60. package/assets/skills/office-pdf/references/limits.md +12 -56
  61. package/assets/skills/office-pdf/tasks/annotations.md +5 -5
  62. package/assets/skills/office-pdf/tasks/create.md +6 -9
  63. package/assets/skills/office-pdf/tasks/edit-pages.md +14 -14
  64. package/assets/skills/office-pdf/tasks/forms.md +15 -15
  65. package/assets/skills/office-pdf/tasks/read.md +9 -9
  66. package/assets/skills/office-pdf/tasks/redact.md +18 -18
  67. package/assets/skills/office-pdf/tasks/stamp.md +10 -10
  68. package/assets/skills/office-presentations/SKILL.md +71 -91
  69. package/assets/skills/office-presentations/container_tools/preview_render.py +4 -1
  70. package/assets/skills/office-presentations/container_tools/render_slides.py +1 -1
  71. package/assets/skills/office-presentations/domain_guidance/finance.md +16 -16
  72. package/assets/skills/office-presentations/recipes/README.md +24 -35
  73. package/assets/skills/office-presentations/recipes/autolayout.md +12 -13
  74. package/assets/skills/office-presentations/recipes/connectors.md +11 -12
  75. package/assets/skills/office-presentations/recipes/images.md +11 -12
  76. package/assets/skills/office-presentations/recipes/master-and-layouts.md +18 -18
  77. package/assets/skills/office-presentations/recipes/native-charts.md +8 -8
  78. package/assets/skills/office-presentations/recipes/rich-text-runs.md +13 -13
  79. package/assets/skills/office-presentations/recipes/speaker-notes.md +7 -7
  80. package/assets/skills/office-presentations/recipes/tables.md +14 -14
  81. package/assets/skills/office-presentations/recipes/textbox-at-coordinate.md +9 -9
  82. package/assets/skills/office-presentations/recipes/theme-tokens.md +7 -8
  83. package/assets/skills/office-presentations/references/authoring-workflow.md +38 -43
  84. package/assets/skills/office-presentations/references/finalization.md +10 -12
  85. package/assets/skills/office-presentations/references/native-bullets.md +14 -18
  86. package/assets/skills/office-presentations/references/native-evidence.md +18 -32
  87. package/assets/skills/office-presentations/references/style-guidelines.md +27 -32
  88. package/assets/skills/office-presentations/references/templates.md +31 -60
  89. package/assets/skills/office-presentations/routing/google_slides.md +4 -4
  90. package/assets/skills/office-presentations/tasks/apply-annotations.md +17 -17
  91. package/assets/skills/office-presentations/tasks/charts-tables-notes.md +20 -23
  92. package/assets/skills/office-presentations/tasks/create-deck.md +19 -19
  93. package/assets/skills/office-presentations/tasks/edit-deck.md +14 -16
  94. package/assets/skills/office-presentations/tasks/inspect-and-verify.md +31 -34
  95. package/assets/skills/office-presentations/tasks/template-following.md +23 -28
  96. package/assets/skills/office-spreadsheets/SKILL.md +32 -39
  97. package/assets/skills/office-spreadsheets/recipes/charts.md +8 -26
  98. package/assets/skills/office-spreadsheets/recipes/conditional-formats.md +9 -26
  99. package/assets/skills/office-spreadsheets/recipes/data-tables.md +7 -23
  100. package/assets/skills/office-spreadsheets/recipes/named-ranges.md +8 -21
  101. package/assets/skills/office-spreadsheets/recipes/number-formats.md +7 -23
  102. package/assets/skills/office-spreadsheets/recipes/sparklines.md +7 -20
  103. package/assets/skills/office-spreadsheets/references/charts.md +15 -34
  104. package/assets/skills/office-spreadsheets/references/domain/financial-models.md +10 -25
  105. package/assets/skills/office-spreadsheets/references/domain/healthcare.md +8 -19
  106. package/assets/skills/office-spreadsheets/references/domain/marketing.md +8 -22
  107. package/assets/skills/office-spreadsheets/references/domain/research.md +8 -19
  108. package/assets/skills/office-spreadsheets/references/formula-patterns.md +20 -50
  109. package/assets/skills/office-spreadsheets/references/style-guidelines.md +19 -44
  110. package/assets/skills/office-spreadsheets/references/workbook-structure.md +12 -35
  111. package/assets/skills/office-spreadsheets/routing/google-sheets.md +11 -26
  112. package/assets/skills/office-spreadsheets/tasks/analyze-workbook.md +10 -25
  113. package/assets/skills/office-spreadsheets/tasks/annotations.md +12 -30
  114. package/assets/skills/office-spreadsheets/tasks/create-workbook.md +5 -5
  115. package/assets/skills/office-spreadsheets/tasks/diff-workbooks.md +2 -2
  116. package/assets/skills/office-spreadsheets/tasks/edit-workbook.md +5 -5
  117. package/assets/skills/office-spreadsheets/tasks/inspect-workbook.md +9 -9
  118. package/assets/skills/office-spreadsheets/tasks/recalc-and-check.md +10 -30
  119. package/assets/skills/shared/annotation-recipes.md +98 -120
  120. package/assets/skills/shared/common-workflow.md +70 -80
  121. package/dist/index.js +1 -1
  122. package/package.json +1 -1
  123. package/python/office_cli.py +38 -4
@@ -1,53 +1,52 @@
1
1
  # Marketing and advertising
2
2
 
3
- Campaign, funnel, lead, CRM and ad-performance data. The failures here are almost always
4
- grain, denominator or freshness — not arithmetic.
3
+ Campaign, funnel, lead, CRM and ad-performance data. Failures here are grain, denominator or
4
+ freshness — rarely arithmetic.
5
5
 
6
6
  ## Know what the export is
7
7
 
8
- - **Extract freshness vs analysis time.** An export pulled Tuesday and analysed Friday is
9
- Tuesday's data. Record the extract date and the as-of date once, in the input area, and
10
- do not compare a Friday figure with a Tuesday one as if the difference were performance.
11
- - **Channel setup.** A campaign may be split across an ad platform, a CRM and a billing
12
- system. Name which one each figure comes from before combining them.
13
- - **Grain.** Daily rows are not campaign rows. Impressions are not sessions; clicks are not
8
+ - **Freshness**: an export pulled Tuesday and analysed Friday is Tuesday's data. Record the
9
+ extract date and as-of date in the input area; do not read a Friday-vs-Tuesday difference as
10
+ performance.
11
+ - **Channel setup**: a campaign may span an ad platform, a CRM and a billing system. Name the
12
+ source of each figure before combining them.
13
+ - **Grain**: daily rows are not campaign rows. Impressions are not sessions; clicks are not
14
14
  leads; leads are not qualified leads; a pipeline total is not revenue.
15
- - **Denominators.** CTR uses impressions, CVR uses sessions or clicks depending on the
16
- platform's definition, CPA uses conversions attributed that period. Carry the platform's
17
- definition into the label; two vendors' "conversion rate" differ.
15
+ - **Denominators**: CTR uses impressions; CVR uses sessions or clicks per the platform's
16
+ definition; CPA uses conversions attributed that period. Put the platform's definition in
17
+ the label — vendors' "conversion rate" differ.
18
18
 
19
19
  ## Layout
20
20
 
21
21
  - Name tabs by content and period: `Leads_Q1_2025`, `Pipeline Analysis`, `ROI Dashboard`.
22
- - Keep the raw import intact on its own sheet; clean in derived columns or a prepared
23
- area, and record what you excluded or mapped (non-target region, test account,
24
- renamed source) so the exclusion is visible.
22
+ - Keep the raw import intact on its own sheet; clean in derived columns or a prepared area,
23
+ and record exclusions and mappings (non-target region, test account, renamed source).
25
24
  - One typed time-series date column with one format. Add an ISO helper column only when a
26
- downstream import needs it, and label it as such.
25
+ downstream import needs it, labelled as such.
27
26
  - Targets and traffic lights only when the source supplies a target and a direction. No
28
- invented targets, no green/amber/red on numbers nobody set a goal for.
29
- - Every dashboard figure traces back to a cell in the prepared data. If a number cannot be
30
- traced, it does not go on the dashboard.
27
+ invented targets, no green/amber/red on goals nobody set.
28
+ - Every dashboard figure traces back to a cell in the prepared data; otherwise it does not go
29
+ on the dashboard.
31
30
 
32
31
  ## Metrics
33
32
 
34
- - Define abbreviations where they are used (CPC, CPM, CPA, ROAS, MQL) — a legend on the
35
- analysis sheet, not a glossary tab nobody opens.
36
- - Keep the comparison basis visible: week over week, month over month, or the same period
37
- last year. Mixing bases across one chart is the most common misleading dashboard.
38
- - Attribution: say which model (last click, first click, linear, platform-reported) and
39
- keep it consistent within a table. Platform-reported and analytics-reported conversions
40
- never reconcile exactly; report both rather than forcing a match.
41
- - Charts only when they make a trend, a comparison or a funnel clearer than a table. A
42
- chart of six categories is a table with extra steps.
33
+ - Define abbreviations where used (CPC, CPM, CPA, ROAS, MQL) — a legend on the analysis sheet,
34
+ not a glossary tab nobody opens.
35
+ - Keep the comparison basis visible: week over week, month over month, or same period last
36
+ year. Mixing bases in one chart is the most common misleading dashboard.
37
+ - Attribution: state the model (last click, first click, linear, platform-reported) and keep
38
+ it consistent within a table. Platform- and analytics-reported conversions never reconcile
39
+ exactly; report both.
40
+ - Charts only when they make a trend, comparison or funnel clearer than a table. Six
41
+ categories is a table with extra steps.
43
42
 
44
43
  ## Doing it live
45
44
 
46
- - Read first: `info` (which sheets, which used ranges, whether the user is mid-edit) then
45
+ - Read first: `info` (sheets, used ranges, whether the user is mid-edit), then
47
46
  `read_range --formulas` across the block you will extend.
48
- - Append new periods as rows below the existing table and extend the table/conditional
49
- formatting/chart source with it. Do not create a second table beside the first.
50
- - When a figure must be flagged for refresh (stale extract, missing platform), use the
51
- amber input fill and a note in the cell beside it, not a colour nobody explained.
52
- - Verify with `read_range --formulas`: this is where a misaligned denominator shows up as a
53
- rate that is off by a factor of ten.
47
+ - Append new periods as rows below the table and extend the table, conditional formatting and
48
+ chart source with them. No second table beside the first.
49
+ - For a refresh flag (stale extract, missing platform), use amber input fill plus a note in
50
+ the cell beside it, not an unexplained colour.
51
+ - Verify with `read_range --formulas`; a misaligned denominator surfaces as a rate off by a
52
+ factor of ten.
@@ -1,53 +1,51 @@
1
1
  # Scientific research data
2
2
 
3
- The spreadsheet is often the only record of an experiment. Treat the observations as
4
- irreplaceable: a cleaned value that overwrote a raw measurement is data loss, and a live
5
- edit is one keystroke away from it.
3
+ The spreadsheet is often the only record of an experiment. Treat observations as
4
+ irreplaceable: a cleaned value that overwrote a raw measurement is data loss, and a live edit
5
+ is one keystroke away.
6
6
 
7
7
  ## Keep the raw table raw
8
8
 
9
- - One flat table: one row per observation, one variable per column, one unit per column.
10
- No merged cells, no multi-row headers, no calculation rows mixed into the data block.
11
- - Do not overwrite a measurement. Transformations live in separate columns (clearly headed
12
- `log10(x)`, `normalised`) or in a separate processed block/sheet.
13
- - Record the cleaning: which rows were excluded and why, which values were imputed, which
14
- units were converted. A protocol note on the sheet or a short note beside the block.
15
- - Missing observations stay visibly missing (blank or `n.a.` as the table's convention
16
- dictates) — never silently zero-filled. Randomised and simulated values are labelled as
17
- such; they are not observations.
9
+ - One flat table: one row per observation, one variable per column, one unit per column. No
10
+ merged cells, no multi-row headers, no calculation rows inside the data block.
11
+ - Never overwrite a measurement. Transformations go in separate columns (headed `log10(x)`,
12
+ `normalised`) or a separate processed block or sheet.
13
+ - Record the cleaning: rows excluded and why, values imputed, units converted — in a protocol
14
+ note on or beside the sheet.
15
+ - Missing observations stay visibly missing (blank or `n.a.`, per the table's convention);
16
+ never zero-fill silently. Label randomised and simulated values as such — they are not
17
+ observations.
18
18
 
19
19
  ## Presentation tables are a different object
20
20
 
21
- When the sheet is a report of the data rather than the data itself:
21
+ When the sheet reports the data rather than holding it:
22
22
 
23
- - One type of data per column, units in the header (`Concentration (mg/L)`), never inside
24
- the cell.
23
+ - One type of data per column; units in the header (`Concentration (mg/L)`), never in the cell.
25
24
  - Never encode meaning only in colour or bold — add a labelled value column (`Flag:
26
- excluded`, `Status: control`). A reader with a monochrome printout must still get the
27
- full information.
28
- - Notes belong in a notes column, not in cell comments that a co-author will never see.
29
- - No merged cells in a table you expect anyone to sort or filter.
25
+ excluded`, `Status: control`). A monochrome printout must still carry the meaning.
26
+ - Notes belong in a notes column, not in cell comments a co-author will never see.
27
+ - No merged cells in a table anyone will sort or filter.
30
28
 
31
29
  ## Analysis
32
30
 
33
- - Prefer a transparent formula to a clever one. Someone has to defend this number in a
34
- review six months from now.
35
- - Show the calculation steps: the intermediate columns are the evidence. Hiding them in a
36
- single formula makes the result unfalsifiable.
37
- - A circular reference or iterative calculation is off-limits unless the user asks for it
38
- and documents it. A simulation's random seed and method are part of the result.
39
- - Distinguish measurement, derived value, flag and assumption — by label, not by font
40
- colour alone.
41
- - If the design of the analysis changes (an exclusion added, a normalisation changed), say
42
- so in the reply. A quiet change to a denominator rewrites the conclusion.
31
+ - A transparent formula beats a clever one; someone must defend the number in review six
32
+ months from now.
33
+ - Show the steps: intermediate columns are the evidence; one formula makes the result
34
+ unfalsifiable.
35
+ - Circular references and iterative calculation are off-limits unless the user asks and
36
+ documents it. A simulation's random seed and method are part of the result.
37
+ - Distinguish measurement, derived value, flag and assumption by label, not by font colour
38
+ alone.
39
+ - If the design changes (exclusion added, normalisation changed), say so in the reply. A
40
+ quiet denominator change rewrites the conclusion.
43
41
 
44
42
  ## Doing it live
45
43
 
46
44
  - Before touching anything: `info`, then `read_range --formulas --display` on the block.
47
- Raw data and report blocks are usually on different sheets; confirm which is which.
48
- - Append observations as rows at the bottom of the table, extend any table object to cover
49
- them, and keep the header row exactly as it is.
50
- - Verify with `read_range --formulas`; the `errors` array catches the `#DIV/0!` and
51
- `#VALUE!` that appear when a text value lands in a numeric column.
52
- - Never delete rows from the middle of a data table; a delete shifts every downstream
53
- formula and every chart range with it.
45
+ Raw data and report blocks usually sit on different sheets; confirm which is which.
46
+ - Append observations as bottom rows, extend any table object to cover them, leave the header
47
+ row untouched.
48
+ - Verify with `read_range --formulas`; the `errors` array catches the `#DIV/0!` and `#VALUE!`
49
+ that appear when text lands in a numeric column.
50
+ - Never delete rows from the middle of a data table; a delete shifts every downstream formula
51
+ and chart range.
@@ -1,13 +1,10 @@
1
1
  # The COM object model behind a live edit
2
2
 
3
- What this skill talks to is not a file. It is an out-of-process COM server that happens
4
- to own a window: every property read and every write is a remote call across process
5
- boundaries, and every object you hold is a proxy that re-reads state when you ask it.
6
- That explains most of the rules below.
3
+ The target is an out-of-process COM server that owns a window. Each property read or write
4
+ is a remote call; each object is a proxy that re-reads state. That explains most rules here.
7
5
 
8
6
  `container_tools/live_excel.py` wraps the common cases. Read this when the CLI has no verb
9
- for what the task needs, when a call fails in a way the JSON does not explain, or when you
10
- are extending the tool.
7
+ for the task, a failure is unexplained, or you extend the tool.
11
8
 
12
9
  ## Reaching the running instance
13
10
 
@@ -15,36 +12,33 @@ are extending the tool.
15
12
  $app = [Runtime.InteropServices.Marshal]::GetActiveObject('Excel.Application')
16
13
  ```
17
14
 
18
- - This is the only supported way to find a *running* instance. `New-Object -ComObject
19
- Excel.Application` creates a new, invisible one — never do that for user work.
20
- - It exists in Windows PowerShell 5.1 (`powershell.exe`). PowerShell 7 (`pwsh`) removed
21
- the method, which is why the tool shells out to `powershell.exe` and not `pwsh`.
22
- - With nothing running it raises `0x800401E3 (MK_E_UNAVAILABLE)`. That is the signal to
23
- ask the user to open the workbook, not to launch anything.
24
- - WPS Spreadsheets registers both `Ket.Application` and `Excel.Application`, so a
25
- successful attach to `Excel.Application` does not prove Microsoft Excel is running.
26
- `Application.Name` and `Application.Version` are worse than useless here — WPS reports
27
- `Microsoft Excel` / `12.0`. Infer the product from `Application.Path`.
28
- - The running-object table is per user session and survives the process that created the
29
- instance. A hidden instance launched for a self-test stays attachable from later calls;
30
- it stays invisible until something shows it, and it leaks until `Application.Quit()`.
15
+ - The only supported way to find a running instance. `New-Object -ComObject
16
+ Excel.Application` creates a new invisible one; never use it for user work.
17
+ - Only in Windows PowerShell 5.1 (`powershell.exe`); PowerShell 7 (`pwsh`) removed it,
18
+ which is why the tool shells out to `powershell.exe`.
19
+ - Nothing running raises `0x800401E3 (MK_E_UNAVAILABLE)`. Ask the user to open the
20
+ workbook; launch nothing.
21
+ - WPS registers `Ket.Application` and `Excel.Application`, so attaching does not prove
22
+ Microsoft Excel. `Application.Name`/`Version` are useless here (WPS reports `Microsoft
23
+ Excel` / `12.0`). Infer the product from `Application.Path`.
24
+ - The running-object table is per user session and outlives the creating process. A hidden
25
+ self-test instance stays attachable, invisible until shown, and leaks until
26
+ `Application.Quit()`.
31
27
 
32
28
  ## Addressing
33
29
 
34
30
  `Application` → `Workbooks` → `Workbook` → `Sheets` → `Worksheet` → `Range`.
35
31
 
36
32
  - Collections are **1-based**: `$book.Sheets.Item(1)`. `Item(0)` throws.
37
- - `$sheet.Cells` is the whole sheet; `$sheet.UsedRange` is the rectangle that has content
38
- or formatting. `UsedRange` is not trimmed by deleting a value in its last cell; treat
39
- it as a hint for what to read, never as the boundary of an edit.
40
- - A1 strings: `A1`, `B4:D9`, `$B$14`, `Sheet1!A1:C9`, `'Build Detail'!E14` (quote sheet
41
- names containing spaces). `Range.Address($row_absolute, $column_absolute)` prints one
42
- back, e.g. `$r.Address(0,0)` gives `A1:C3`. Prefer reporting the absolute form the user
43
- can paste into the Name Box.
44
- - Sheet names are case-insensitive in .NET-side matching but must match for COM:
45
- `$book.Sheets.Item('Build')` throws on `build`. Use `Worksheet.Name` for the exact
46
- spelling.
47
- - Multi-area ranges (`A1:A5,C1:C5`) exist; the tool refuses them for writes because a
33
+ - `$sheet.Cells` is the whole sheet; `$sheet.UsedRange` is the rectangle holding content or
34
+ formatting. Clearing the last cell does not shrink it: a read hint, never an edit
35
+ boundary.
36
+ - A1 strings: `A1`, `B4:D9`, `$B$14`, `Sheet1!A1:C9`, `'Build Detail'!E14` (quote names
37
+ with spaces). `$r.Address(0,0)` gives `A1:C3`; report the absolute form the user can
38
+ paste into the Name Box.
39
+ - Sheet-name matching is case-insensitive in .NET but exact for COM:
40
+ `$book.Sheets.Item('Build')` throws on `build`. Use `Worksheet.Name` for spelling.
41
+ - Multi-area ranges (`A1:A5,C1:C5`) exist; the tool refuses them for writes because one
48
42
  rectangular `Value2` assignment cannot fill them.
49
43
 
50
44
  ## Cells: what you write and what comes back
@@ -52,156 +46,136 @@ $app = [Runtime.InteropServices.Marshal]::GetActiveObject('Excel.Application')
52
46
  | Property | Use it for | Reads back |
53
47
  | --- | --- | --- |
54
48
  | `Range.Value2` | every value: numbers, text, dates (as serials), booleans, arrays | typed scalars / `Object[,]` |
55
- | `Range.Formula` | formulas in A1 notation; also the way to read them back | `String`, e.g. `=SUM(B2:B9)` |
49
+ | `Range.Formula` | formulas in A1 notation; also how to read them back | `String`, e.g. `=SUM(B2:B9)` |
56
50
  | `Range.FormulaR1C1` | fills where you think in offsets | `String` |
57
- | `Range.Text` | the *displayed* string (after number format) | per-cell only |
58
-
59
- - **`Value2` is type-preserving and locale-blind.** Assign a `Double` and the cell is a
60
- number; assign a `String` and it is text — except that the application may *parse* a
61
- string that looks like a date, a currency amount, or `=1+1`. Writing `"2026-01-31"`
62
- through `Value2` produced the serial `46053` in a date-formatted cell. If the user meant
63
- the literal text, store the serial (or the number) and set a number format, or prefix
64
- the apostrophe; do not rely on the parse.
65
- - **`$null` empties a cell.** That is the supported way to clear one value inside a block
66
- you are writing.
67
- - A multi-cell read returns a 2-D `Object[,]` with **1-based** indices. A single cell
51
+ | `Range.Text` | the displayed string, after number format | per-cell only |
52
+
53
+ - **`Value2` preserves type and ignores locale.** A `Double` stays a number, a `String`
54
+ stays text — unless the app parses it as a date, currency, or `=1+1`. `"2026-01-31"`
55
+ written through `Value2` produced serial `46053` in a date-formatted cell. For literal
56
+ text, store the serial/number and set a number format, or prefix the apostrophe. Never
57
+ rely on the parse.
58
+ - **`$null` empties a cell**, the supported way to clear one value inside a written block.
59
+ - A multi-cell read returns a 2-D `Object[,]` with **1-based** indices; a single cell
68
60
  returns a scalar, so code that always indexes `[1,1]` fails on one-cell ranges.
69
- - **Errors come back as integers.** A live value of `#DIV/0!` is the Int32 SCODE
70
- `-2146826281`; `#VALUE!` is `-2146826273`, `#REF!` `-2146826265`, `#NAME?`
71
- `-2146826259`, `#NUM!` `-2146826252`, `#N/A` `-2146826246`. In WPS,
72
- `WorksheetFunction.IsError` returns `False` for them and `SpecialCells(16)` throws, so
73
- the practical test is "is this value an Int32?" — then read `Range.Text`,
74
- `Range.Formula` and `Range.Address(0,0)` for that cell alone.
75
- - `Range.Text` is a per-cell property. On a multi-cell range WPS returns `DBNull`, so the
76
- tool reads it cell by cell (that is why `--display` is capped).
77
- - **Assigning one formula to a range fills it with relative adjustment.** Setting
78
- `A1:A3` to `=B1` gives `=B1`, `=B2`, `=B3`. This is the cheap way to fill a column; it
79
- is also the way to accidentally create a formula you did not intend when the target is
80
- more than one cell.
81
- - Dates are serials (`days since 1899-12-30`, the 1900 date system). `DateSerial(y,m,d)`
82
- gives the number; a `NumberFormat` gives the look.
83
- - Formulas are text. There is no calculation on write; the value updates when the
84
- application recalculates, which is why `recalc` exists.
61
+ - **Errors are integers.** `#DIV/0!` is Int32 SCODE `-2146826281`; `#VALUE!` `-2146826273`,
62
+ `#REF!` `-2146826265`, `#NAME?` `-2146826259`, `#NUM!` `-2146826252`, `#N/A`
63
+ `-2146826246`. WPS returns `False` from `WorksheetFunction.IsError` and throws on
64
+ `SpecialCells(16)`, so test "is this an Int32?", then read `Range.Text`,
65
+ `Range.Formula`, `Range.Address(0,0)` for that cell alone.
66
+ - `Range.Text` is per-cell; on a multi-cell range WPS returns `DBNull`, so the tool reads
67
+ cell by cell (hence the `--display` cap).
68
+ - **One formula assigned to a range fills with relative adjustment**: `A1:A3` = `=B1`
69
+ gives `=B1`, `=B2`, `=B3`. Cheap for a column, and how unintended formulas appear on
70
+ multi-cell targets.
71
+ - Dates are serials (`days since 1899-12-30`, 1900 system). `DateSerial(y,m,d)` gives the
72
+ number; `NumberFormat` gives the look.
73
+ - Formulas are text. Nothing calculates on write; values update on recalc, which is why
74
+ `recalc` exists.
85
75
 
86
76
  ## Number formats
87
77
 
88
- `Range.NumberFormat` takes format *codes*, not locales: `#,##0`, `#,##0.00`, `0.0%`,
89
- `"$"#,##0.00`, `mmm-yy`, `d-mmm`, `@`. Use invariant ASCII codes — the dot is always the
90
- decimal point inside the code, whatever the machine's `Application.DecimalSeparator`
91
- says. WPS returns empty strings for `DecimalSeparator`/`ThousandsSeparator`, so do not
92
- read locale from the app and do not build codes from locale data.
93
-
94
- - Format is display only. The stored value keeps full precision; never round data to make
95
- it read right.
96
- - One format per column. Mixing `#,##0` and `#,##0.00` down a single column is the most
97
- common visual defect in a live sheet.
98
- - Percentages carry precision: one decimal for analytical tables, zero for dashboards,
99
- two when the differences are small. The code encodes that: `0.0%`, `0%`, `0.00%`.
100
- - `###` in the window means the column is too narrow for the formatted value, not that
101
- the value is wrong. Widen with `ColumnWidth` (points-ish, default about 8.43) or
102
- `EntireColumn.AutoFit()`, and check the result with `--display`.
78
+ `Range.NumberFormat` takes codes, not locales: `#,##0`, `#,##0.00`, `0.0%`, `"$"#,##0.00`,
79
+ `mmm-yy`, `d-mmm`, `@`. Use invariant ASCII; the dot is always the decimal point inside a
80
+ code, whatever `Application.DecimalSeparator` says. WPS returns empty
81
+ `DecimalSeparator`/`ThousandsSeparator`: never read locale from the app or build codes from
82
+ locale data.
83
+
84
+ - Format is display only; values keep full precision. Never round data to make it read
85
+ right.
86
+ - One format per column; mixing `#,##0` and `#,##0.00` down a column is the commonest
87
+ visual defect.
88
+ - Precision belongs in the code: `0.0%` for analytical tables, `0%` for dashboards,
89
+ `0.00%` when differences are small.
90
+ - `###` means too narrow, not wrong. Widen with `ColumnWidth` (default ~8.43) or
91
+ `EntireColumn.AutoFit()`, then check with `--display`.
103
92
 
104
93
  ## Colour is BGR
105
94
 
106
- `Interior.Color` and `Font.Color` are OLE colour longs written `0xBBGGRR`, while every
107
- spec in this repo is `#RRGGBB`. Convert explicitly:
95
+ `Interior.Color` and `Font.Color` are OLE longs `0xBBGGRR`; specs are `#RRGGBB`:
108
96
 
109
97
  ```
110
98
  ole = red + green * 256 + blue * 65536 # #RRGGBB -> BGR long
111
99
  ```
112
100
 
113
- `#C00000` becomes `192`; a pure blue fill becomes `16711680`. Setting `ColorIndex = -4142`
114
- removes a fill (`Interior.Color` then reads back `16777215`). Theme colours and tints are
115
- not addressable this way: if the workbook's palette matters, use the workbook's own
116
- existing colours rather than inventing one.
101
+ `#C00000` becomes `192`; pure blue fill is `16711680`. `ColorIndex = -4142` removes a fill
102
+ (`Interior.Color` then reads `16777215`). Theme colours and tints are not addressable;
103
+ reuse existing workbook colours.
117
104
 
118
105
  ## Font, borders, alignment, layout
119
106
 
120
107
  - `Range.Font`: `Name`, `Size`, `Bold`, `Italic`, `Color`, `Underline`, `Strikethrough`.
121
- One family across the sheet; the repo default is Helvetica Neue → Helvetica → Arial →
122
- Aptos, whichever the workbook already uses.
123
- - `Range.Borders.Item(index)`: 7 left, 8 top, 9 bottom, 10 right; `LineStyle` (1 continuous),
124
- `Weight` (2 thin), `Color`. `BorderAround(1, 2)` draws a thin outline in one call.
125
- - `HorizontalAlignment` / `VerticalAlignment` take `xlHAlign*` constants (`-4131` left,
126
- `-4108` center, `-4152` right) and `xlVAlign*` (`-4160` top, `-4108` center, `-4104`
127
- bottom). The tool maps the readable names for you.
128
- - `Range.Merge()`, `Range.UnMerge()`, `Range.WrapText`, `Range.IndentLevel`,
129
- `EntireColumn.ColumnWidth`, `Tab.Color`, `Rows.Hidden`.
130
- - `FreezePanes` lives on `Window`, not on the sheet, and did not stick on a hidden
131
- instance in WPS. The tool deliberately has no freeze-pane verb; if the user needs panes
132
- frozen, tell them to do it, or do it visibly in a shown window.
133
- - Whole-sheet `AutoFit` and `Select`/`Activate` are traps: they change the user's view,
134
- can take seconds on a large sheet, and `ScreenUpdating` does not hide the flicker from a
135
- user who is looking at the window.
108
+ One family per sheet: Helvetica Neue → Helvetica → Arial → Aptos, whichever the workbook
109
+ uses.
110
+ - `Range.Borders.Item(index)`: 7 left, 8 top, 9 bottom, 10 right; `LineStyle` (1
111
+ continuous), `Weight` (2 thin), `Color`. `BorderAround(1, 2)` draws a thin outline in one
112
+ call.
113
+ - `HorizontalAlignment` takes `xlHAlign*` (`-4131` left, `-4108` center, `-4152` right);
114
+ `VerticalAlignment` takes `xlVAlign*` (`-4160` top, `-4108` center, `-4104` bottom). The
115
+ tool maps readable names.
116
+ - Also: `Merge()`, `UnMerge()`, `WrapText`, `IndentLevel`, `EntireColumn.ColumnWidth`,
117
+ `Tab.Color`, `Rows.Hidden`.
118
+ - `FreezePanes` lives on `Window` and did not stick on a hidden WPS instance. No freeze
119
+ verb: tell the user, or do it visibly.
120
+ - Whole-sheet `AutoFit` and `Select`/`Activate` change the user's view, can take seconds,
121
+ and `ScreenUpdating` does not hide the flicker.
136
122
 
137
123
  ## Objects beside the cells
138
124
 
139
- - **Tables**: `$sheet.ListObjects.Add(1, $range, [Type]::Missing, $has_headers)` creates
140
- a `ListObject`. PowerShell cannot reproduce VBA's empty argument positions — pass
141
- `[Type]::Missing` for the `Destination` slot. Created tables get localized default
142
- names (`表1`), so name them. With `has_headers = 0` the application *generates a header
143
- row above the data*, which shifts the values down one row and makes the table one row
144
- taller than the range you named. Extend an existing table with `ListObject.Resize`
145
- rather than adding a second one over the same cells.
125
+ - **Tables**: `$sheet.ListObjects.Add(1, $range, [Type]::Missing, $has_headers)`.
126
+ PowerShell cannot reproduce VBA's empty argument positions, so pass `[Type]::Missing` for
127
+ `Destination`. Tables get localized default names (`表1`); name them. `has_headers = 0`
128
+ makes the app generate a header row above the data, shifting values down and making the
129
+ table one row taller than the range. Extend with `ListObject.Resize`, never a second
130
+ table.
146
131
  - **Charts**: `$sheet.ChartObjects().Add(left, top, width, height)` → `.Chart` →
147
- `.SetSourceData($range)` + `.ChartType`. `HasTitle`/`ChartTitle.Text` set the title.
148
- `ChartType` numbers: column 51, stacked column 52, bar 57, stacked bar 58, line 4, line
149
- with markers 65, area 1, stacked area 76, pie 5, doughnut -4120, scatter -4169,
150
- scatter with lines 74. A chart over an empty range renders blank — check the source
151
- cells before blaming the chart.
152
- - **Shapes**: `$sheet.Shapes.AddShape(...)`, `AddTextbox`, `AddPicture`. They float over
153
- cells and are not part of the used range; anchor them to a cell (`TopLeftCell`) so they
154
- move with the data.
155
- - **Pivots**: `PivotTables`, `PivotFields`. Read and report them; never rebuild one
156
- through COM unless the user asked for a new pivot — the field layout is not recoverable
157
- from a failed attempt.
158
- - **Names**: `$workbook.Names`, `Range.Name`. Names break when you delete the cells they
159
- point at (`#REF!` in the name), so a rename or a row delete is a contract change.
160
- - **Conditional formats**: `Range.FormatConditions`. Appending a condition to a range
161
- that already has one edits the *existing* set; a "fix" here can silently change rules
162
- the user depends on. Read first, and keep the range bounded.
163
- - **Validation**: `Range.Validation` — same rule. Read the existing rule before writing a
164
- new one.
132
+ `.SetSourceData($range)` + `.ChartType`; `HasTitle`/`ChartTitle.Text` set the title.
133
+ Types: column 51, stacked column 52, bar 57, stacked bar 58, line 4, line with markers
134
+ 65, area 1, stacked area 76, pie 5, doughnut -4120, scatter -4169, scatter with lines 74.
135
+ A chart over an empty range renders blank; check the source cells.
136
+ - **Shapes**: `Shapes.AddShape(...)`, `AddTextbox`, `AddPicture`. They float over cells, sit
137
+ outside the used range, and need `TopLeftCell` anchoring to move with data.
138
+ - **Pivots**: `PivotTables`, `PivotFields`. Read and report; never rebuild through COM
139
+ unless asked — a failed attempt loses the layout.
140
+ - **Names**: `$workbook.Names`, `Range.Name`. Deleting their cells breaks names (`#REF!`),
141
+ so a rename or row delete is a contract change.
142
+ - **Conditional formats**: `Range.FormatConditions`. Appending to a range with an existing
143
+ condition edits that set; read first, keep the range bounded.
144
+ - **Validation**: `Range.Validation`; read the existing rule before writing.
165
145
 
166
146
  ## Calculation and freshness
167
147
 
168
- - `Application.Calculation`: `-4105` automatic, `-4135` manual. A workbook left in manual
169
- mode shows stale numbers after your edit; `recalc` runs `CalculateFull` across every
170
- open workbook and reports the mode it found.
171
- - `CalculateFull` recalculates dependencies, not just the active sheet. `CalculateFullRebuild`
172
- rebuilds the dependency tree — use it only when a value looks impossible, because it is
173
- slow on a large model.
174
- - Iterative calculation (`Application.Iteration`) is off by default. Circular references
175
- that "worked" for the user are a sign it was on; do not turn it on to make an edit
176
- succeed.
148
+ - `Application.Calculation`: `-4105` automatic, `-4135` manual. Manual mode shows stale
149
+ numbers after an edit; `recalc` runs `CalculateFull` on every open workbook and reports
150
+ the mode.
151
+ - `CalculateFull` recalculates dependencies, not just the active sheet.
152
+ `CalculateFullRebuild` rebuilds the tree; use it only when a value looks impossible — it
153
+ is slow on large models.
154
+ - Iteration (`Application.Iteration`) is off by default. Circular references that "worked"
155
+ mean it was on; do not enable it to make an edit succeed.
177
156
 
178
157
  ## Latency, failures, retries
179
158
 
180
- - Every property access is an RPC. A trivial call (`info`, three reads) took about 1.2–1.5 s
181
- on this host; a call that formats 100 cells one at a time can take tens of seconds.
182
- **Batch**: one `write_range` for a block, one `style_range` for a range, one `set_formula`
183
- for a column. Do not loop calls per cell.
184
- - The default timeout in the tool is 180 s. Raising it is legitimate for a big paste;
185
- lowering it below a second will fail on a busy application.
186
- - Failures you will meet:
187
- - `0x800401E3` — nothing is running. Remediate by asking the user to open the workbook.
188
- - `0x8001010A` (RPC_E_SERVERCALL_RETRYLATER) — the application is busy (a modal dialog,
189
- a recalculation, a user dragging a window). Read again, and if it persists tell the
190
- user there is a dialog blocking the app.
191
- - "Object required" / a stale proxy — usually after the user closed the workbook you
192
- were holding. Re-attach by name; never reuse the old object.
193
- - A method that exists in Excel raising "does not contain a method named …" in WPS — the
194
- WPS object model is a subset. Fall back to a simpler route (formatting via
195
- `Value2`/`NumberFormat` instead of a specialised helper).
196
- - **Nothing is transactional.** A call that fails halfway leaves the cells it already
197
- wrote. The retry protocol is: `read_range` the block, compare with intent, write only
198
- what is missing or wrong. Replaying the same batch is how a live document gets
199
- duplicated rows.
159
+ - Every property access is an RPC. `info` plus three reads took 1.2–1.5 s here; formatting
160
+ 100 cells one at a time can take tens of seconds. **Batch**: one `write_range` per block,
161
+ one `style_range` per range, one `set_formula` per column. Never loop per cell.
162
+ - Default timeout is 180 s. Raising it is fine for a big paste; below 1 s fails on a busy
163
+ app.
164
+ - Failures you meet:
165
+ - `0x800401E3` — nothing running. Ask the user to open the workbook.
166
+ - `0x8001010A` (RPC_E_SERVERCALL_RETRYLATER) — app busy (dialog, recalculation, dragged
167
+ window). Read again; if it persists, tell the user a dialog is blocking it.
168
+ - "Object required" / stale proxy — usually the workbook was closed. Re-attach by name;
169
+ never reuse the old object.
170
+ - An Excel method raising "does not contain a method named …" in WPS: the WPS model is a
171
+ subset. Fall back to a simpler route (formatting via `Value2`/`NumberFormat`).
172
+ - **Nothing is transactional.** A half-failing call leaves written cells. Retry:
173
+ `read_range` the block, compare with intent, write only what is missing or wrong.
174
+ Replaying a batch duplicates rows.
200
175
 
201
176
  ## The one rule that outranks the rest
202
177
 
203
- A live edit mutates a document the user is looking at and may have unsaved changes in.
204
- **Read and diff before writing.** `read_range` the target with `--formulas`; note whether
205
- `info` says the workbook is `saved: false`; then make the smallest write that satisfies the
206
- request. If the diff shows values you did not expect, stop and ask — the user's unsaved
207
- work is more important than finishing the task.
178
+ A live edit mutates a document the user is looking at, possibly with unsaved changes.
179
+ **Read and diff before writing.** `read_range` the target with `--formulas`; note
180
+ `saved: false` from `info`; then make the smallest write that satisfies the request. If the
181
+ diff shows unexpected values, stop and ask — unsaved user work outranks finishing the task.