Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 17 additions & 13 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ right here, with the URL, the header and the data never passing through our serv
<img alt="Tailwind CSS" src="https://img.shields.io/badge/Tailwind_CSS-4-06B6D4?logo=tailwindcss&logoColor=white">
<img alt="Zustand" src="https://img.shields.io/badge/Zustand-5-443E38">
<a href="https://excel-to-go.vercel.app"><img alt="Open the app" src="https://img.shields.io/badge/▶_try_it-excel--to--go.vercel.app-2F9E44"></a>
<img alt="Vitest" src="https://img.shields.io/badge/tests-1974%20passing-2F9E44?logo=vitest&logoColor=white">
<img alt="Vitest" src="https://img.shields.io/badge/tests-1979%20passing-2F9E44?logo=vitest&logoColor=white">
<img alt="CI" src="https://github.com/SuruchBoss/ExcelToGo/actions/workflows/ci.yml/badge.svg">
</p>

Expand All @@ -65,7 +65,7 @@ self-hosted, straight from PostgreSQL/MySQL — one saved read-only query, and n
and full-fidelity Excel/PDF export, where a chart exported to `.xlsx` is a real, editable chart
bound to its cells, because the OOXML chart parts are written by hand (ExcelJS writes none). Plus optional
bring-your-own-backend cloud save and live co-editing over it — presence, last-writer-wins with the loser told, and
an undo that does not erase the other person's work. Bilingual UI (Thai/English), 1974 automated tests.
an undo that does not erase the other person's work. Bilingual UI (Thai/English), 1979 automated tests.

---

Expand Down Expand Up @@ -109,7 +109,7 @@ Want the harder parts: [embedding a Thai font in the PDF, with stacked tone mark

---

### 🧪 What 1974 passing tests could not catch
### 🧪 What 1979 passing tests could not catch

Every test of the assistant **mocks the model** — it returns what I imagined it would. Put a real
API key behind it, ask fourteen ordinary questions, and **six answers used functions this engine
Expand All @@ -120,7 +120,7 @@ Then **the first fix made it worse.** The rule started as "give the closest form
allows", so _"join all the names into one line"_ came back as `=SUM(A2:A20)` — `0` in the cell, no
error, nothing to notice. **A visible `#NAME?` traded for an invisible wrong number.**

**And it happened again, in a different place.** With every gate green — 1974 tests, `axe` clean on
**And it happened again, in a different place.** With every gate green — 1979 tests, `axe` clean on
both pages at two widths — an hour of clicking through the public build the way a first-time visitor
would found three things no gate can see:

Expand Down Expand Up @@ -395,7 +395,7 @@ Other available commands:
| `npm run build` | Build a production bundle |
| `npm run start` | Run the production build (run `npm run build` first) |
| `npm run lint` | Check code quality with ESLint |
| `npm test` | Run the 1974-case Vitest suite |
| `npm test` | Run the 1979-case Vitest suite |
| `npm run check:readme` | Check the READMEs still match the code (links/images/test count/new modules/both languages) |
| `npm run check:screens` | Figures printed on a screenshot still match the source |
| `npm run check:rls` | Two real accounts against your own Supabase: does the database refuse what the policies say it should (needs env) |
Expand Down Expand Up @@ -2512,12 +2512,16 @@ package again as it opens and **says so before anything is edited**: what, how m
| Excel PivotTables | values only | static numbers, not a PivotTable |
| Formulas calling a function the engine lacks | `#NAME?` | **the formula is kept**, and Excel calculates it |
| Range names that cannot come in (a formula, a whole column, several areas, or a sheet the file lacks) | formulas using them read `#NAME?` | the name is lost; formulas still carry it |
| Cells holding a constant error value (`#DIV/0!`, `#N/A`) | shown as the text of the code; a formula that reads one does not see an error, so `SUM` steps over it (until [#226](https://github.com/SuruchBoss/ExcelToGo/issues/226)) | **written back as the error value** |
| External links to other files | values do not update | lost |
| Macros (VBA) | not run | lost |

<p align="center"><img src="public/screenshots/en/69-file-losses.png" width="820"></p>

- **A plain file sees nothing.** The report comes up only when something is actually lost.
- **A constant error cell used to open blank, with nothing said** (#227), and a total over it read as a clean number. It now
keeps its code and goes back out as the error it was. An error code typed as Excel spells it (`#N/A`, `#DIV/0!`) also
goes out as the error, as Excel would store it; a leading apostrophe or the Text format keeps it text.
- **Got it** closes it. It opens again from the **menu** (or the amber button beside Export Excel from 1366px) for as long
as the file's sheets are open; undoing the open, or opening another file over it, takes the report away too.
- It reads the original package, not what the importer returned, because what the importer returned is exactly what
Expand Down Expand Up @@ -3010,7 +3014,7 @@ architecture behind it.
| `@anthropic-ai/sdk` | Connects to the Claude API for the AI assistant |
| `lucide-react` | UI icons |
| `clsx` | Conditional className composition |
| `vitest` | Unit tests for the formula engine, sort logic, JSON-to-table conversion, pagination, rate limiting, templates, file fidelity, conditional formatting and live blocks (1974 cases) |
| `vitest` | Unit tests for the formula engine, sort logic, JSON-to-table conversion, pagination, rate limiting, templates, file fidelity, conditional formatting and live blocks (1979 cases) |

> **Note:** No off-the-shelf formula library (e.g. HyperFormula) is used — the **formula engine is hand-written**
> (tokenizer, parser, evaluator, and functions) to keep full control over its behavior. See
Expand Down Expand Up @@ -3284,7 +3288,7 @@ src/
fillSeries.ts # What dragging the corner continues into — numbers, Thai days and months, quarters, formulas
sheetSearch.ts # Find and replace over the raw text rather than the displayed result
workbookRefs.ts # The formula rewrites that are not a fact about one sheet: cross-sheet shifts, renames
fileLosses.ts # What an opened file holds that the app can't keep (pictures, charts, PivotTables, macros, links, missing functions), read from the package (#83)
fileLosses.ts # What an opened file holds that the app can't keep (pictures, charts, PivotTables, macros, links, missing functions, constant error values), read from the package (#83)
sheetNames.ts # Excel's sheet-name rules in one place: ≤31 chars, no \ / ? * [ ] :, unique ignoring case (#54)
byok.ts # The visitor's own API key: this tab only, masked when shown
sheet.ts # The core sheet data model, whole-sheet computation, applying a formula by scope,
Expand Down Expand Up @@ -4050,13 +4054,13 @@ the framework bundle itself, which isn't a trade worth making here. Written down
## 🧪 Testing

```bash
npm test # 1974 cases across 140 files, via Vitest
npm test # 1979 cases across 140 files, via Vitest
```

Testing is focused on the **formula engine, sort logic, JSON-to-table conversion, pagination, rate-limit backoff, Excel templates and live-block placement** — pure functions with no React/DOM dependency, so
they run fast and give high confidence.

**But not one of those 1974 cases opens the app**, and nearly every bug this project found by hand lived in
**But not one of those 1979 cases opens the app**, and nearly every bug this project found by hand lived in
the wiring *between* pieces that all passed their tests — the toolbar's "+ row" called `addRow`, which
announced nothing, while `insertRowAtSelection` next to it announced correctly (both tested) · the AI
assistant sent a range including its text header, because the context builder read raw `sheet.cells`
Expand Down Expand Up @@ -4175,10 +4179,10 @@ once; disable `ArrowRight` in the grid and two assertions in the third fail. (Th
second one stayed green: the `case` I inserted landed *after* the existing `case "ArrowRight"` and was dead
code. Proving a gate means checking that the thing you meant to break actually broke.)

> **1974 tests passed, and 43% of the assistant's answers were unusable** — because those tests mock
> **1979 tests passed, and 43% of the assistant's answers were unusable** — because those tests mock
> the model, so it returns what the test author imagined. A test count says what you thought to ask,
> not whether you asked enough. Only a real API key found this: see
> [What 1974 passing tests could not catch](#-what-1974-passing-tests-could-not-catch), repeatable
> [What 1979 passing tests could not catch](#-what-1979-passing-tests-could-not-catch), repeatable
> with `npm run check:ai`.

| File | Cases | Tests |
Expand Down Expand Up @@ -4219,8 +4223,8 @@ code. Proving a gate means checking that the thing you meant to break actually b
| `sheetNames.test.ts` | 7 | Excel's sheet-name rules (#54): each rule broken, case counted as one name, cleaning a name and finding a free one, + never reusing a name after a delete, an old save's duplicate keeping its formulas on the first tab, an invalid name renamed with its formulas |
| `sheetMerges.test.ts` | 35 | Which cells a merge swallows, shifting merges on row/column insert and delete, dropping one that collapses to a single cell |
| `sheetTemplate.test.ts` | 14 | Which cells are locked vs. fields, inline and range-backed dropdown options, column-width conversion |
| `fileLosses.test.ts` | 10 | What an opened file can't keep (#83): a real file with a picture, a chart and a function the engine lacks, counted with its sheet; that formula exported as it was; a plain file reporting nothing; `_xlfn.` spelling of a function the engine has not counted; PivotTables, shapes, macros, links and a chart sheet as the spec lays them out; a non-zip not failing the open; range names that cannot come in (#60) as one line of the same report, after the missing functions, the whole report for a file with nothing else, and still said when the package cannot be read again |
| `excelIO.test.ts` | 55 | Builds a real .xlsx and round-trips it: reading fields/dropdowns/widths, an unprotected file isn't a template, export→import comes back identical, and styling (fills/font sizes/borders/row heights/merges) round-trips, as do all five kinds of conditional formatting rule and cell notes (both the plain-string and Excel's rich-text form), cross-sheet dropdowns read from the sheet the rule names (quoted names, a hidden sheet, a missing one, and the PaynEat ERP draft-0 template), a column exactly width 9 still 9 after export (#136), a merge in the file holding its value in the top-left cell only with SUM/COUNTA matching Excel and the merge still exported (#55) |
| `fileLosses.test.ts` | 12 | What an opened file can't keep (#83): a real file with a picture, a chart and a function the engine lacks, counted with its sheet; that formula exported as it was; a plain file reporting nothing; `_xlfn.` spelling of a function the engine has not counted; PivotTables, shapes, macros, links and a chart sheet as the spec lays them out; a non-zip not failing the open; range names that cannot come in (#60) as one line of the same report, after the missing functions, the whole report for a file with nothing else, and still said when the package cannot be read again; constant error cells (#227) counted with their sheet while a formula whose result is an error is not, and still counted after an export and a reopen |
| `excelIO.test.ts` | 58 | Builds a real .xlsx and round-trips it: reading fields/dropdowns/widths, an unprotected file isn't a template, export→import comes back identical, and styling (fills/font sizes/borders/row heights/merges) round-trips, as do all five kinds of conditional formatting rule and cell notes (both the plain-string and Excel's rich-text form), cross-sheet dropdowns read from the sheet the rule names (quoted names, a hidden sheet, a missing one, and the PaynEat ERP draft-0 template), a column exactly width 9 still 9 after export (#136), a merge in the file holding its value in the top-left cell only with SUM/COUNTA matching Excel and the merge still exported (#55), a real `t="e"` cell opening as its error code rather than blank, going back out as `t="e"` and still there on a reopen, while `'#N/A` and a Text-formatted cell go out as text (#227) |
| `charts.test.ts` | 42 | Reading a range into series and labels (including a text label column), gaps for non-numbers, a zero-anchored axis, moving/resizing/clamping a chart's frame, what the legend names per kind, shifting on edits |
| `server/urlGuard.test.ts` | 31 | Addresses the server refuses to reach (loopback, private ranges, cloud metadata, IPv6 link-local), IPv4 embedded in IPv6 (`::ffff:`, `::`, SIIT, NAT64, 6to4, Teredo), non-http schemes, and the allowlist |
| `server/sourcesAuth.test.ts` | 9 | No token means off, right/wrong/prefix tokens, and telling "switched off" apart from "wrong token" |
Expand Down
Loading
Loading