From e842eb3a858537034a20bf36d284b04a72d506c6 Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 5 Oct 2026 02:13:12 +0000
Subject: [PATCH 1/2] Show serial 0 and negative values under a date format as
Excel does (#219)
Checked in real Excel: 0 under dd/mm/yyyy shows 00/01/1900, a negative
value under a date or [h]:mm format fills the cell with #, and TEXT of a
negative value under a date or time code gives #VALUE!. The app showed
31/12/1899 and 30/12/1899, dates Excel never shows.
formatSerial now shows serial 0 as day 0 of January 1900, leaving
partsOfSerial as it is for the date functions and the ISO text a cell
keeps. A negative serial under a date or time format shows ######## in
the grid and #VALUE! from TEXT.
Co-Authored-By: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_013pmWZsbmugfpqtYazrzfjj
---
README.en.md | 27 ++++++++++++++-----------
README.md | 24 ++++++++++++----------
src/app/opengraph-image.tsx | 2 +-
src/i18n/en.ts | 2 +-
src/i18n/th.ts | 2 +-
src/lib/cellFormat.ts | 4 ++--
src/lib/excelDate.test.ts | 15 ++++++++++++++
src/lib/excelDate.ts | 17 +++++++++++++++-
src/lib/formulaEngine/functions.test.ts | 10 +++++++++
src/lib/formulaEngine/functions.ts | 6 ++++--
10 files changed, 78 insertions(+), 31 deletions(-)
diff --git a/README.en.md b/README.en.md
index 0a832b96..c4b27263 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), 2006 automated tests.
+an undo that does not erase the other person's work. Bilingual UI (Thai/English), 2009 automated tests.
---
@@ -109,7 +109,7 @@ Want the harder parts: [embedding a Thai font in the PDF, with stacked tone mark
---
-### 🧪 What 2006 passing tests could not catch
+### 🧪 What 2009 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 — 2006 tests, `axe` clean on
+**And it happened again, in a different place.** With every gate green — 2009 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 2006-case Vitest suite |
+| `npm test` | Run the 2009-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) |
@@ -3037,7 +3037,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 (2006 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 (2009 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
@@ -3717,7 +3717,10 @@ unformatted.
**Elapsed time counts past a day, as Excel does (#219)** — `[h]`, `[mm]` and `[ss]` count the total hours, minutes or
seconds, not the time of day. 1.5 days in `[h]:mm` shows `36:00` (it used to show `12:00`), so a timesheet totalled in
-`[h]:mm` shows the real total, in the grid and in `TEXT` alike.
+`[h]:mm` shows the real total, in the grid and in `TEXT` alike. Zero and negative values show as Excel shows them
+too (checked in real Excel): 0 under `dd/mm/yyyy` is `00/01/1900` (it used to show `31/12/1899`, a date Excel never
+shows), a negative value under a date or time format shows `########` rather than an invented date, and `TEXT` of a
+negative value under a date or time code gives `#VALUE!`.
**Not supported:** `MATCH` over a two-dimensional range — that returns `#N/A` rather than guessing a
position inside a block. `MROUND` is not in the engine. Arithmetic that overflows (`10^308*10`) still
@@ -4087,13 +4090,13 @@ the framework bundle itself, which isn't a trade worth making here. Written down
## 🧪 Testing
```bash
-npm test # 2006 cases across 144 files, via Vitest
+npm test # 2009 cases across 144 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 2006 cases opens the app**, and nearly every bug this project found by hand lived in
+**But not one of those 2009 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`
@@ -4212,10 +4215,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.)
-> **2006 tests passed, and 43% of the assistant's answers were unusable** — because those tests mock
+> **2009 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 2006 passing tests could not catch](#-what-2006-passing-tests-could-not-catch), repeatable
+> [What 2009 passing tests could not catch](#-what-2009-passing-tests-could-not-catch), repeatable
> with `npm run check:ai`.
| File | Cases | Tests |
@@ -4246,7 +4249,7 @@ code. Proving a gate means checking that the thing you meant to break actually b
| `moveRefs.test.ts` | 7 | Reference rewriting for a cut and paste (#51): references into the block follow it with their `$`, everything outside and an untouched formula's text stay as they were, a range moves only when all of it was cut, a sheet name is added across sheets, a moved formula keeps naming the sheet it came from, a reference to a cell the block landed on becomes `#REF!`, and a cut under a filter moves only the rows it took |
| `sheetSort.test.ts` | 13 | The bounds/header-detection heuristic, and sorting itself (blank values, limited column scope) · The header stays on top either way (#49): numbers under a word in any column, a label over an empty column, bold, a row with a number is not a header, a plain word list sorts whole |
| `jsonToTable.test.ts` | 14 | Finding the record array in a response, flattening nested objects, numeric-column detection, single-row KPI objects, an empty list in an envelope as no rows rather than a row of the envelope (#65), CSV codes keeping their leading zeros all the way to the sheet (#36) |
-| `excelDate.test.ts` | 17 | Dates as Excel keeps them (#45): 1900-system serials matching Excel, including its 29 Feb 1900; the ISO forms read as dates (others stay text); Excel date format codes (`dd/mm/yyyy`, `d-mmm-yy`, `h:mm AM/PM`) and elapsed time past a day (`[h]:mm` of 1.5 days is `36:00`, #219) · Buddhist-Era dates (#82): every form in the issue as 15 Jan 2026, all twelve Thai months full, short and dotless, a two-digit year beside a Thai month, what must not be guessed (`2569`, `15/01/69`, `15/01/2024`), every Thai-calendar code plus `bbbb` and Thai month names |
+| `excelDate.test.ts` | 19 | Dates as Excel keeps them (#45): 1900-system serials matching Excel, including its 29 Feb 1900; the ISO forms read as dates (others stay text); Excel date format codes (`dd/mm/yyyy`, `d-mmm-yy`, `h:mm AM/PM`) and elapsed time past a day (`[h]:mm` of 1.5 days is `36:00`, #219), 0 as `00/01/1900` and a negative value as `########` in the grid (#219) · Buddhist-Era dates (#82): every form in the issue as 15 Jan 2026, all twelve Thai months full, short and dotless, a two-digit year beside a Thai month, what must not be guessed (`2569`, `15/01/69`, `15/01/2024`), every Thai-calendar code plus `bbbb` and Thai month names |
| `dateConvert.test.ts` | 6 | "Convert to dates" (#82): a two-digit B.E. year is 25yy and an A.D. one follows Excel's window, month-first and year-first orders, dates already readable written as ISO, unreadable text left alone, a range planned without empty cells or formulas |
| `numberFormatCode.test.ts` | 6 | Excel number-format codes (#53): the issue's table (7%, 12.5%, $1,234.50, 1.235, 12,345), the old `0.00"%"` not multiplied, negative/zero sections, currency tags, scaling commas, literals between digits, and the fallback for fractions and scientific codes |
| `numberFormats.test.ts` | 5 | Percent, currency and decimals through a real .xlsx both ways (#53): a file's codes shown and exported unchanged, the app's Percent ×100 and exported as `0.00%`, and old sheets in the browser and the cloud (format 1) still reading 50.00% |
diff --git a/README.md b/README.md
index 7ae9ba59..1d53f82a 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), 2006 automated tests. บันทึกบนคลาวด์และการแก้พร้อมกันหลายคนมีให้เลือกใช้ได้ โดยต่อ backend ของคุณเอง
+(Thai/English), 2009 automated tests. บันทึกบนคลาวด์และการแก้พร้อมกันหลายคนมีให้เลือกใช้ได้ โดยต่อ backend ของคุณเอง
---
@@ -106,7 +106,7 @@ top/bottom ranks, colour scales and data bars — and round-trips through Excel'
---
-### 🧪 สิ่งที่เทสต์ 2006 เคสจับไม่ได้
+### 🧪 สิ่งที่เทสต์ 2009 เคสจับไม่ได้
เทสต์ของผู้ช่วย 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 ที่มองเห็น เป็นตัวเลขผิดที่มองไม่เห็น**
-**แล้วมันก็เกิดอีก คนละที่** ตอนที่ทุกด่านเขียวหมด — 2006 เทสต์ผ่าน, `axe` ไม่มี violation ทั้งสองหน้า
+**แล้วมันก็เกิดอีก คนละที่** ตอนที่ทุกด่านเขียวหมด — 2009 เทสต์ผ่าน, `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 2006 เคสด้วย Vitest |
+| `npm test` | รัน unit test 2009 เคสด้วย Vitest |
| `npm run check:readme` | ตรวจว่า README ยังตรงกับโค้ด (ลิงก์/ภาพ/จำนวนเทสต์/โมดูลใหม่/สองภาษาตรงกัน) |
| `npm run check:screens` | ตัวเลขที่พิมพ์อยู่บนภาพหน้าจอยังตรงกับซอร์สไหม |
| `npm run check:rls` | ยิงจริงสองบัญชีบน Supabase ของคุณ ว่าฐานข้อมูลปฏิเสธตามที่ policy เขียนไว้ไหม (ต้องตั้ง env) |
@@ -2641,7 +2641,7 @@ stack ของ `font-mono` จึงต่อท้ายด้วย Plex Sans
| `@anthropic-ai/sdk` | เชื่อมต่อ Claude API สำหรับผู้ช่วย AI |
| `lucide-react` | ไอคอน UI |
| `clsx` | รวม className แบบมีเงื่อนไข |
-| `vitest` | unit test เอนจินคำนวณสูตร, ตรรกะเรียงข้อมูล, แปลง JSON เป็นตาราง, การแบ่งหน้า/rate limit, แม่แบบ/รูปแบบจากไฟล์ และบล็อกข้อมูลสด (2006 เคส) |
+| `vitest` | unit test เอนจินคำนวณสูตร, ตรรกะเรียงข้อมูล, แปลง JSON เป็นตาราง, การแบ่งหน้า/rate limit, แม่แบบ/รูปแบบจากไฟล์ และบล็อกข้อมูลสด (2009 เคส) |
> **หมายเหตุ:** ไม่ได้ใช้ไลบรารีคำนวณสูตรสำเร็จรูป (เช่น HyperFormula) แต่เขียน **เอนจินคำนวณสูตรขึ้นเอง**
> ทั้ง tokenizer, parser, evaluator และฟังก์ชันต่างๆ เพื่อควบคุมพฤติกรรมได้เต็มที่ ดูรายละเอียดที่หัวข้อ
@@ -3256,6 +3256,8 @@ error อยู่แล้ว คือ `IF` `IFERROR` `IFNA` `COUNT` `COUNTA`
**เวลาสะสมนับข้ามวันแบบ Excel (#219)** — รหัส `[h]` `[mm]` `[ss]` นับชั่วโมง นาที หรือวินาทีรวมทั้งหมด ไม่ใช่เวลาของวัน
1.5 วันในรูปแบบ `[h]:mm` ขึ้น `36:00` (เดิมขึ้น `12:00`) ใบลงเวลาที่รวมชั่วโมงด้วย `[h]:mm` จึงได้ยอดจริง ทั้งในตารางและใน `TEXT`
+· ค่า 0 กับค่าติดลบก็ขึ้นแบบ Excel (ลองใน Excel จริงแล้ว): 0 ในรูปแบบ `dd/mm/yyyy` ขึ้น `00/01/1900` (เดิมขึ้น `31/12/1899`
+ซึ่งเป็นวันที่ที่ Excel ไม่เคยแสดง) ค่าติดลบในรูปแบบวันที่หรือเวลาขึ้น `########` แทนวันที่ที่แต่งขึ้น และ `TEXT` ของค่าติดลบได้ `#VALUE!`
**ยังไม่รองรับ:** `MATCH` ต้องเป็นช่วงแถวเดียวหรือคอลัมน์เดียว ถ้าส่งช่วงสองมิติจะได้ `#N/A`
แทนการเดาตำแหน่งให้ · ยังไม่มี `MROUND` · การคำนวณที่ค่าล้น (`10^308*10`) ยังโชว์เป็น Infinity แทน `#NUM!`
@@ -3546,13 +3548,13 @@ B อ่านและแก้ได้ แต่ยังยึดเป็
## 🧪 การทดสอบ
```bash
-npm test # 2006 เคส ใน 144 ไฟล์ ด้วย Vitest
+npm test # 2009 เคส ใน 144 ไฟล์ ด้วย Vitest
```
โฟกัสเทสต์ไปที่ **เอนจินคำนวณสูตร, ตรรกะเรียงข้อมูล, การแปลง JSON เป็นตาราง, การไล่ดึงหน้าถัดไป, การถอยเมื่อโดน rate limit, แม่แบบจากไฟล์ Excel, กฎจัดรูปแบบตามเงื่อนไข และการวางบล็อกข้อมูลสด** — ส่วนที่เป็น pure function ล้วน ไม่ต้องพึ่ง React/DOM
จึงเทสต์ได้เร็วและมั่นใจได้สูง
-**แต่ 2006 เคสนั้นไม่มีสักเคสที่เปิดแอป** และบั๊กที่โปรเจกต์นี้เจอด้วยการนั่งกดเอง อยู่ที่รอยต่อระหว่างชิ้นส่วนที่ผ่านเทสต์
+**แต่ 2009 เคสนั้นไม่มีสักเคสที่เปิดแอป** และบั๊กที่โปรเจกต์นี้เจอด้วยการนั่งกดเอง อยู่ที่รอยต่อระหว่างชิ้นส่วนที่ผ่านเทสต์
ทุกตัวแทบทั้งหมด — ปุ่ม "+ แถว" บนแถบเครื่องมือเรียก `addRow` ที่ไม่ประกาศอะไรเลย ขณะที่ `insertRowAtSelection`
ข้าง ๆ ประกาศถูกต้อง (ทั้งคู่เทสต์ผ่าน) · ผู้ช่วย AI ส่งช่วงที่รวมหัวคอลัมน์ไปด้วย เพราะตัวประกอบ context อ่าน
`sheet.cells` ที่เป็นสูตรดิบแทนค่าที่คำนวณแล้ว (ทั้งคู่เทสต์ผ่าน) · ปุ่มใหม่หนึ่งปุ่มดันปุ่มสลับภาษาตกขอบจอไป 42px
@@ -3652,9 +3654,9 @@ seed ถูกปักไว้ ด่านที่แดงจึงแด
ในตารางแล้วสองข้อในflow ที่สามตกทันที (ครั้งแรกที่ลองปิดยังเขียวอยู่ เพราะ `case` ที่แทรกเข้าไปไปอยู่หลัง
`case "ArrowRight"` เดิม กลายเป็นโค้ดตาย — การพิสูจน์ต้องเช็กด้วยว่าของที่ตั้งใจทำให้พังนั้นพังจริง)
-> **2006 เคสผ่านหมด แต่ 43% ของคำตอบจากผู้ช่วย AI ใช้ไม่ได้** — เพราะเทสต์ส่วนนั้น mock ตัวโมเดลไว้
+> **2009 เคสผ่านหมด แต่ 43% ของคำตอบจากผู้ช่วย AI ใช้ไม่ได้** — เพราะเทสต์ส่วนนั้น mock ตัวโมเดลไว้
> มันจึงตอบตามที่คนเขียนเทสต์คิดว่ามันจะตอบ จำนวนเทสต์บอกได้แค่ว่าเราถามอะไรไปบ้าง ไม่ได้บอกว่าเราถาม
-> ครบหรือยัง เรื่องนี้เจอได้ก็ต่อเมื่อเอา API key จริงมายิง — ดู [สิ่งที่เทสต์ 2006 เคสจับไม่ได้](#-สิ่งที่เทสต์-2006-เคสจับไม่ได้)
+> ครบหรือยัง เรื่องนี้เจอได้ก็ต่อเมื่อเอา API key จริงมายิง — ดู [สิ่งที่เทสต์ 2009 เคสจับไม่ได้](#-สิ่งที่เทสต์-2009-เคสจับไม่ได้)
> และรันซ้ำเองได้ด้วย `npm run check:ai`
| ไฟล์ | เคส | ทดสอบอะไร |
@@ -3685,7 +3687,7 @@ seed ถูกปักไว้ ด่านที่แดงจึงแด
| `moveRefs.test.ts` | 7 | การเขียนอ้างอิงใหม่ตอนตัดแล้ววาง (#51): อ้างอิงเข้าก้อนตามไปพร้อม `$`, นอกก้อนและข้อความสูตรที่ไม่ถูกแตะคงเดิม, ช่วงย้ายเมื่อถูกตัดทั้งก้อนเท่านั้น, ข้ามชีตใส่ชื่อชีตให้, สูตรที่ถูกย้ายยังชี้ชีตเดิม, อ้างเซลล์ที่ถูกวางทับเป็น `#REF!`, ตัดใต้ตัวกรองย้ายเฉพาะแถวที่ตัด |
| `sheetSort.test.ts` | 13 | ฮิวริสติกตรวจจับขอบเขต+หัวตาราง และการเรียงลำดับ (รวมกรณีค่าว่าง, จำกัดคอลัมน์ที่ย้าย) · หัวตารางอยู่บนทั้งสองทิศ (#49): ตัวเลขใต้คำในคอลัมน์ไหนก็ได้, ป้ายเหนือคอลัมน์ว่าง, ตัวหนา, แถวที่มีตัวเลขไม่ใช่หัว, รายการคำล้วนเรียงทั้งหมด |
| `jsonToTable.test.ts` | 14 | หา array ของ record ในการตอบกลับ, แตก object ซ้อนเป็นคอลัมน์, ตรวจจับคอลัมน์ตัวเลข, KPI object แถวเดียว, รายการว่างในซองได้ 0 แถว ไม่ใช่แถวของซอง (#65), CSV เก็บรหัสที่ขึ้นต้นด้วยศูนย์เป็นข้อความจนถึงตาราง (#36) |
-| `excelDate.test.ts` | 17 | วันที่แบบ Excel (#45): serial ระบบ 1900 ตรงกับ Excel รวม 29 ก.พ. 1900 ที่ Excel ถือว่ามี, รูปแบบ ISO ที่อ่านเป็นวันที่ (รูปอื่นเป็นข้อความ), รหัสรูปแบบวันที่ของ Excel (`dd/mm/yyyy`, `d-mmm-yy`, `h:mm AM/PM`), เวลาสะสมเกินวัน (`[h]:mm` ของ 1.5 วันเป็น `36:00`, #219) · วันที่แบบ พ.ศ. (#82): ทุกรูปใน issue เป็น 15 ม.ค. 2026, เดือนไทยครบ 12 เดือนทั้งเต็ม/ย่อ/ไม่มีจุด, ปี 2 หลักคู่เดือนไทย, สิ่งที่ต้องไม่เดา (`2569`, `15/01/69`, `15/01/2024`), รหัสปฏิทินพุทธทุกแบบ + `bbbb` + ชื่อเดือนไทย |
+| `excelDate.test.ts` | 19 | วันที่แบบ Excel (#45): serial ระบบ 1900 ตรงกับ Excel รวม 29 ก.พ. 1900 ที่ Excel ถือว่ามี, รูปแบบ ISO ที่อ่านเป็นวันที่ (รูปอื่นเป็นข้อความ), รหัสรูปแบบวันที่ของ Excel (`dd/mm/yyyy`, `d-mmm-yy`, `h:mm AM/PM`), เวลาสะสมเกินวัน (`[h]:mm` ของ 1.5 วันเป็น `36:00`, #219), 0 เป็น `00/01/1900` และค่าติดลบเป็น `########` ในตาราง (#219) · วันที่แบบ พ.ศ. (#82): ทุกรูปใน issue เป็น 15 ม.ค. 2026, เดือนไทยครบ 12 เดือนทั้งเต็ม/ย่อ/ไม่มีจุด, ปี 2 หลักคู่เดือนไทย, สิ่งที่ต้องไม่เดา (`2569`, `15/01/69`, `15/01/2024`), รหัสปฏิทินพุทธทุกแบบ + `bbbb` + ชื่อเดือนไทย |
| `dateConvert.test.ts` | 6 | "แปลงเป็นวันที่" (#82): ปี 2 หลัก พ.ศ. = 25yy, ค.ศ. ตามหน้าต่าง Excel, เดือน/วัน/ปี และปีก่อน, วันที่ที่อ่านได้อยู่แล้วเขียนเป็น ISO, สิ่งที่อ่านไม่ออกไม่ถูกแตะ, แผนของช่วงที่ไม่รวมช่องว่างและสูตร |
| `numberFormatCode.test.ts` | 6 | รหัสรูปแบบตัวเลขแบบ Excel (#53): ตารางจาก issue (7%, 12.5%, $1,234.50, 1.235, 12,345), `0.00"%"` แบบเดิมไม่ ×100, ส่วนติดลบ/ศูนย์, แท็กสกุลเงิน, จุลภาคหารพัน, ตัวอักษรคั่นกลางเลข และ fallback ของเศษส่วน/เลขวิทยาศาสตร์ |
| `numberFormats.test.ts` | 5 | เปอร์เซ็นต์/สกุลเงิน/ทศนิยมผ่าน .xlsx จริงทั้งไป-กลับ (#53): รหัสของไฟล์แสดงและส่งออกกลับตามเดิม, เปอร์เซ็นต์ของแอป ×100 และส่งออกเป็น `0.00%`, ชีตเก่าในเบราว์เซอร์และบนคลาวด์ (format 1) ยังขึ้น 50.00% |
diff --git a/src/app/opengraph-image.tsx b/src/app/opengraph-image.tsx
index 7e459e45..e1441b39 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"],
- ["2006", "automated tests"],
+ ["2009", "automated tests"],
["325", "of them security"],
["0", "formula libraries"],
];
diff --git a/src/i18n/en.ts b/src/i18n/en.ts
index 6c808e9c..0436f8b2 100644
--- a/src/i18n/en.ts
+++ b/src/i18n/en.ts
@@ -432,7 +432,7 @@ export const en: Messages = {
stats: [
{ value: "37", label: "ready-made formulas" },
{ value: "66", label: "engine functions" },
- { value: "2006", label: "automated tests" },
+ { value: "2009", label: "automated tests" },
{ value: "325", label: "security tests" },
{ value: "0", label: "formula libraries used" },
],
diff --git a/src/i18n/th.ts b/src/i18n/th.ts
index 0902d4e5..135ca8f0 100644
--- a/src/i18n/th.ts
+++ b/src/i18n/th.ts
@@ -432,7 +432,7 @@ export const th: Messages = {
stats: [
{ value: "37", label: "สูตรพร้อมใช้" },
{ value: "66", label: "ฟังก์ชันในเอนจิน" },
- { value: "2006", label: "เทสต์อัตโนมัติ" },
+ { value: "2009", label: "เทสต์อัตโนมัติ" },
{ value: "325", label: "เทสต์ด้านความปลอดภัย" },
{ value: "0", label: "ไลบรารีคำนวณสูตร" },
],
diff --git a/src/lib/cellFormat.ts b/src/lib/cellFormat.ts
index 0db866fa..27d4b896 100644
--- a/src/lib/cellFormat.ts
+++ b/src/lib/cellFormat.ts
@@ -1,7 +1,7 @@
// Copyright 2026 Suruch Chakrapeesirisuk
// SPDX-License-Identifier: Apache-2.0
-import { BE_DATE_CODE, DEFAULT_DATE_CODE, formatSerial, isDateFormatCode, kindOfDateCode } from "./excelDate";
+import { BE_DATE_CODE, DEFAULT_DATE_CODE, formatSerial, isDateFormatCode, kindOfDateCode, NOT_A_DATE, showableAsDate } from "./excelDate";
import { formatNumberCode, isPercentCode } from "./numberFormatCode";
/**
@@ -90,7 +90,7 @@ export function pxToPt(px: number | undefined): number | undefined {
* (`numCode`) wins over the preset, so `0%` and `"$"#,##0.00` show as the file has them.
*/
export function formatNumberForDisplay(value: number, fmt: NumberFormat, dateCode?: string, numCode?: string): string {
- if (isDateFormat(fmt)) return formatSerial(value, dateCode ?? DEFAULT_DATE_CODE[fmt]);
+ if (isDateFormat(fmt)) return showableAsDate(value) ? formatSerial(value, dateCode ?? DEFAULT_DATE_CODE[fmt]) : NOT_A_DATE;
if (numCode) return formatNumberCode(value, numCode);
const fixed2 = (n: number) => n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
switch (fmt) {
diff --git a/src/lib/excelDate.test.ts b/src/lib/excelDate.test.ts
index 857dead0..7d731c64 100644
--- a/src/lib/excelDate.test.ts
+++ b/src/lib/excelDate.test.ts
@@ -3,6 +3,7 @@
import { describe, expect, it } from "vitest";
import { dateLiteral, formatSerial, isDateFormatCode, isoFromSerial, partsOfSerial, serialOf } from "./excelDate";
+import { formatNumberForDisplay } from "./cellFormat";
/**
* Dates as Excel keeps them (#45): a serial number of days, 1900 system, with the 29 February 1900
@@ -158,3 +159,17 @@ describe("a Buddhist-Era format shows the year Thai Excel does (#82)", () => {
for (const code of ["[$-107041E]d/m/yyyy;@", "dd/mm/bbbb", "[$-th-TH,107]d/m/yyyy"]) expect(isDateFormatCode(code), code).toBe(true);
});
});
+
+// Checked in Excel (#219): a cell holding 0 under dd/mm/yyyy shows 00/01/1900, and one holding a
+// negative number under a date or [h]:mm code is filled with #. It used to show 31/12/1899 and
+// 30/12/1899, dates Excel never shows.
+describe("zero and negative values under a date or time format (#219)", () => {
+ it("shows 0 as 00/01/1900 in the grid", () => {
+ expect(formatNumberForDisplay(0, "date", "dd/mm/yyyy")).toBe("00/01/1900");
+ });
+
+ it("fills a negative value with # in the grid instead of inventing a date", () => {
+ expect(formatNumberForDisplay(-1, "date", "dd/mm/yyyy")).toBe("########");
+ expect(formatNumberForDisplay(-1.5, "time", "[h]:mm")).toBe("########");
+ });
+});
diff --git a/src/lib/excelDate.ts b/src/lib/excelDate.ts
index 5845f9d6..724de5d2 100644
--- a/src/lib/excelDate.ts
+++ b/src/lib/excelDate.ts
@@ -302,6 +302,17 @@ function tokenizeWithLocale(code: string): { tokens: Token[]; locale: DateLocale
return { tokens: out, locale };
}
+/**
+ * Whether a serial can be shown under a date or time code. A negative one cannot (#219, checked in
+ * Excel): the grid fills the cell with `#` and `TEXT` gives `#VALUE!`, under the 1900 date system.
+ */
+export function showableAsDate(serial: number): boolean {
+ return Number.isFinite(serial) && serial >= 0;
+}
+
+/** What a cell shows for a value that cannot be a date, as Excel fills it at its default width. */
+export const NOT_A_DATE = "########";
+
/**
* A serial shown the way an Excel format code says: `dd/mm/yyyy`, `d-mmm-yy`, `h:mm AM/PM`.
*
@@ -312,7 +323,11 @@ function tokenizeWithLocale(code: string): { tokens: Token[]; locale: DateLocale
* year in any code (#82). Other locale tags (`[$-409]`) are read past.
*/
export function formatSerial(serial: number, code: string): string {
- const p = partsOfSerial(serial);
+ // Serial 0 is Excel's 0 January 1900 (`00/01/1900`), not the day before 1 January (#219,
+ // checked in Excel). Only the display moves: `partsOfSerial` still answers 31 December 1899 for
+ // the date functions and for the ISO text a cell keeps, which have no day 0 to write.
+ const zero = Math.floor(Math.round(serial * 86_400) / 86_400) === 0;
+ const p = zero ? { ...partsOfSerial(serial), year: 1900, month: 1, day: 0 } : partsOfSerial(serial);
const { tokens, locale } = tokenizeWithLocale(code);
const months = locale.thai ? THAI_MONTHS : MONTHS;
const shortMonth = (m: number) => (locale.thai ? THAI_MONTHS_SHORT[m - 1] : MONTHS[m - 1].slice(0, 3));
diff --git a/src/lib/formulaEngine/functions.test.ts b/src/lib/formulaEngine/functions.test.ts
index fddf6cf0..b951b038 100644
--- a/src/lib/formulaEngine/functions.test.ts
+++ b/src/lib/formulaEngine/functions.test.ts
@@ -427,6 +427,16 @@ describe("TEXT writes the value the way its format says, as Excel does (#29)", (
expect(calc('TEXT(0.1,"[mm]:ss")')).toBe("144:00");
expect(calc('TEXT(0.5,"[s]")')).toBe("43200");
});
+
+ // Checked in Excel (#219): serial 0 is 0 January 1900, and a negative serial is no date at all.
+ it("shows serial 0 as Excel's 00/01/1900 and refuses a negative one with #VALUE!", () => {
+ expect(calc('TEXT(0,"dd/mm/yyyy")')).toBe("00/01/1900");
+ for (const f of ['TEXT(-1,"dd/mm/yyyy")', 'TEXT(-0.5,"hh:mm")', 'TEXT(-1,"[h]:mm")']) {
+ expect(String(calc(f)), f).toBe("#VALUE!");
+ }
+ // A negative number under a number code is still just a number.
+ expect(calc('TEXT(-1,"0.0")')).toBe("-1.0");
+ });
});
describe("the *IF(S) functions pass on an error they read, as Excel does (#96)", () => {
diff --git a/src/lib/formulaEngine/functions.ts b/src/lib/formulaEngine/functions.ts
index c6f83651..d456e5e4 100644
--- a/src/lib/formulaEngine/functions.ts
+++ b/src/lib/formulaEngine/functions.ts
@@ -3,7 +3,7 @@
import { EvalResult, FormulaError, FormulaValue, flattenResult, isError, ERR_DIV0, ERR_NA, ERR_NUM, ERR_VALUE } from "./types";
import { toBoolean, toDisplayString, toNumber, isBlank } from "./coerce";
-import { dateLiteral, formatSerial, isDateFormatCode, partsOfSerial, serialOf } from "../excelDate";
+import { dateLiteral, formatSerial, isDateFormatCode, partsOfSerial, serialOf, showableAsDate } from "../excelDate";
import { formatNumberCode } from "../numberFormatCode";
/** Whether an argument was read from cells — a range, or one cell by reference (#166). */
@@ -673,7 +673,9 @@ export const FUNCTIONS: Record = {
const n = isBlank(v) ? 0 : toNumber(v);
if (isError(n)) return toDisplayString(v);
if (code === "") return "";
- return isDateFormatCode(code) ? formatSerial(n, code) : formatNumberCode(n, code);
+ if (!isDateFormatCode(code)) return formatNumberCode(n, code);
+ // A negative serial is no date at all, and Excel says so rather than inventing one (#219).
+ return showableAsDate(n) ? formatSerial(n, code) : ERR_VALUE;
},
// Both are serials (#45), in the reader's own time zone: NOW used to be UTC while TODAY was local,
// so in Bangkok between midnight and 7am the two disagreed about the date.
From d1e06c94295e0efbe2a256e4bc8481de6fdd946d Mon Sep 17 00:00:00 2001
From: Claude
Date: Mon, 5 Oct 2026 03:16:49 +0000
Subject: [PATCH 2/2] Recount to 2010 tests; retake picture 27 and the social
card
Co-Authored-By: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_013pmWZsbmugfpqtYazrzfjj
---
README.en.md | 20 ++++++++++----------
README.md | 20 ++++++++++----------
public/screenshots/27-landing-stats.png | Bin 64257 -> 63973 bytes
public/screenshots/en/27-landing-stats.png | Bin 61713 -> 61456 bytes
public/screenshots/screenshots.json | 4 ++--
public/social-preview.png | Bin 109746 -> 109940 bytes
src/app/opengraph-image.tsx | 2 +-
src/i18n/en.ts | 2 +-
src/i18n/th.ts | 2 +-
9 files changed, 25 insertions(+), 25 deletions(-)
diff --git a/README.en.md b/README.en.md
index 8df65599..61863ba7 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), 2009 automated tests.
+an undo that does not erase the other person's work. Bilingual UI (Thai/English), 2010 automated tests.
---
@@ -109,7 +109,7 @@ Want the harder parts: [embedding a Thai font in the PDF, with stacked tone mark
---
-### 🧪 What 2009 passing tests could not catch
+### 🧪 What 2010 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 — 2009 tests, `axe` clean on
+**And it happened again, in a different place.** With every gate green — 2010 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 2009-case Vitest suite |
+| `npm test` | Run the 2010-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) |
@@ -3037,7 +3037,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 (2009 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 (2010 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
@@ -4092,13 +4092,13 @@ the framework bundle itself, which isn't a trade worth making here. Written down
## 🧪 Testing
```bash
-npm test # 2009 cases across 144 files, via Vitest
+npm test # 2010 cases across 144 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 2009 cases opens the app**, and nearly every bug this project found by hand lived in
+**But not one of those 2010 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`
@@ -4217,10 +4217,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.)
-> **2009 tests passed, and 43% of the assistant's answers were unusable** — because those tests mock
+> **2010 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 2009 passing tests could not catch](#-what-2009-passing-tests-could-not-catch), repeatable
+> [What 2010 passing tests could not catch](#-what-2010-passing-tests-could-not-catch), repeatable
> with `npm run check:ai`.
| File | Cases | Tests |
diff --git a/README.md b/README.md
index 978009f8..2c390668 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), 2009 automated tests. บันทึกบนคลาวด์และการแก้พร้อมกันหลายคนมีให้เลือกใช้ได้ โดยต่อ backend ของคุณเอง
+(Thai/English), 2010 automated tests. บันทึกบนคลาวด์และการแก้พร้อมกันหลายคนมีให้เลือกใช้ได้ โดยต่อ backend ของคุณเอง
---
@@ -106,7 +106,7 @@ top/bottom ranks, colour scales and data bars — and round-trips through Excel'
---
-### 🧪 สิ่งที่เทสต์ 2009 เคสจับไม่ได้
+### 🧪 สิ่งที่เทสต์ 2010 เคสจับไม่ได้
เทสต์ของผู้ช่วย 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 ที่มองเห็น เป็นตัวเลขผิดที่มองไม่เห็น**
-**แล้วมันก็เกิดอีก คนละที่** ตอนที่ทุกด่านเขียวหมด — 2009 เทสต์ผ่าน, `axe` ไม่มี violation ทั้งสองหน้า
+**แล้วมันก็เกิดอีก คนละที่** ตอนที่ทุกด่านเขียวหมด — 2010 เทสต์ผ่าน, `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 2009 เคสด้วย Vitest |
+| `npm test` | รัน unit test 2010 เคสด้วย Vitest |
| `npm run check:readme` | ตรวจว่า README ยังตรงกับโค้ด (ลิงก์/ภาพ/จำนวนเทสต์/โมดูลใหม่/สองภาษาตรงกัน) |
| `npm run check:screens` | ตัวเลขที่พิมพ์อยู่บนภาพหน้าจอยังตรงกับซอร์สไหม |
| `npm run check:rls` | ยิงจริงสองบัญชีบน Supabase ของคุณ ว่าฐานข้อมูลปฏิเสธตามที่ policy เขียนไว้ไหม (ต้องตั้ง env) |
@@ -2641,7 +2641,7 @@ stack ของ `font-mono` จึงต่อท้ายด้วย Plex Sans
| `@anthropic-ai/sdk` | เชื่อมต่อ Claude API สำหรับผู้ช่วย AI |
| `lucide-react` | ไอคอน UI |
| `clsx` | รวม className แบบมีเงื่อนไข |
-| `vitest` | unit test เอนจินคำนวณสูตร, ตรรกะเรียงข้อมูล, แปลง JSON เป็นตาราง, การแบ่งหน้า/rate limit, แม่แบบ/รูปแบบจากไฟล์ และบล็อกข้อมูลสด (2009 เคส) |
+| `vitest` | unit test เอนจินคำนวณสูตร, ตรรกะเรียงข้อมูล, แปลง JSON เป็นตาราง, การแบ่งหน้า/rate limit, แม่แบบ/รูปแบบจากไฟล์ และบล็อกข้อมูลสด (2010 เคส) |
> **หมายเหตุ:** ไม่ได้ใช้ไลบรารีคำนวณสูตรสำเร็จรูป (เช่น HyperFormula) แต่เขียน **เอนจินคำนวณสูตรขึ้นเอง**
> ทั้ง tokenizer, parser, evaluator และฟังก์ชันต่างๆ เพื่อควบคุมพฤติกรรมได้เต็มที่ ดูรายละเอียดที่หัวข้อ
@@ -3550,13 +3550,13 @@ B อ่านและแก้ได้ แต่ยังยึดเป็
## 🧪 การทดสอบ
```bash
-npm test # 2009 เคส ใน 144 ไฟล์ ด้วย Vitest
+npm test # 2010 เคส ใน 144 ไฟล์ ด้วย Vitest
```
โฟกัสเทสต์ไปที่ **เอนจินคำนวณสูตร, ตรรกะเรียงข้อมูล, การแปลง JSON เป็นตาราง, การไล่ดึงหน้าถัดไป, การถอยเมื่อโดน rate limit, แม่แบบจากไฟล์ Excel, กฎจัดรูปแบบตามเงื่อนไข และการวางบล็อกข้อมูลสด** — ส่วนที่เป็น pure function ล้วน ไม่ต้องพึ่ง React/DOM
จึงเทสต์ได้เร็วและมั่นใจได้สูง
-**แต่ 2009 เคสนั้นไม่มีสักเคสที่เปิดแอป** และบั๊กที่โปรเจกต์นี้เจอด้วยการนั่งกดเอง อยู่ที่รอยต่อระหว่างชิ้นส่วนที่ผ่านเทสต์
+**แต่ 2010 เคสนั้นไม่มีสักเคสที่เปิดแอป** และบั๊กที่โปรเจกต์นี้เจอด้วยการนั่งกดเอง อยู่ที่รอยต่อระหว่างชิ้นส่วนที่ผ่านเทสต์
ทุกตัวแทบทั้งหมด — ปุ่ม "+ แถว" บนแถบเครื่องมือเรียก `addRow` ที่ไม่ประกาศอะไรเลย ขณะที่ `insertRowAtSelection`
ข้าง ๆ ประกาศถูกต้อง (ทั้งคู่เทสต์ผ่าน) · ผู้ช่วย AI ส่งช่วงที่รวมหัวคอลัมน์ไปด้วย เพราะตัวประกอบ context อ่าน
`sheet.cells` ที่เป็นสูตรดิบแทนค่าที่คำนวณแล้ว (ทั้งคู่เทสต์ผ่าน) · ปุ่มใหม่หนึ่งปุ่มดันปุ่มสลับภาษาตกขอบจอไป 42px
@@ -3656,9 +3656,9 @@ seed ถูกปักไว้ ด่านที่แดงจึงแด
ในตารางแล้วสองข้อในflow ที่สามตกทันที (ครั้งแรกที่ลองปิดยังเขียวอยู่ เพราะ `case` ที่แทรกเข้าไปไปอยู่หลัง
`case "ArrowRight"` เดิม กลายเป็นโค้ดตาย — การพิสูจน์ต้องเช็กด้วยว่าของที่ตั้งใจทำให้พังนั้นพังจริง)
-> **2009 เคสผ่านหมด แต่ 43% ของคำตอบจากผู้ช่วย AI ใช้ไม่ได้** — เพราะเทสต์ส่วนนั้น mock ตัวโมเดลไว้
+> **2010 เคสผ่านหมด แต่ 43% ของคำตอบจากผู้ช่วย AI ใช้ไม่ได้** — เพราะเทสต์ส่วนนั้น mock ตัวโมเดลไว้
> มันจึงตอบตามที่คนเขียนเทสต์คิดว่ามันจะตอบ จำนวนเทสต์บอกได้แค่ว่าเราถามอะไรไปบ้าง ไม่ได้บอกว่าเราถาม
-> ครบหรือยัง เรื่องนี้เจอได้ก็ต่อเมื่อเอา API key จริงมายิง — ดู [สิ่งที่เทสต์ 2009 เคสจับไม่ได้](#-สิ่งที่เทสต์-2009-เคสจับไม่ได้)
+> ครบหรือยัง เรื่องนี้เจอได้ก็ต่อเมื่อเอา API key จริงมายิง — ดู [สิ่งที่เทสต์ 2010 เคสจับไม่ได้](#-สิ่งที่เทสต์-2010-เคสจับไม่ได้)
> และรันซ้ำเองได้ด้วย `npm run check:ai`
| ไฟล์ | เคส | ทดสอบอะไร |
diff --git a/public/screenshots/27-landing-stats.png b/public/screenshots/27-landing-stats.png
index 79e0614ed4bfa6f3eba363e0a58b060bd08b083a..706e20e0907fb7f95b35127dbb5e675260b4674e 100644
GIT binary patch
literal 63973
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