-
-
Notifications
You must be signed in to change notification settings - Fork 16
Description
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 nameBuilt-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,numberFormatLocalhorizontalAlignment,verticalAlignment,wrapTextautoIndent,indentLevel,shrinkToFit,textOrientationformulaHidden,lockedincludeAlignment,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 formatincludeFont— font (bold, italic, color, size, etc.)includeBorder— bordersincludePatterns— fill/patternsincludeAlignment— alignment + wrapincludeProtection— 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 + borderLimitation: 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.styleread-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_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 prompt- Office.js types:
node_modules/@types/office-js/index.d.ts— search forclass Style extends,enum BuiltInStyle,StyleCollection