From f716fdcfbf4f7c9cf7e448b4c6c06067770ecb39 Mon Sep 17 00:00:00 2001
From: Claude
Date: Sun, 4 Oct 2026 15:28:21 +0000
Subject: [PATCH 1/2] Keep constant error cells from an opened .xlsx, report
them, write them back (#227)
An error cell with no formula (#DIV/0!) opened blank because the
importer had no branch for ExcelJS's { error } value, and a SUM over it came out as a
clean number with nothing said.
- excelIO: cellValueToRaw keeps the code as text (an error value once #226 lands).
- excelIO: an exact Excel error code goes out as an error value, as Excel stores it,
so a file that held #DIV/0! still reads #DIV/0! in Excel after a round trip. An
apostrophe or the Text format keeps it text.
- fileLosses: a new 'errorValues' kind counts constant error cells per sheet from the
package; a formula whose result is an error is not counted, since it recalculates.
- Both READMEs and both languages of the report.
Co-Authored-By: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_013pmWZsbmugfpqtYazrzfjj
---
README.en.md | 30 ++++++++++--------
README.md | 30 ++++++++++--------
src/app/opengraph-image.tsx | 2 +-
src/i18n/en.ts | 5 ++-
src/i18n/th.ts | 4 ++-
src/lib/excelIO.test.ts | 61 +++++++++++++++++++++++++++++++++++++
src/lib/excelIO.ts | 15 +++++++++
src/lib/fileLosses.test.ts | 28 +++++++++++++++++
src/lib/fileLosses.ts | 35 +++++++++++++++++++--
9 files changed, 178 insertions(+), 32 deletions(-)
diff --git a/README.en.md b/README.en.md
index 428adcc4..90610476 100644
--- a/README.en.md
+++ b/README.en.md
@@ -44,7 +44,7 @@ right here, with the URL, the header and the data never passing through our serv
-
+
@@ -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), 1970 automated tests.
+an undo that does not erase the other person's work. Bilingual UI (Thai/English), 1975 automated tests.
---
@@ -109,7 +109,7 @@ Want the harder parts: [embedding a Thai font in the PDF, with stacked tone mark
---
-### 🧪 What 1970 passing tests could not catch
+### 🧪 What 1975 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
@@ -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 — 1970 tests, `axe` clean on
+**And it happened again, in a different place.** With every gate green — 1975 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:
@@ -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 1970-case Vitest suite |
+| `npm test` | Run the 1975-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) |
@@ -2509,12 +2509,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 |

- **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
@@ -3007,7 +3011,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 (1970 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 (1975 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
@@ -3281,7 +3285,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,
@@ -4047,13 +4051,13 @@ the framework bundle itself, which isn't a trade worth making here. Written down
## 🧪 Testing
```bash
-npm test # 1970 cases across 139 files, via Vitest
+npm test # 1975 cases across 139 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 1970 cases opens the app**, and nearly every bug this project found by hand lived in
+**But not one of those 1975 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`
@@ -4172,10 +4176,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.)
-> **1970 tests passed, and 43% of the assistant's answers were unusable** — because those tests mock
+> **1975 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 1970 passing tests could not catch](#-what-1970-passing-tests-could-not-catch), repeatable
+> [What 1975 passing tests could not catch](#-what-1975-passing-tests-could-not-catch), repeatable
> with `npm run check:ai`.
| File | Cases | Tests |
@@ -4216,8 +4220,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" |
diff --git a/README.md b/README.md
index 31ac041b..5c683671 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@
-
+
@@ -61,7 +61,7 @@ and saying so when data came back incomplete. A range can be given a name (in Th
what it means, and a cell can carry a rule about what may go in it, refused before it is written rather than
flagged afterwards. Conditional formatting re-colours cells from their current values — comparisons,
top/bottom ranks, colour scales and data bars — and round-trips through Excel's own rule format. Bilingual UI
-(Thai/English), 1970 automated tests. บันทึกบนคลาวด์และการแก้พร้อมกันหลายคนมีให้เลือกใช้ได้ โดยต่อ backend ของคุณเอง
+(Thai/English), 1975 automated tests. บันทึกบนคลาวด์และการแก้พร้อมกันหลายคนมีให้เลือกใช้ได้ โดยต่อ backend ของคุณเอง
---
@@ -106,7 +106,7 @@ top/bottom ranks, colour scales and data bars — and round-trips through Excel'
---
-### 🧪 สิ่งที่เทสต์ 1970 เคสจับไม่ได้
+### 🧪 สิ่งที่เทสต์ 1975 เคสจับไม่ได้
เทสต์ของผู้ช่วย AI ใช้ **mock ทั้งหมด** — มันตอบตามที่ผมคิดว่ามันจะตอบ พอเอา API key จริงมายิงคำถามธรรมดา
14 ข้อ **6 คำตอบใช้ฟังก์ชันที่เอนจินนี้ไม่มี** (`TEXTJOIN` `FIND` `RANK.EQ` `SUMPRODUCT` `CEILING` `CHAR`)
@@ -116,7 +116,7 @@ top/bottom ranks, colour scales and data bars — and round-trips through Excel'
มันจึงตอบคำถาม *"ต่อชื่อทั้งหมดเป็นบรรทัดเดียว"* ด้วย `=SUM(A2:A20)` ซึ่งได้ `0` โดยไม่มีอะไรบอกว่าผิด
**เปลี่ยน error ที่มองเห็น เป็นตัวเลขผิดที่มองไม่เห็น**
-**แล้วมันก็เกิดอีก คนละที่** ตอนที่ทุกด่านเขียวหมด — 1970 เทสต์ผ่าน, `axe` ไม่มี violation ทั้งสองหน้า
+**แล้วมันก็เกิดอีก คนละที่** ตอนที่ทุกด่านเขียวหมด — 1975 เทสต์ผ่าน, `axe` ไม่มี violation ทั้งสองหน้า
ที่สองความกว้าง — ผมลองไล่คลิก build สาธารณะแบบคนที่เพิ่งเข้ามาครั้งแรกอยู่ชั่วโมงหนึ่ง แล้วเจอสามอย่าง
ที่ไม่มีด่านไหนมองเห็น:
@@ -381,7 +381,7 @@ npm run dev
| `npm run build` | build เป็นเวอร์ชัน production |
| `npm run start` | รันเวอร์ชันที่ build แล้ว (ต้อง `npm run build` ก่อน) |
| `npm run lint` | ตรวจสอบคุณภาพโค้ดด้วย ESLint |
-| `npm test` | รัน unit test 1970 เคสด้วย Vitest |
+| `npm test` | รัน unit test 1975 เคสด้วย Vitest |
| `npm run check:readme` | ตรวจว่า README ยังตรงกับโค้ด (ลิงก์/ภาพ/จำนวนเทสต์/โมดูลใหม่/สองภาษาตรงกัน) |
| `npm run check:screens` | ตัวเลขที่พิมพ์อยู่บนภาพหน้าจอยังตรงกับซอร์สไหม |
| `npm run check:rls` | ยิงจริงสองบัญชีบน Supabase ของคุณ ว่าฐานข้อมูลปฏิเสธตามที่ policy เขียนไว้ไหม (ต้องตั้ง env) |
@@ -2200,12 +2200,16 @@ merge เลื่อนตามการแทรก/ลบแถวและ
| Pivot Table ของ Excel | เห็นแค่ค่า | ค่าเป็นตัวเลขนิ่ง ไม่ใช่ Pivot Table |
| สูตรที่ใช้ฟังก์ชันที่เอนจินไม่มี | ขึ้น `#NAME?` | **สูตรเดิมยังอยู่** เปิดใน Excel แล้วคำนวณได้ |
| ชื่อช่วงที่นำเข้าไม่ได้ (ชื่อที่เป็นสูตร ทั้งคอลัมน์ หลายช่วง หรือชี้ชีตที่ไม่มี) | สูตรที่ใช้ชื่อขึ้น `#NAME?` | ชื่อหายไป สูตรยังถือชื่อนั้นไว้ |
+| เซลล์ที่เก็บค่า error คงที่ (`#DIV/0!`, `#N/A`) | แสดงเป็นข้อความของรหัส สูตรที่อ่านช่องนั้นไม่เห็นว่าเป็น error `SUM` จึงข้ามไป (จนกว่า [#226](https://github.com/SuruchBoss/ExcelToGo/issues/226) จะเข้า) | **เขียนกลับเป็นค่า error เหมือนเดิม** |
| ลิงก์ไปไฟล์อื่น (external links) | ค่าไม่อัปเดต | หายไป |
| มาโคร (VBA) | ไม่รัน | หายไป |

- **ไฟล์ธรรมดาไม่เห็นอะไรเลย** รายงานขึ้นเฉพาะเมื่อเจอของที่เก็บไม่ได้จริง
+- **เซลล์ที่เก็บค่า error คงที่เคยเปิดมาเป็นช่องว่างโดยไม่มีอะไรบอก** (#227) และยอดรวมที่อ่านช่องนั้นออกมาเป็นตัวเลขดูปกติ · ตอนนี้รหัสยังอยู่
+ และส่งออกกลับเป็นค่า error เหมือนเดิม · รหัส error ที่พิมพ์ตรงตามที่ Excel เขียน (`#N/A`, `#DIV/0!`) ก็ส่งออกเป็นค่า error แบบที่ Excel เก็บ
+ ถ้าขึ้นต้นด้วย `'` หรือจัดรูปแบบเป็นข้อความจะยังเป็นข้อความ
- กด **เข้าใจแล้ว** เพื่อปิด แล้วเปิดดูอีกครั้งได้จาก**เมนู** (หรือปุ่มสีเหลืองข้างปุ่มส่งออก Excel ตั้งแต่ 1366px) ตราบเท่าที่ชีตจากไฟล์นั้นยังเปิดอยู่
· undo การเปิดไฟล์ หรือเปิดไฟล์อื่นทับ รายงานก็หายไปด้วย
- อ่านจาก package ต้นฉบับ ไม่ใช่จากผลของตัวนำเข้า เพราะผลของตัวนำเข้าคือสิ่งที่ของพวกนี้หายไปแล้ว ·
@@ -2617,7 +2621,7 @@ stack ของ `font-mono` จึงต่อท้ายด้วย Plex Sans
| `@anthropic-ai/sdk` | เชื่อมต่อ Claude API สำหรับผู้ช่วย AI |
| `lucide-react` | ไอคอน UI |
| `clsx` | รวม className แบบมีเงื่อนไข |
-| `vitest` | unit test เอนจินคำนวณสูตร, ตรรกะเรียงข้อมูล, แปลง JSON เป็นตาราง, การแบ่งหน้า/rate limit, แม่แบบ/รูปแบบจากไฟล์ และบล็อกข้อมูลสด (1970 เคส) |
+| `vitest` | unit test เอนจินคำนวณสูตร, ตรรกะเรียงข้อมูล, แปลง JSON เป็นตาราง, การแบ่งหน้า/rate limit, แม่แบบ/รูปแบบจากไฟล์ และบล็อกข้อมูลสด (1975 เคส) |
> **หมายเหตุ:** ไม่ได้ใช้ไลบรารีคำนวณสูตรสำเร็จรูป (เช่น HyperFormula) แต่เขียน **เอนจินคำนวณสูตรขึ้นเอง**
> ทั้ง tokenizer, parser, evaluator และฟังก์ชันต่างๆ เพื่อควบคุมพฤติกรรมได้เต็มที่ ดูรายละเอียดที่หัวข้อ
@@ -2877,7 +2881,7 @@ src/
fillSeries.ts # ลากมุมแล้วได้อะไรต่อ — ชุดตัวเลข วันไทย เดือนไทย ไตรมาส และการขยับสูตร
sheetSearch.ts # ค้นหา/แทนที่จากข้อความดิบ ไม่ใช่ผลลัพธ์ที่แสดง
workbookRefs.ts # การแก้สูตรที่ไม่ใช่เรื่องของชีตเดียว — แทรกแถวข้ามชีต และเปลี่ยนชื่อชีต
- fileLosses.ts # ไฟล์ที่เปิดมามีอะไรที่แอปเก็บไว้ไม่ได้ (รูป กราฟ Pivot มาโคร ลิงก์ ฟังก์ชันที่ไม่มี) อ่านจาก package (#83)
+ fileLosses.ts # ไฟล์ที่เปิดมามีอะไรที่แอปเก็บไว้ไม่ได้ (รูป กราฟ Pivot มาโคร ลิงก์ ฟังก์ชันที่ไม่มี ค่า error คงที่) อ่านจาก package (#83)
sheetNames.ts # กติกาชื่อชีตของ Excel ที่เดียว — ≤31 ตัว, ไม่มี \ / ? * [ ] :, ไม่ซ้ำแม้ต่างตัวพิมพ์ (#54)
byok.ts # เก็บ API key ของผู้ใช้ไว้ในแท็บเดียว + ปิดบังตอนแสดงผล
sheet.ts # โมเดลข้อมูลตารางหลัก, คำนวณค่าทั้งชีต, ใส่สูตรตาม scope ต่างๆ, แทรก/ลบแถว-คอลัมน์
@@ -3514,13 +3518,13 @@ B อ่านและแก้ได้ แต่ยังยึดเป็
## 🧪 การทดสอบ
```bash
-npm test # 1970 เคส ใน 139 ไฟล์ ด้วย Vitest
+npm test # 1975 เคส ใน 139 ไฟล์ ด้วย Vitest
```
โฟกัสเทสต์ไปที่ **เอนจินคำนวณสูตร, ตรรกะเรียงข้อมูล, การแปลง JSON เป็นตาราง, การไล่ดึงหน้าถัดไป, การถอยเมื่อโดน rate limit, แม่แบบจากไฟล์ Excel, กฎจัดรูปแบบตามเงื่อนไข และการวางบล็อกข้อมูลสด** — ส่วนที่เป็น pure function ล้วน ไม่ต้องพึ่ง React/DOM
จึงเทสต์ได้เร็วและมั่นใจได้สูง
-**แต่ 1970 เคสนั้นไม่มีสักเคสที่เปิดแอป** และบั๊กที่โปรเจกต์นี้เจอด้วยการนั่งกดเอง อยู่ที่รอยต่อระหว่างชิ้นส่วนที่ผ่านเทสต์
+**แต่ 1975 เคสนั้นไม่มีสักเคสที่เปิดแอป** และบั๊กที่โปรเจกต์นี้เจอด้วยการนั่งกดเอง อยู่ที่รอยต่อระหว่างชิ้นส่วนที่ผ่านเทสต์
ทุกตัวแทบทั้งหมด — ปุ่ม "+ แถว" บนแถบเครื่องมือเรียก `addRow` ที่ไม่ประกาศอะไรเลย ขณะที่ `insertRowAtSelection`
ข้าง ๆ ประกาศถูกต้อง (ทั้งคู่เทสต์ผ่าน) · ผู้ช่วย AI ส่งช่วงที่รวมหัวคอลัมน์ไปด้วย เพราะตัวประกอบ context อ่าน
`sheet.cells` ที่เป็นสูตรดิบแทนค่าที่คำนวณแล้ว (ทั้งคู่เทสต์ผ่าน) · ปุ่มใหม่หนึ่งปุ่มดันปุ่มสลับภาษาตกขอบจอไป 42px
@@ -3620,9 +3624,9 @@ seed ถูกปักไว้ ด่านที่แดงจึงแด
ในตารางแล้วสองข้อในflow ที่สามตกทันที (ครั้งแรกที่ลองปิดยังเขียวอยู่ เพราะ `case` ที่แทรกเข้าไปไปอยู่หลัง
`case "ArrowRight"` เดิม กลายเป็นโค้ดตาย — การพิสูจน์ต้องเช็กด้วยว่าของที่ตั้งใจทำให้พังนั้นพังจริง)
-> **1970 เคสผ่านหมด แต่ 43% ของคำตอบจากผู้ช่วย AI ใช้ไม่ได้** — เพราะเทสต์ส่วนนั้น mock ตัวโมเดลไว้
+> **1975 เคสผ่านหมด แต่ 43% ของคำตอบจากผู้ช่วย AI ใช้ไม่ได้** — เพราะเทสต์ส่วนนั้น mock ตัวโมเดลไว้
> มันจึงตอบตามที่คนเขียนเทสต์คิดว่ามันจะตอบ จำนวนเทสต์บอกได้แค่ว่าเราถามอะไรไปบ้าง ไม่ได้บอกว่าเราถาม
-> ครบหรือยัง เรื่องนี้เจอได้ก็ต่อเมื่อเอา API key จริงมายิง — ดู [สิ่งที่เทสต์ 1970 เคสจับไม่ได้](#-สิ่งที่เทสต์-1970-เคสจับไม่ได้)
+> ครบหรือยัง เรื่องนี้เจอได้ก็ต่อเมื่อเอา API key จริงมายิง — ดู [สิ่งที่เทสต์ 1975 เคสจับไม่ได้](#-สิ่งที่เทสต์-1975-เคสจับไม่ได้)
> และรันซ้ำเองได้ด้วย `npm run check:ai`
| ไฟล์ | เคส | ทดสอบอะไร |
@@ -3663,8 +3667,8 @@ seed ถูกปักไว้ ด่านที่แดงจึงแด
| `sheetNames.test.ts` | 7 | กติกาชื่อชีตของ Excel (#54): ทุกข้อที่ผิด, ตัวพิมพ์เล็กใหญ่ถือเป็นชื่อเดียว, แก้ชื่อให้ถูกและหาชื่อที่ว่าง, ปุ่ม + ไม่ได้ชื่อซ้ำหลังลบแท็บ, งานเก่าที่ชื่อซ้ำ: แท็บแรกได้ชื่อเดิมและสูตรไม่ย้าย, ชื่อผิดกติกาเปลี่ยนพร้อมสูตร |
| `sheetMerges.test.ts` | 35 | ช่องไหนถูก merge กลืน, การเลื่อน merge ตอนแทรก/ลบแถว-คอลัมน์, merge ที่เหลือช่องเดียวถูกทิ้ง |
| `sheetTemplate.test.ts` | 14 | เซลล์ไหนล็อก/กรอกได้, ตัวเลือก dropdown แบบ inline และแบบอ้างช่วง, แปลงความกว้างคอลัมน์ |
-| `fileLosses.test.ts` | 10 | ไฟล์ที่เปิดมามีอะไรที่เก็บไว้ไม่ได้ (#83): ไฟล์จริงที่มีรูป กราฟ และฟังก์ชันที่เอนจินไม่มี นับได้ครบพร้อมชื่อชีต, สูตรนั้นส่งออกไปเหมือนเดิม, ไฟล์ธรรมดาไม่มีรายงาน, `_xlfn.` ของฟังก์ชันที่มีไม่ถูกนับ, Pivot Table/รูปร่าง/มาโคร/ลิงก์ และ chart sheet ตามรูปแบบใน spec, ไฟล์ที่ไม่ใช่ zip ไม่ทำให้การเปิดล้ม, ชื่อช่วงที่นำเข้าไม่ได้ (#60) เป็นบรรทัดหนึ่งของรายงานเดียวกัน ต่อจากฟังก์ชันที่ไม่มี เป็นรายงานทั้งหมดของไฟล์ที่ไม่มีอย่างอื่น และยังบอกแม้อ่าน package ซ้ำไม่ได้ |
-| `excelIO.test.ts` | 55 | สร้างไฟล์ .xlsx จริงแล้ววนครบรอบ: อ่านช่องกรอก/dropdown/ความกว้าง, ไฟล์ที่ไม่ protect ไม่ใช่แม่แบบ, export แล้ว import กลับได้เหมือนเดิม, และรูปแบบ (แถบสี/ขนาดตัวอักษร/เส้นขอบ/ความสูงแถว/merge) ครบรอบ รวมถึงกฎจัดรูปแบบตามเงื่อนไขทั้ง 5 แบบ และโน้ตของเซลล์ (ทั้งที่เขียนเป็นข้อความธรรมดาและ rich text ของ Excel), dropdown ข้ามชีตอ่านจากชีตที่กฎชี้ (ชื่อชีตมีเครื่องหมายคำพูด, ชีตซ่อน, ชีตที่ไม่มีอยู่, แม่แบบร่าง 0 ของ PaynEat ERP), คอลัมน์กว้าง 9 พอดีส่งออกแล้วยังเป็น 9 (#136), merge ในไฟล์มีค่าที่ซ้ายบนช่องเดียว SUM/COUNTA ตรงกับ Excel และ merge ยังส่งออกไป (#55) |
+| `fileLosses.test.ts` | 12 | ไฟล์ที่เปิดมามีอะไรที่เก็บไว้ไม่ได้ (#83): ไฟล์จริงที่มีรูป กราฟ และฟังก์ชันที่เอนจินไม่มี นับได้ครบพร้อมชื่อชีต, สูตรนั้นส่งออกไปเหมือนเดิม, ไฟล์ธรรมดาไม่มีรายงาน, `_xlfn.` ของฟังก์ชันที่มีไม่ถูกนับ, Pivot Table/รูปร่าง/มาโคร/ลิงก์ และ chart sheet ตามรูปแบบใน spec, ไฟล์ที่ไม่ใช่ zip ไม่ทำให้การเปิดล้ม, ชื่อช่วงที่นำเข้าไม่ได้ (#60) เป็นบรรทัดหนึ่งของรายงานเดียวกัน ต่อจากฟังก์ชันที่ไม่มี เป็นรายงานทั้งหมดของไฟล์ที่ไม่มีอย่างอื่น และยังบอกแม้อ่าน package ซ้ำไม่ได้, เซลล์ error คงที่ (#227) นับพร้อมชื่อชีตแต่สูตรที่ผลเป็น error ไม่นับ และยังนับได้หลังส่งออกแล้วเปิดใหม่ |
+| `excelIO.test.ts` | 58 | สร้างไฟล์ .xlsx จริงแล้ววนครบรอบ: อ่านช่องกรอก/dropdown/ความกว้าง, ไฟล์ที่ไม่ protect ไม่ใช่แม่แบบ, export แล้ว import กลับได้เหมือนเดิม, และรูปแบบ (แถบสี/ขนาดตัวอักษร/เส้นขอบ/ความสูงแถว/merge) ครบรอบ รวมถึงกฎจัดรูปแบบตามเงื่อนไขทั้ง 5 แบบ และโน้ตของเซลล์ (ทั้งที่เขียนเป็นข้อความธรรมดาและ rich text ของ Excel), dropdown ข้ามชีตอ่านจากชีตที่กฎชี้ (ชื่อชีตมีเครื่องหมายคำพูด, ชีตซ่อน, ชีตที่ไม่มีอยู่, แม่แบบร่าง 0 ของ PaynEat ERP), คอลัมน์กว้าง 9 พอดีส่งออกแล้วยังเป็น 9 (#136), merge ในไฟล์มีค่าที่ซ้ายบนช่องเดียว SUM/COUNTA ตรงกับ Excel และ merge ยังส่งออกไป (#55), เซลล์ `t="e"` จริงเปิดมาได้รหัส error ไม่ใช่ช่องว่าง ส่งออกกลับเป็น `t="e"` และเปิดซ้ำยังอยู่ ส่วน `'#N/A` กับช่องรูปแบบข้อความยังออกเป็นข้อความ (#227) |
| `charts.test.ts` | 42 | อ่านช่วงเป็นชุดข้อมูล+ป้ายกำกับ (รวมกรณีคอลัมน์ซ้ายเป็นข้อความ), ช่องว่างเมื่อไม่ใช่ตัวเลข, แกนที่รวมศูนย์, การย้าย/ย่อขยาย/หนีบกรอบไว้ในชีต, คำอธิบายสีตามชนิดกราฟ, การเลื่อนตามแทรก/ลบ |
| `server/urlGuard.test.ts` | 31 | ที่อยู่ที่ห้ามยิงถึง (loopback, วงใน, metadata ของคลาวด์, IPv6 link-local), IPv4 ที่ซ่อนใน IPv6 (`::ffff:`, `::`, SIIT, NAT64, 6to4, Teredo), scheme ที่ไม่ใช่ http, และ allowlist |
| `server/sourcesAuth.test.ts` | 9 | ไม่ตั้งโทเคน = ปิดทั้งหมด, โทเคนถูก/ผิด/เป็นแค่บางส่วน, แยก "ปิดอยู่" ออกจาก "โทเคนผิด" |
diff --git a/src/app/opengraph-image.tsx b/src/app/opengraph-image.tsx
index 41705f95..bd9da59c 100644
--- a/src/app/opengraph-image.tsx
+++ b/src/app/opengraph-image.tsx
@@ -29,7 +29,7 @@ export default function OgImage() {
const stats = [
["66", "engine functions"],
["37", "palette formulas"],
- ["1970", "automated tests"],
+ ["1975", "automated tests"],
["325", "of them security"],
["0", "formula libraries"],
];
diff --git a/src/i18n/en.ts b/src/i18n/en.ts
index 36bf7f02..ad1dbb39 100644
--- a/src/i18n/en.ts
+++ b/src/i18n/en.ts
@@ -430,7 +430,7 @@ export const en: Messages = {
stats: [
{ value: "37", label: "ready-made formulas" },
{ value: "66", label: "engine functions" },
- { value: "1970", label: "automated tests" },
+ { value: "1975", label: "automated tests" },
{ value: "325", label: "security tests" },
{ value: "0", label: "formula libraries used" },
],
@@ -816,12 +816,15 @@ export const en: Messages = {
pivots: (n) => `${n} Excel ${n === 1 ? "PivotTable" : "PivotTables"}`,
unknownFunctions: (n, names) => `${n} ${n === 1 ? "formula uses" : "formulas use"} a function the app doesn't have yet (${names.join(", ")})`,
names: (n, names) => `${n} range ${n === 1 ? "name" : "names"} that could not be brought in (${names.join(", ")})`,
+ errorValues: (n) => `${n} ${n === 1 ? "cell holds" : "cells hold"} an error value such as #DIV/0! or #N/A`,
externalLinks: (n) => `${n} ${n === 1 ? "link" : "links"} to other files`,
macros: () => "Macros (VBA)",
},
detail: {
unknownFunctions: "The app shows #NAME? for them, but the exported file keeps the formula as it was, and Excel can calculate it",
names: "Formulas using them read #NAME?, and the exported file won't have these names. Only a name for one range on a sheet of the file comes in — not a formula, a whole column or several areas",
+ errorValues:
+ "The app shows them as text, so a formula that reads one does not see an error: SUM steps over it and gives a number. The exported file writes them back as error values, as they were",
externalLinks: "Values pulled from other files won't update in the app",
macros: "The app doesn't run macros, and the exported file doesn't carry them",
},
diff --git a/src/i18n/th.ts b/src/i18n/th.ts
index 12560f81..479c167c 100644
--- a/src/i18n/th.ts
+++ b/src/i18n/th.ts
@@ -429,7 +429,7 @@ export const th: Messages = {
stats: [
{ value: "37", label: "สูตรพร้อมใช้" },
{ value: "66", label: "ฟังก์ชันในเอนจิน" },
- { value: "1970", label: "เทสต์อัตโนมัติ" },
+ { value: "1975", label: "เทสต์อัตโนมัติ" },
{ value: "325", label: "เทสต์ด้านความปลอดภัย" },
{ value: "0", label: "ไลบรารีคำนวณสูตร" },
],
@@ -814,12 +814,14 @@ export const th: Messages = {
pivots: (n) => `Pivot Table ของ Excel ${n} ตัว`,
unknownFunctions: (n, names) => `สูตร ${n} ช่องที่ใช้ฟังก์ชันที่แอปยังไม่มี (${names.join(", ")})`,
names: (n, names) => `ชื่อช่วง ${n} ชื่อที่นำเข้ามาไม่ได้ (${names.join(", ")})`,
+ errorValues: (n) => `เซลล์ ${n} ช่องที่เก็บค่า error เช่น #DIV/0! หรือ #N/A`,
externalLinks: (n) => `ลิงก์ไปไฟล์อื่น ${n} ลิงก์`,
macros: () => "มาโคร (VBA)",
},
detail: {
unknownFunctions: "ในแอปจะขึ้น #NAME? แต่ไฟล์ที่ส่งออกยังเก็บสูตรเดิมไว้ เปิดใน Excel แล้วคำนวณได้",
names: "สูตรที่ใช้ชื่อเหล่านี้ขึ้น #NAME? และไฟล์ที่ส่งออกจะไม่มีชื่อเหล่านี้ · นำเข้าได้เฉพาะชื่อที่ชี้ช่วงเดียวบนชีตในไฟล์ ไม่ใช่สูตร ทั้งคอลัมน์ หรือหลายช่วง",
+ errorValues: "ในแอปแสดงเป็นข้อความ สูตรที่อ่านช่องเหล่านี้จึงไม่เห็นว่าเป็น error เช่น SUM จะข้ามไปแล้วได้ตัวเลข · ไฟล์ที่ส่งออกยังเขียนกลับเป็นค่า error เหมือนเดิม",
externalLinks: "ค่าที่ดึงจากไฟล์อื่นจะไม่อัปเดตในแอป",
macros: "แอปไม่รันมาโคร และไฟล์ที่ส่งออกไม่มีมาโครติดไป",
},
diff --git a/src/lib/excelIO.test.ts b/src/lib/excelIO.test.ts
index 1f7a48a6..f4a6f976 100644
--- a/src/lib/excelIO.test.ts
+++ b/src/lib/excelIO.test.ts
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import ExcelJS from "exceljs";
+import JSZip from "jszip";
import { describe, expect, it } from "vitest";
import { exportWorkbookToXlsxBlob, importWorkbookFromFile } from "./excelIO";
import { computeSheet, createEmptySheet, createWorkbookResolver, setCellRaw, SheetModel } from "./sheet";
@@ -705,3 +706,63 @@ describe("a merged cell imports as Excel holds it", () => {
expect(ws.getCell("A1").value).toBe(10);
});
});
+
+/**
+ * A constant error cell, as Excel writes one: `#DIV/0!` with no formula (#227).
+ * The importer had no branch for it, so it opened blank and a total over it came out clean.
+ */
+async function errorCellsFile(): Promise {
+ const wb = new ExcelJS.Workbook();
+ const ws = wb.addWorksheet("ยอดขาย");
+ ws.getCell("A1").value = { error: "#DIV/0!" } as ExcelJS.CellErrorValue;
+ ws.getCell("A2").value = { error: "#N/A" } as ExcelJS.CellErrorValue;
+ ws.getCell("A3").value = 5;
+ ws.getCell("A4").value = { formula: "SUM(A1:A3)", result: { error: "#DIV/0!" } } as ExcelJS.CellFormulaValue;
+ return new File([await wb.xlsx.writeBuffer()], "errors.xlsx");
+}
+
+/** The `` element for one cell, straight from the package, so a test sees what Excel will read. */
+async function cellXml(data: Blob | ArrayBuffer, address: string): Promise {
+ const zip = await JSZip.loadAsync(data instanceof Blob ? await data.arrayBuffer() : data);
+ const xml = (await zip.file("xl/worksheets/sheet1.xml")?.async("string")) ?? "";
+ return new RegExp(`]*?(?:/>|>[\\s\\S]*?)`).exec(xml)?.[0] ?? "";
+}
+
+describe("a constant error cell in an opened file (#227)", () => {
+ it("keeps its code instead of opening blank", async () => {
+ const file = await errorCellsFile();
+ // The file holds the cells the way Excel writes them: an error type and no formula.
+ expect(await cellXml(await file.arrayBuffer(), "A1")).toMatch(/t="e"[^>]*>\s*#DIV\/0!<\/v>/);
+ expect(await cellXml(await file.arrayBuffer(), "A1")).not.toContain("");
+
+ const [{ sheet }] = await importWorkbookFromFile(file);
+ expect(sheet.cells.slice(0, 4).map((row) => row[0])).toEqual(["#DIV/0!", "#N/A", "5", "=SUM(A1:A3)"]);
+ });
+
+ it("goes back out as an error value, so Excel's own SUM over it still says #DIV/0!", async () => {
+ const [{ sheet }] = await importWorkbookFromFile(await errorCellsFile());
+ const blob = await exportWorkbookToXlsxBlob(exportable(sheet, "ยอดขาย"));
+ expect(await cellXml(blob, "A1")).toMatch(/t="e"[^>]*>\s*#DIV\/0!<\/v>/);
+ expect(await cellXml(blob, "A2")).toMatch(/t="e"[^>]*>\s*#N\/A<\/v>/);
+
+ // And open → export → open keeps them.
+ const [{ sheet: again }] = await importWorkbookFromFile(new File([await blob.arrayBuffer()], "again.xlsx"));
+ expect(again.cells.slice(0, 4).map((row) => row[0])).toEqual(["#DIV/0!", "#N/A", "5", "=SUM(A1:A3)"]);
+ });
+
+ it("text that only looks like an error stays text: after an apostrophe, or in a cell formatted as text", async () => {
+ const sheet = createEmptySheet(5, 2);
+ sheet.cells[0][0] = "'#N/A";
+ sheet.cells[1][0] = "#N/A";
+ sheet.formats[1][0] = { numberFormat: "text" };
+ sheet.cells[2][0] = "#REF!";
+ sheet.cells[3][0] = "#n/a";
+ const ws = await readBack(await exportWorkbookToXlsxBlob(exportable(sheet, "Codes")));
+ expect(ws.getCell("A1").value).toBe("#N/A");
+ expect(ws.getCell("A2").value).toBe("#N/A");
+ // Typed as Excel spells it, it is the error Excel would make of it.
+ expect(ws.getCell("A3").value).toEqual({ error: "#REF!" });
+ // Only the exact spelling Excel writes in a file goes out as an error; any other spelling stays as typed.
+ expect(ws.getCell("A4").value).toBe("#n/a");
+ });
+});
diff --git a/src/lib/excelIO.ts b/src/lib/excelIO.ts
index c62d9fe8..21597980 100644
--- a/src/lib/excelIO.ts
+++ b/src/lib/excelIO.ts
@@ -108,6 +108,13 @@ function cellValueToRaw(cell: ExcelJS.Cell): string {
if ("text" in v && typeof v.text === "string") {
return v.text;
}
+ // A constant error, `#DIV/0!`. Kept as its code rather than dropped (#227):
+ // it opened blank, and a total over it came out as a clean number. The grid holds it as text
+ // until error values can live in a cell (#226); the open report says so, and export writes it
+ // back as the error it was.
+ if ("error" in v && typeof v.error === "string") {
+ return v.error;
+ }
if ("result" in v) {
const result = v.result;
return result === undefined || result === null ? "" : String(result);
@@ -693,6 +700,9 @@ export async function importWorkbookFromFile(file: File): Promise v !== undefined && v !== false);
@@ -720,6 +730,11 @@ async function writeSheetToWorksheet(worksheet: ExcelJS.Worksheet, sheet: SheetM
};
} else if (raw === "") {
// leave blank
+ } else if (EXCEL_ERROR_CODES.has(raw) && format?.numberFormat !== "text") {
+ // An error code as Excel spells it goes out as the error value, the way Excel itself stores
+ // `#N/A` typed into a cell. Written as text, a file that held `#DIV/0!` came back to Excel
+ // with SUM stepping over it (#227). An apostrophe or a Text format still keeps it text.
+ cell.value = { error: raw } as ExcelJS.CellErrorValue;
} else {
// The engine's own reading, not a second copy of it: a file that decided differently from
// the grid is how `0812345678` went out as the number 812345678 (#23).
diff --git a/src/lib/fileLosses.test.ts b/src/lib/fileLosses.test.ts
index 95d4ad36..06de1bbd 100644
--- a/src/lib/fileLosses.test.ts
+++ b/src/lib/fileLosses.test.ts
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { readFileSync } from "node:fs";
+import ExcelJS from "exceljs";
import JSZip from "jszip";
import { describe, expect, it } from "vitest";
import { findFileLosses, unknownFunctionsIn } from "./fileLosses";
@@ -156,3 +157,30 @@ describe("range names a file holds that cannot come in (#60), in the same report
expect(losses).toEqual([{ kind: "names", count: 1, sheets: [], names: ["Dynamic"] }]);
});
});
+
+describe("constant error cells (#227), shown as text until #226", () => {
+ /** Two sheets: errors typed into the first, as Excel writes them, and a formula that errs in the second. */
+ async function errorsFile(): Promise {
+ const wb = new ExcelJS.Workbook();
+ const sales = wb.addWorksheet("ยอดขาย");
+ sales.getCell("A1").value = { error: "#DIV/0!" } as ExcelJS.CellErrorValue;
+ sales.getCell("A2").value = { error: "#N/A" } as ExcelJS.CellErrorValue;
+ sales.getCell("A3").value = 5;
+ sales.getCell("A4").value = { formula: "SUM(A1:A3)", result: { error: "#DIV/0!" } } as ExcelJS.CellFormulaValue;
+ const other = wb.addWorksheet("อื่นๆ");
+ other.getCell("B2").value = { formula: "1/0", result: { error: "#DIV/0!" } } as ExcelJS.CellFormulaValue;
+ return new Uint8Array(await wb.xlsx.writeBuffer());
+ }
+
+ it("are counted and placed; a formula whose result is an error is not, since it calculates again", async () => {
+ const { losses } = await openFixture(await errorsFile());
+ expect(losses).toEqual([{ kind: "errorValues", count: 2, sheets: ["ยอดขาย"] }]);
+ });
+
+ it("are still there, and still said, after the file goes out of the app and back in", async () => {
+ const { sheets } = await openFixture(await errorsFile());
+ const blob = await exportWorkbookToXlsxBlob(sheets.map(({ name, sheet }) => ({ name, sheet, computed: computeSheet(sheet) })));
+ const { losses } = await openFixture(new Uint8Array(await blob.arrayBuffer()));
+ expect(losses).toEqual([{ kind: "errorValues", count: 2, sheets: ["ยอดขาย"] }]);
+ });
+});
diff --git a/src/lib/fileLosses.ts b/src/lib/fileLosses.ts
index 9ccd81b7..d408e2ce 100644
--- a/src/lib/fileLosses.ts
+++ b/src/lib/fileLosses.ts
@@ -22,7 +22,16 @@ import { tokenize } from "./formulaEngine/tokenizer";
import { FUNCTIONS } from "./formulaEngine/functions";
import type { SheetModel } from "./sheet";
-export type LossKind = "pictures" | "shapes" | "charts" | "pivots" | "macros" | "externalLinks" | "unknownFunctions" | "names";
+export type LossKind =
+ | "pictures"
+ | "shapes"
+ | "charts"
+ | "pivots"
+ | "macros"
+ | "externalLinks"
+ | "unknownFunctions"
+ | "names"
+ | "errorValues";
export interface FileLoss {
kind: LossKind;
@@ -36,9 +45,10 @@ export interface FileLoss {
/**
* The order the notice lists them in: what a person sees in the file first, then what runs. Range
* names sit next to unknown functions because both end in `#NAME?` on screen; a person who
- * reads one line about `#NAME?` should find the other cause right under it.
+ * reads one line about `#NAME?` should find the other cause right under it. Error values follow,
+ * the last thing that changes what a formula reads.
*/
-const ORDER: LossKind[] = ["pictures", "charts", "shapes", "pivots", "unknownFunctions", "names", "externalLinks", "macros"];
+const ORDER: LossKind[] = ["pictures", "charts", "shapes", "pivots", "unknownFunctions", "names", "errorValues", "externalLinks", "macros"];
interface Rel {
type: string;
@@ -78,6 +88,23 @@ function resolve(dir: string, target: string): string {
const endsWith = (type: string, kind: string) => type.endsWith(`/${kind}`);
+/**
+ * Cells holding a constant error, `#DIV/0!` (#227). The app keeps the code as
+ * text until #226, so a formula that reads one does not see an error; that is what the notice owes.
+ * A cell with a formula is not one of them: its error is a result, and the app calculates it again.
+ */
+export function countConstantErrors(sheetXml: string): number {
+ if (!sheetXml.includes('t="e"')) return 0;
+ let n = 0;
+ for (const m of sheetXml.matchAll(/<(?:[A-Za-z_][\w.-]*:)?c\b([^>]*?)(?:\/>|>([\s\S]*?)<\/(?:[A-Za-z_][\w.-]*:)?c>)/g)) {
+ if (!/\bt="e"/.test(m[1])) continue;
+ const inner = m[2] ?? "";
+ if (/<(?:[A-Za-z_][\w.-]*:)?f[\s>/]/.test(inner)) continue;
+ if (/<(?:[A-Za-z_][\w.-]*:)?v[\s>]/.test(inner)) n += 1;
+ }
+ return n;
+}
+
/** Count of elements with this local name, whatever prefix the writer chose (`xdr:pic`, `pic`). */
function countElements(xml: string, local: string): number {
return (xml.match(new RegExp(`<(?:[A-Za-z_][\\w.-]*:)?${local}[\\s>/]`, "g")) ?? []).length;
@@ -162,6 +189,8 @@ export async function findFileLosses(
tally.add("charts", 1, part.name);
continue;
}
+ const sheetXml = (await zip.file(part.path)?.async("string")) ?? "";
+ tally.add("errorValues", countConstantErrors(sheetXml), part.name);
const rels = await relsOf(zip, part.path);
for (const rel of rels.values()) {
if (endsWith(rel.type, "pivotTable")) tally.add("pivots", 1, part.name);
From 40880219b9733b79070dab973176856d710388e6 Mon Sep 17 00:00:00 2001
From: Claude
Date: Sun, 4 Oct 2026 16:15:14 +0000
Subject: [PATCH 2/2] Retake landing stats picture and social card for 1979
tests
Co-Authored-By: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_013pmWZsbmugfpqtYazrzfjj
---
public/screenshots/27-landing-stats.png | Bin 61652 -> 62735 bytes
public/screenshots/en/27-landing-stats.png | Bin 59266 -> 60352 bytes
public/screenshots/screenshots.json | 4 ++--
public/social-preview.png | Bin 109482 -> 109968 bytes
4 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/public/screenshots/27-landing-stats.png b/public/screenshots/27-landing-stats.png
index e188902cbba2c031aadf1d31ee9cf608cc69d2e1..71229d0ca47da225bc89f911963974475c2d99f1 100644
GIT binary patch
literal 62735
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
z
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