Decide: Native Excel Styles vs Our Own Style System
Summary
We're building a composable cell style system (see .research/conventions-design.md). Excel has a native Style API (ExcelApi 1.7) that overlaps significantly. We need to decide whether to build on top of it, alongside it, or hybrid.
For Phase 1 we're proceeding with Option B (our own system) — but this issue tracks the decision on whether to integrate with native Excel styles longer-term.
Excel's Native Style API — What It Can Do
Reading & applying styles
// Apply a style to a range — single property, single API call
range.style = "Currency";
// Read what style a range has
range.load("style");
// Returns: "Currency", "Total", or custom style name
Built-in styles (BuiltInStyle enum)
Relevant ones that overlap with our planned styles:
| Excel Built-In |
Our Equivalent |
Notes |
Currency |
currency |
Default format: $#,##0.00 (US locale) |
Percent |
percent |
Default format: 0% |
Comma |
number |
Default format: #,##0.00 |
WholeComma |
integer |
Default format: #,##0 |
Total |
total-row |
Bold + top/bottom border |
Input |
input |
Yellow fill + blue border |
Heading1–4 |
header |
Theme-colored headings |
Output |
— |
Light grey fill |
Calculation |
— |
Orange fill |
Also: Good/Bad/Neutral, Accent1–6 (with 20/40/60% variants), SheetTitle, Emphasis1–3, ExplanatoryText, Note, CheckCell, LinkedCell.
Modifying built-in styles
Built-in styles are fully writable via the API. builtIn is a readonly flag that tells you whether a style is built-in, but it doesn't prevent modification:
const currencyStyle = workbook.styles.getItem("Currency");
currencyStyle.numberFormat = '$* #,##0.00_);$*(#,##0.00);$* --_)';
currencyStyle.font.bold = false;
currencyStyle.includeFont = false; // don't touch font properties when applied
currencyStyle.includeBorder = false; // don't touch borders when applied
await context.sync();
Writable properties on Excel.Style:
numberFormat, numberFormatLocal
horizontalAlignment, verticalAlignment, wrapText
autoIndent, indentLevel, shrinkToFit, textOrientation
formulaHidden, locked
includeAlignment, includeBorder, includeFont, includeNumber, includePatterns, includeProtection
- Navigation properties (writable via child objects):
font.* (bold, italic, color, size, name), fill.* (color), borders.getItem(edge).* (style, weight, color)
Readonly: builtIn, name (can't rename).
Creating custom styles
workbook.styles.add("pi-ratio");
const ratioStyle = workbook.styles.getItem("pi-ratio");
ratioStyle.numberFormat = '#,##0.0x_);(#,##0.0x);--_x_)';
ratioStyle.includeFont = false;
ratioStyle.includeBorder = false;
await context.sync();
Composability via include* flags
Each style has boolean flags controlling which property groups it applies:
includeNumber — number format
includeFont — font (bold, italic, color, size, etc.)
includeBorder — borders
includePatterns — fill/patterns
includeAlignment — alignment + wrap
includeProtection — locked/hidden
This enables partial application: a "Currency" style with includeFont: false sets the number format but doesn't touch the font. A "Total" style with includeNumber: false sets bold + border but doesn't touch the number format.
Sequential application works:
range.style = "Currency"; // sets number format only (includeFont=false)
range.style = "Total"; // sets bold + border only (includeNumber=false)
// Result: currency number format + bold + border
Limitation: range.style is a single string — after the above, it reports "Total" (last applied). The metadata that it's also "Currency" is lost. The properties are preserved, but the style association isn't composable.
Style scope
- Styles are per-workbook (not per-user).
- Built-in styles reset when you create a new workbook.
- Custom styles only exist in the workbook where they were created.
Three Options
Option A: Build on native styles
On session start, configure Excel's built-in styles to match our conventions. Create custom styles for ones without built-in equivalents (ratio, blank-section). Apply via range.style = "StyleName".
Pros:
- Cells show named styles in Excel's Cell Styles gallery (Home ribbon)
- User can inspect/tweak styles via native Excel UI (right-click > Modify)
- Single API call to apply (
range.style = "Name")
- Native feel — looks like the user created the styles themselves
Cons:
- Modifying built-in styles is aggressive — changes the user's existing workbook setup
- Per-workbook: need to sync styles on every session start, for every workbook
- Composition: sequential
include* approach works but is fragile; style name metadata is lost
- No parameterisation: can't have "Currency with 0dp" as a single style without creating separate named styles for each variant
Option B: Our own system (current Phase 1 approach)
Conventions module as source of truth. Style resolution produces flat properties. Apply via direct property manipulation (range.format.font.bold = true etc.).
Pros:
- Full control over composition logic (array of style names → merged properties)
- No side-effects on the user's workbook styles
- Per-user persistence via SettingsStore (works across workbooks)
- Clean parameterisation (
currency + dp: 0 → resolved at call time)
Cons:
- Cells don't show a style name in Excel's Cell Styles gallery
- Can't leverage
include* mask semantics
- More API calls per formatting operation (set each property individually vs one style assignment)
Option C: Hybrid — our system + sync to native styles
Our conventions module is the source of truth. On session start, sync our styles to the workbook as custom styles (namespaced, e.g. "pi.currency", "pi.total-row"). Apply via range.style where possible; fall back to direct properties for composed/parameterised cases.
Pros:
- Best of both: clean composition logic + styles visible in Excel UI
- Don't touch built-in styles (use our own
pi.* namespace)
- User can see what the agent applied (Cell Styles gallery)
range.style read-back tells us what was previously applied
Cons:
- More implementation complexity (sync lifecycle, conflict handling)
- Need to handle: what if user deletes a
pi.* style? What if they modify it?
- Custom style namespace could feel foreign to users expecting native style names
- Still per-workbook for the synced styles (our SettingsStore conventions remain per-user)
Recommendation
Option C (hybrid) is likely the best long-term answer, but the conventions module architecture is identical for B and C — the sync layer is additive. So starting with B and adding the sync layer later has no architectural cost.
Questions to answer before choosing
- Do users care about seeing style names in Excel's Cell Styles gallery?
- Is the per-workbook sync lifecycle acceptable? (Every new workbook needs style provisioning.)
- How should we handle conflicts between our styles and user-modified native styles?
- Is the
pi.* namespace acceptable, or should we hijack built-in names?
- Should
read_range report both our style label AND Excel's native style name?
Key files
.research/conventions-design.md — full design doc for the conventions system
src/tools/format-cells.ts — current formatting tool
src/prompt/system-prompt.ts — current conventions in system prompt
- Office.js types:
node_modules/@types/office-js/index.d.ts — search for class Style extends, enum BuiltInStyle, StyleCollection
Decide: Native Excel Styles vs Our Own Style System
Summary
We're building a composable cell style system (see
.research/conventions-design.md). Excel has a native Style API (ExcelApi 1.7) that overlaps significantly. We need to decide whether to build on top of it, alongside it, or hybrid.For Phase 1 we're proceeding with Option B (our own system) — but this issue tracks the decision on whether to integrate with native Excel styles longer-term.
Excel's Native Style API — What It Can Do
Reading & applying styles
Built-in styles (BuiltInStyle enum)
Relevant ones that overlap with our planned styles:
Currencycurrency$#,##0.00(US locale)Percentpercent0%Commanumber#,##0.00WholeCommainteger#,##0Totaltotal-rowInputinputHeading1–4headerOutputCalculationAlso:
Good/Bad/Neutral,Accent1–6(with 20/40/60% variants),SheetTitle,Emphasis1–3,ExplanatoryText,Note,CheckCell,LinkedCell.Modifying built-in styles
Built-in styles are fully writable via the API.
builtInis a readonly flag that tells you whether a style is built-in, but it doesn't prevent modification:Writable properties on
Excel.Style:numberFormat,numberFormatLocalhorizontalAlignment,verticalAlignment,wrapTextautoIndent,indentLevel,shrinkToFit,textOrientationformulaHidden,lockedincludeAlignment,includeBorder,includeFont,includeNumber,includePatterns,includeProtectionfont.*(bold, italic, color, size, name),fill.*(color),borders.getItem(edge).*(style, weight, color)Readonly:
builtIn,name(can't rename).Creating custom styles
Composability via
include*flagsEach style has boolean flags controlling which property groups it applies:
includeNumber— number formatincludeFont— font (bold, italic, color, size, etc.)includeBorder— bordersincludePatterns— fill/patternsincludeAlignment— alignment + wrapincludeProtection— locked/hiddenThis enables partial application: a "Currency" style with
includeFont: falsesets the number format but doesn't touch the font. A "Total" style withincludeNumber: falsesets bold + border but doesn't touch the number format.Sequential application works:
Limitation:
range.styleis a single string — after the above, it reports"Total"(last applied). The metadata that it's also "Currency" is lost. The properties are preserved, but the style association isn't composable.Style scope
Three Options
Option A: Build on native styles
On session start, configure Excel's built-in styles to match our conventions. Create custom styles for ones without built-in equivalents (ratio, blank-section). Apply via
range.style = "StyleName".Pros:
range.style = "Name")Cons:
include*approach works but is fragile; style name metadata is lostOption B: Our own system (current Phase 1 approach)
Conventions module as source of truth. Style resolution produces flat properties. Apply via direct property manipulation (
range.format.font.bold = trueetc.).Pros:
currency+dp: 0→ resolved at call time)Cons:
include*mask semanticsOption C: Hybrid — our system + sync to native styles
Our conventions module is the source of truth. On session start, sync our styles to the workbook as custom styles (namespaced, e.g.
"pi.currency","pi.total-row"). Apply viarange.stylewhere possible; fall back to direct properties for composed/parameterised cases.Pros:
pi.*namespace)range.styleread-back tells us what was previously appliedCons:
pi.*style? What if they modify it?Recommendation
Option C (hybrid) is likely the best long-term answer, but the conventions module architecture is identical for B and C — the sync layer is additive. So starting with B and adding the sync layer later has no architectural cost.
Questions to answer before choosing
pi.*namespace acceptable, or should we hijack built-in names?read_rangereport both our style label AND Excel's native style name?Key files
.research/conventions-design.md— full design doc for the conventions systemsrc/tools/format-cells.ts— current formatting toolsrc/prompt/system-prompt.ts— current conventions in system promptnode_modules/@types/office-js/index.d.ts— search forclass Style extends,enum BuiltInStyle,StyleCollection