elixcee
Run, test, and diagnose Excel VBA macros without requiring Microsoft Excel. elixcee is a Rust-powered headless VBA runtime for Linux, macOS, and Windows, with static checks, property-based workbook testing, and structured diagnostics for VBA and workbook-operation failures.
The core engine is written in Rust; Python bindings are provided via pyo3 + maturin.
Name
elixcee = Excel + elixir + C
An elixir that cures your Excel dependency — running at C-level speed via Rust.
Comparison with similar tools
| Feature | elixcee | xlwings | LibreOffice UNO | openpyxl | xlcalculator |
|---|---|---|---|---|---|
| Runs VBA macros | Yes | Yes | Yes (subset) | No | No |
| Requires Excel | No | Yes | No | No | No |
| Requires LibreOffice | No | No | Yes | No | No |
| Evaluates formulas | Yes | Yes | Yes | No | Yes |
| macOS/Linux/Windows | Yes | partial | Yes | Yes | Yes |
| Simple Python API | Yes | Yes | No | Yes | Yes |
| Read .xlsx | Yes | Yes | Yes | Yes | Yes |
| Read .ods | Yes | Yes | Yes | No | No |
| Write .xlsx | Yes | Yes | Yes | Yes | No |
| Write .ods | Yes | Yes | Yes | No | No |
| Execution speed | Rust (native) | COM/IPC (slow) | IPC (slow) | — | Python |
Notes:
- xlwings requires Excel for Mac on macOS (via AppleScript) and Excel on Windows (via COM). Linux support requires a running Excel instance or a cloud bridge.
- LibreOffice UNO has a slow startup (≥ 1 s process launch) and a complex API. It runs VBA via LibreOffice's own interpreter, which may not match Excel's behavior exactly.
- openpyxl reads cached formula values from .xlsx files but does not re-evaluate formulas at runtime.
- xlcalculator re-evaluates Excel formulas in Python but has no VBA support.
- elixcee's VBA interpreter covers the subset of VBA used in typical data-processing macros (loops, conditionals, cell read/write, string/math functions, multi-sheet access). Most Excel UI operations such as charting and formatting are unsupported or no-ops.
MsgBoxis handled specially: depending on the mode, it's printed to stdout, collected in JSON output, or raised as an error.
Installation
Development build (from source):
&&
CLI (Windows / Linux / macOS)
Pre-built binaries are available on the Releases page — no Python required.
| Download | Platform |
|---|---|
| elixcee-x86_64-windows.exe | Windows x64 |
| elixcee-x86_64-linux | Linux x64 |
| elixcee-aarch64-macos | macOS Apple Silicon |
Usage
elixcee <vba_file>... <MacroName> [OPTIONS]
Arguments:
<vba_file>... One or more VBA source files (.vbs / .bas / .txt). With
more than one, use Module.Sub to disambiguate same-named
Subs/Functions across modules.
<MacroName> Name of the Sub to execute (last argument)
Options:
--file <path> Load cell data from spreadsheet (.xlsx / .xlsm / .ods)
--sheet <name> Active sheet name (default: first sheet in --file)
--output <path> Save result cells to spreadsheet (.xlsx / .ods)
--json Emit a single JSON object (result or error) instead of plain text
Examples
Run a VBA file and print results to stdout:
elixcee macro.vbs ProcessData
Load data from an Excel file, run a macro, and save the output:
elixcee macro.vbs ProcessData --file input.xlsx --output result.xlsx
Output format — one line per non-empty cell, tab-separated address and value:
A1 Hello
B1 42
A2 3.14
MsgBox calls are printed to stdout.
Multiple files (multi-module projects)
Pass more than one source file to run a project spanning several modules.
Sub/Function names are shared project-wide — use Module.Sub to pick a
specific one if the bare name exists in more than one module (module names
come from Attribute VB_Name if present, else the filename):
elixcee Helpers.bas Main.bas Main.ProcessData
There's no project manifest yet (see docs/agent-contract.md for exactly what is/isn't supported, including how cross-module name collisions are handled).
JSON output (for scripts / AI agents)
Add --json for a single machine-readable JSON object instead of plain text:
elixcee macro.vbs ProcessData --json
Full contract — error codes, exit codes, messages semantics: docs/agent-contract.md.
Static analysis without running the macro
elixcee check inspects one or more .bas files without executing them: parse errors, whether the entrypoint macro exists, undefined Sub/Function calls anywhere in the body, and interactive MsgBox calls. Every positional argument is a file; the entrypoint (if any) is always --entry, never positional — so elixcee check *.bas checks every module in a project without asserting any particular entrypoint.
elixcee check macro.vbs --entry ProcessData --json
Workbook snapshot
elixcee snapshot reads a .xlsx/.xlsm/.ods file directly — no VBA
execution — and prints every sheet's non-empty cells as Markdown by default,
or JSON with --json:
elixcee snapshot Book1.xlsx --json
stable_id is derived from the file's own sheetId when available (else a
positional fallback) — it is not VBA's CodeName property. See
docs/agent-contract.md for the full rationale.
Property-based workbook testing
elixcee test-workbook reruns a macro against a starting workbook many
times with generated boundary-value inputs (blank, 0, 1, -1, near
overflow, empty/short/long strings), checking every run for panics,
runtime errors, timeouts, and Excel error values — each case starts from a
completely fresh workbook state:
# fixture.toml
= "order calculation"
= "orders.xlsx"
= ["Main.bas"]
= "Main.Process"
= 100
= 42
[[]]
= "Input!B2:B10"
= "boundary_numeric"
[[]]
= "Result!A1:F100"
= "no_excel_errors"
elixcee test-workbook fixture.toml --json
A failing case reports its seed and case index so it can be reproduced
exactly: elixcee test-workbook fixture.toml --seed 42 --case 17. Full
schema, strategies, and assertion rules: docs/agent-contract.md.
Excel operation diagnostics
elixcee diagnose runs a macro once and explains why Excel would reject
it — a missing worksheet, a missing workbook, an out-of-bounds array
index, a Copy/Paste shape mismatch, a write to a protected sheet, or a
Copy/Paste that conflicts with a merged-cell layout — with evidence,
instead of a bare error string:
elixcee diagnose Main.bas --file report.xlsx --json Main.Run
Range("A1:C10").Copy followed by Range("E1:F10").PasteSpecial reports
both the shape mismatch and where each statement is:
Writing to a .Protected sheet reports which sheet and how to fix it:
Pasting A1:C10 into E1:G10 when the destination's first row is merged
(E1:G1) but the source's isn't reports the layout conflict and both
locations:
Full classification rules and JSON schema: docs/agent-contract.md.
Diagnosing across generated inputs
elixcee diagnose-workbook combines the two features above: it reruns a
macro across test-workbook's generated cases and classifies whichever
failures it finds, instead of only reporting a bare error string. It's most
useful for input-dependent failures like array-bounds errors, where only
some drawn values trigger the bug — a single diagnose call already finds
structural issues (shape mismatches, merged-cell conflicts, sheet
protection) in one shot, since those don't depend on the input at all:
elixcee diagnose-workbook fixture.toml --json
Same fixture format and --seed/--case replay as test-workbook, plus
--cases N to override the fixture's own case count for one run. Full
schema: docs/agent-contract.md.
Multi-area ranges
Range("A1:A10,C1:C10") — a disjoint, multi-area range — is now
recognized by .Copy, but pasting it is diagnose-only: diagnose/
diagnose-workbook classify why, instead of silently doing nothing:
Union(), Areas, Dim rng As Range/Set object variables, and matching-shape
multi-area paste are now implemented — see "VBA object model" below. The 4 classified
codes above still apply to every multi-area shape that doesn't match exactly (different
area counts or shapes, or either side single-area): full scope in
docs/agent-contract.md.
Hidden row/column evidence
diagnose/diagnose-workbook now report when a .Copy'd range overlaps
hidden rows/columns (read from real XLSX hidden="1" metadata) — not an
error, just a new observations field, present alongside (or instead of)
root_causes:
This is what SpecialCells(xlCellTypeVisible) (below) builds on — plain Copy/Paste
itself is unaffected (hidden cells still copy/paste exactly as before). XLSX only; ODS
is deferred. Full scope: docs/agent-contract.md.
VBA object model
Dim rng As Range
Set rng = Range("A1:B2")
rng.Value = 5 ' real Set reference semantics — an alias, not a copy
Dim u As Range
Set u = Union(Range("A1"), Range("D1"))
Range("C1").Value = u.Areas.Count ' 2
Dim ws As Worksheet
Set ws = ActiveSheet
ws.Range("A1").Value = 1
Range("F1").Value = 7 Mod 3 ' 1
Range("F2").Value = 2 ^ 3 ' 8
Range("F3").Value = 7 \ 3 ' 2 (integer division)
If Not (a And b) Then MsgBox "ok"
With Cells(r, c) ' any target expression, evaluated once
.Value = 5
If .Value > 0 Then .Value = .Value + 1 ' .member works at any nesting depth
End With
Set rng = Range("A1"): Set rng2 = rng: Set rng = Nothing
rng2.Value = 1 ' aliases survive Set ... = Nothing on the original
rng.Value = 2 ' raises "Object variable or With block variable not set"
Dim n
n = Null
If IsNull(n + 5) Then MsgBox "Null propagates through +" ' True
Function DoubleIt(x As Integer) As Integer
DoubleIt = x * 2
End Function
Set-assigned Range/Worksheet/Workbook object variables with real reference
semantics — including a genuine unset/Nothing state (member access through a never-Set
or explicitly-Nothing variable raises real VBA's "Object variable or With block variable
not set"; Set x = Nothing clears only x, not any alias made from it earlier) —
Union/Areas, SpecialCells(xlCellTypeVisible) (built on the hidden row/column evidence
above), matching-shape multi-area Copy/Paste, Mod/\/^, infix And/Or/Xor/Not
(real bitwise semantics on non-Boolean operands), a runtime With stack (any target
expression — including a computed one like With Cells(r, c) — evaluated once, with
.member resolving correctly at any nesting depth inside If/For/Do/Select Case),
Variant's Null (documented VBA propagation through +/&/comparisons, distinct from
Empty), the : multi-statement-per-line separator, typed Function parameters/return
types, comma-separated multi-declarator Dim (Dim a As Integer, b As Range), and
single-line If cond Then stmt [Else stmt] are all supported.
Known gaps: multi-area Paste only executes when both sides are multi-area with
matching Areas.Count and per-area shapes — every other combination stays diagnose-only
(see above).
XLSX.read()/write() — @elixcee/xlsx (npm, prepared but not yet published)
A synchronous, WebAssembly-backed XLSX.read(bytes) — no await init() required — is
implemented in the @elixcee/xlsx npm package (see
docs/xlsx-architecture.md for the compatibility initiative
and the sync-bridge design), along with readFile()/readFileSync() (Node-only; the
browser entry point throws rather than faking a filesystem). They return sheet names,
!ref, !merges, !rows/!cols (hidden rows/columns), and per-cell {t, v, f, w, z} —
values, formula text, formatted display strings, and date-typed cells, resolved via real
styles.xml/number-format parsing. Differential-tested against the real xlsx@0.18.5
package: 33/33 MATCH, 0 disclosed (the src/reader.rs xml:space="preserve" trimming
defect noted in earlier rounds is fixed; see CHANGELOG.md). Works in Node (CJS/ESM) and the
browser: a "browser" export condition routes to the inlined-bytes/initSync WASM
artifact, verified not just by Node simulating that export condition but by an actual
headless Chrome process loading a real bundle and reading XLSX.read()'s result back out
of the page's own DOM (no Safari claim). The browser entry point still assumes bundled
consumption — its shared code has a CJS require('ssf'), so it's not literal no-build
<script type="module"> usage — but a real packed-npm-tarball install (not a relative
import into this repo) and CJS/ESM bundling both round-trip cleanly with no manual asset
copy step required anymore.
XLSX.write(wb, opts)/writeFile()/writeFileSync() — pure JS/XML/ZIP generation, no
Rust writer needed — are implemented too (bookType: "xlsx" only), differential-tested
both directions against the real oracle: 36 MATCH + 1 disclosed (bookType: "ods", not
implemented). package.json's description was updated to match, but its version
(0.0.0-development), private (true), and publishConfig (unset) were deliberately
left untouched — no npm publish has actually run, and @elixcee scope ownership on
npm is unconfirmed from this environment either way (see ROADMAP.md's "Known gaps").
Build from source
# binary: target/release/elixcee (or elixcee.exe on Windows)
Quick Start
# Run a VBA macro and get all resulting cells
=
# cells == {(1,1): 1, (2,1): 4, (3,1): 9, (4,1): 16, (5,1): 25}
# Pre-populate cells from Python, then run a macro
=
# 300
# {"total": 300}
# Load cell data from an existing Excel file, then run a macro
=
= # {(row, col): value, ...}
# Store a worksheet formula on a cell and evaluate it
# sum of rows 1-3 in column A
# Bulk range/row access -- no per-cell round trips needed
=
=
# Sheet management and row/column edits
# move to the first tab
# shift everything down by one row
# e.g. ["B1:C1"]
# Column iteration, sorting, and merge create/remove
= # column-major, values only
# Hide/unhide rows and columns
# e.g. [5]
# Copy a sheet
# Read workbook-level defined names
# e.g. {"MyRange": "Sheet1!$A$1:$A$3"}
# Read a sheet's whole-tab visibility
# "visible", "hidden", or "veryHidden"
# Read a row's height or a column's width, if explicitly set
# e.g. 30.5, or None
# e.g. 12.5, or None
# Control MsgBox behavior
= # silently ignore MsgBox calls (default)
= # raise RuntimeError on MsgBox
Python API
| Method | Description |
|---|---|
Vm(on_msgbox="skip") |
Create a new VM. on_msgbox="error" raises RuntimeError on MsgBox. |
vm.run(vba_code, macro_name) |
Parse and execute the named Sub. |
vm.set_cell(row, col, value) |
Write a value into a cell (1-based). |
vm.get_cell(row, col) |
Read a cell value. Returns None for empty cells. |
vm.cells() |
All non-empty cells as {(row, col): value}. |
vm.variables() |
All VBA variables as {name: value}. |
vm.set_cell_formula(row, col, formula) |
Store a formula (e.g. "=SUM(A1:A3)") and evaluate it. |
vm.set_cell_formula_batch(formulas) |
Set multiple formulas at once: {(row, col): formula_str}. |
vm.recalculate() |
Re-evaluate all formula cells (useful after manual cell writes). |
vm.set_sheet(name) |
Switch the active sheet (creates it if absent). |
vm.active_sheet() |
Name of the currently active sheet. |
vm.sheet_names() |
List of all sheet names. |
vm.get_sheet(name) |
Cells of a named sheet as {(row, col): value}. |
vm.get_range(addr, sheet=None) |
Read a rectangular range (e.g. "A1:C5") as a nested list. |
vm.set_range(addr, values, sheet=None) |
Write a rectangular range from a nested list. |
vm.append_row(values, sheet=None) |
Write one row just past the sheet's used range; returns the row number. |
vm.iter_rows(min_row=1, max_row=None, min_col=1, max_col=None, sheet=None) |
Values-only iteration over a rectangular region. |
vm.iter_cols(min_row=1, max_row=None, min_col=1, max_col=None, sheet=None) |
Column-major values-only iteration -- the transposed sibling of iter_rows. |
vm.max_row(sheet=None) / vm.max_column(sheet=None) |
Highest used row/column, or None if the sheet is empty. |
vm.calculate_dimension(sheet=None) |
Used range as an A1-style string (e.g. "B2:D10"), or None if empty. |
vm.sort_range(addr, key_col, descending=False, header=False, sheet=None) |
Sort a rectangular range in place by one column. |
vm.rename_sheet(old_name, new_name) |
Rename a sheet. |
vm.move_sheet(name, new_index) |
Move a sheet to an absolute 0-based tab position. |
vm.copy_sheet(source_name, new_name) |
Duplicate a sheet (cells, merges, hidden state, styles) into a new one. |
vm.defined_names() |
Workbook-level defined names as {name: raw_formula_text}. Read-only. |
vm.sheet_state(name) |
A sheet's whole-tab visibility: "visible", "hidden", or "veryHidden". Read-only. |
vm.row_height(row, sheet=None) / vm.column_width(col, sheet=None) |
A row's height in points / column's width in characters, or None if never explicitly set. Read-only. |
vm.insert_rows(idx, amount=1, sheet=None) / vm.delete_rows(...) |
Insert/delete rows (values only -- doesn't shift merges/styles). |
vm.insert_cols(idx, amount=1, sheet=None) / vm.delete_cols(...) |
Insert/delete columns (same caveat as rows). |
vm.merged_cells(sheet=None) |
List a sheet's merged ranges as A1 strings, e.g. ["B1:C1"]. |
vm.merge_cells(addr, sheet=None) / vm.unmerge_cells(addr, sheet=None) |
Create/remove a merge. |
vm.hidden_rows(sheet=None) / vm.hidden_columns(sheet=None) |
Sorted list of hidden row/column numbers. |
vm.set_row_hidden(row, hidden=True, sheet=None) / vm.set_column_hidden(col, ...) |
Hide or unhide a single row/column. |
vm.save_workbook(path) |
Save all sheets to .xlsx or .ods. |
vm.cells_df() |
Return the active sheet as a pandas DataFrame (requires pandas). |
elixcee.run_macro(vba, name) |
One-shot: run a macro and return {(row, col): value}. |
elixcee.load_workbook(path) |
Load an .xlsx or .ods file into a Vm. |
Coverage
See FUNCTIONS.md for the complete function and VBA syntax reference, including Excel version for each function.
Highlights:
- Classic (Excel 2003-): SUM, VLOOKUP, IF, PMT, FV, PV, NPER, RATE, IPMT, PPMT, NPV, IRR, MIRR, XNPV, XIRR, DGET, DSUM, DAVERAGE, DCOUNT, DCOUNTA, DMAX, DMIN, and 100+ core functions
- 2007–2019: IFERROR, COUNTIFS/SUMIFS, XOR, IFS, SWITCH, TEXTJOIN, MAXIFS/MINIFS
- 365/2021: XLOOKUP, XMATCH, FILTER, SORT, UNIQUE, SEQUENCE, LET, LAMBDA, MAP, REDUCE
- 2024/365: TEXTSPLIT, TEXTBEFORE, TEXTAFTER, VSTACK, HSTACK, TAKE, DROP, CHOOSECOLS, CHOOSEROWS
- VBA: For/If/While/With/On Error/Function/
Type...End Type/Named Ranges/Array of UDT
Named Ranges
Register a named range in VBA with Range("A1:B5").Name = "MyData", then use the name anywhere a range address is accepted:
Range("MyData").Value = 0 ' write to all cells in the range
For Each cell In Range("MyData") ' iterate over cells
total = total + cell
Next cell
Named ranges are stored on vm.named_ranges (a dict[str, str] mapping lowercase name → address).
Criteria Syntax (COUNTIF / SUMIF / SUMIFS / etc.)
| Criteria | Example | Meaning |
|---|---|---|
| Number | 10 |
Exact numeric match |
| String | "apple" |
Case-insensitive string match |
| Comparison | ">5", "<=10", "<>" |
Numeric comparison |
| Wildcard | "a*", "?bc" |
* = any chars, ? = one char |
Application Object
| Property / Method | Description | Behavior |
|---|---|---|
Application.Calculation = xlCalculationManual |
Disable auto-recalculation | Active |
Application.Calculation = xlCalculationAutomatic |
Enable auto-recalculation + re-evaluate all formula cells | Active |
Application.ScreenUpdating = False/True |
Suppress screen refresh | No-op (no screen) |
Application.EnableEvents = False/True |
Disable/enable event triggers | No-op (no events) |
Application.DisplayAlerts = False/True |
Suppress dialog boxes | No-op (no dialogs) |
Application.StatusBar = "..." / False |
Set/clear status bar text | No-op (no UI) |
Application.Cursor = xlWait / xlDefault |
Change cursor shape | No-op (no UI) |
Application.CutCopyMode = False |
Cancel clipboard mode | Active (clears the modeled clipboard) |
No-op properties are parsed and accepted without error, but have no effect. This allows VBA macro performance patterns (e.g.,
Application.ScreenUpdating = Falseat the start of a macro) to run unchanged.
Microsoft Excel round-trip validation
elixcee's workbook save path has been validated using five sanitized,
Microsoft Excel-authored .xlsm fixtures on Microsoft Excel for Mac.
Validated scope:
- open an Excel-authored workbook
- modify cells with elixcee
- save-as and in-place save
- reopen in Microsoft Excel without a repair warning
- preserve formulas, existing cell styles, merged cells, hidden rows/columns, VBA project bytes, unknown ZIP parts, and surviving relationships
Not validated:
- post-save VBA macro execution
- tables, data validation, conditional formatting, hyperlinks, comments, defined names, charts, images, and print settings embedded in regenerated worksheet XML
See compat/oracle-excel-com/results/0.9.0-A_summary.md
for the full results.
Not Yet Supported
See FUNCTIONS.md — Not Yet Supported for the full list.
Key gaps by category:
- Statistical: NORM.S.DIST, T.INV, F.DIST, CHISQ.DIST, and more
- Text: REPT, NUMBERVALUE, PHONETIC
- Out of scope: IMAGE (URL image fetch), GROUPBY (pivot aggregation), TRIMRANGE
Status Legend
| Mark | Meaning |
|---|---|
| Done | Implemented and tested |
| TBD | Not yet scheduled |
Development Phases
| Phase | Content | Status |
|---|---|---|
| Phase 1 | Rust project setup + pyo3 Python bindings | Done |
| Phase 2 | VBA parser MVP (Sub/End Sub, assignment, Cells) | Done |
| Phase 3 | Virtual Excel VM (variables, cell storage, interpreter) | Done |
| Phase 3.5 | Excel formula engine (SUM, IF, VLOOKUP, Application.Calculation, etc.) | Done |
| Phase 4 | Control flow (For loop, If/Else, arithmetic expressions) | Done |
| Phase 5 | Python interface (Vm class, run_macro, load_workbook, MsgBox) | Done |
| Phase 6 | Formula function expansion (100+ Excel functions, 118 tests) | Done |
| Phase 7 | Advanced VBA constructs (ElseIf, Exit, For Each, On Error, Function, arrays, While-Wend) | Done |
| Phase 8 | Range API (ClearContents, Offset, Sheets.Cells, WorksheetFunction, multi-sheet) | Done |
| Phase 9 | Multi-sheet support (Sheets HashMap, With Sheets, Python API, load_workbook all sheets) | Done |
| Phase 10 | Worksheet function expansion (math, trig, stats, array/spill, lambda functions) | Done |
| Phase 11 | User-defined types (Type...End Type), named ranges, RANDARRAY, pandas integration (cells_df), type stubs (.pyi) |
Done |
| Phase D1 | Remove rust_xlsxwriter, hand-written XLSX via zip (dependencies: 5→4) | Done |
| Phase D2 | Remove pest/pest_derive, hand-written recursive descent VBA parser (dependencies: 4→3) | Done |
| Phase D3 | Remove calamine from runtime, hand-written XLSX/ODS reader (dependencies: 3→2) | Done |
| Perf R4 | SUM/AVERAGE/MIN/MAX fast path (skip Vec<Variant>), RangeWrite dirty-flag batching |
Done |
| CLI | Standalone elixcee binary; pyo3 made optional; GitHub Actions release workflow |
Done |
| Milestone A | JSON agent contract (--json), error classification, MsgBox message log |
Done |
| Milestone A.1 | JSON contract hardening (serde_json-verified tests, message-log lifecycle, error code docs) |
Done |
| Milestone A.5 | Source location tracking — line/column in parse and runtime errors | Done |
| Milestone B1 | check subcommand — parse diagnostics, entrypoint check, MsgBox/interactive-call detection |
Done |
| Milestone B1.1 | check: undefined Sub/Function call detection, unsupported-construct (no-op) detection |
Done |
| Milestone B2 | Multi-module projects — multiple .bas files, Module.Sub qualified entrypoints, cross-module collision detection |
Done |
| Milestone B3 | Deterministic black-box tests (tests/blackbox.rs, declarative .toml fixtures) |
Done |
| Milestone B4 | snapshot subcommand — read a workbook's cells without executing VBA |
Done |
| Milestone B5a | test-workbook subcommand — property-based testing with generated boundary-value inputs |
Done |
| Milestone B6a | diagnose subcommand — missing sheet/workbook, array-out-of-bounds root causes |
Done |
| Milestone B6b | diagnose: Copy/Paste shape mismatch + clipboard state |
Done |
| Milestone B6c | diagnose: sheet protection (Protect/Unprotect) |
Done |
| Milestone B6c2 | diagnose: merged-cell-aware Copy/Paste diagnostics |
Done |
| Milestone B6d | diagnose-workbook — root-cause diagnosis across generated test cases |
Done |
| Milestone B7a | Multi-area Range/Union/Areas foundation for Copy/Paste diagnostics |
Done |
| Milestone B7b | Hidden row/column metadata foundation for Copy/Paste diagnostics | Done |
| Phase 3A-1 | compat/vba-semantics/ value-correctness suite: 208 → 301 cases (6 new categories); fixed single-line-If statement dispatch, Boolean arithmetic (True = -1), WorksheetFunction Boolean coercion, Empty equality |
Done |
| Phase 3A-2 | CI wasm job: fresh wasm-pack build (Node + web targets) plus a Node/browser-condition smoke test, wired into GitHub Actions |
Done |
| 0.5.0 | VBA structural semantics (: statement separator, Variant::Null with documented propagation, real object-Nothing state with alias safety, a runtime With target stack) merged with @elixcee/xlsx real-consumer/real-browser validation (packed-tarball install, headless-Chrome smoke, bundle-safe WASM loading, readFile()); compat/vba-semantics/ 301 → 386 cases; elixcee-types bumped to 0.2.0 for the new public Variant::Null enum variant; published to crates.io, PyPI, and GitHub Releases |
Done |