diff --git a/CHANGELOG.md b/CHANGELOG.md index cef3200..954259e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -76,6 +76,11 @@ Versions follow [Semantic Versioning](https://semver.org/) — minor bump per su - A bare `INPUT "prompt" TO ` typed at the REPL silently discarded the value: the submitted form was only applied when a continuation existed, which is never the case for a single statement. Values a form collects are now always stored. (#50) +- The `BROWSE` cell-validation message was invisible. Grid cells set `overflow: hidden` + (for the ellipsis on long values), which clipped the absolutely-positioned error tooltip + away entirely — an invalid edit showed a red border and no reason. The e2e tests missed + it because `toContainText`/`toBeVisible` do not account for clipping by an ancestor's + overflow; the assertions now use `toBeInViewport()`, which does. (#46) ### Changed - Removed the `input-request` / `input-response` WebSocket message types. They were declared diff --git a/CLAUDE.md b/CLAUDE.md index 11b068d..ca37288 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -362,6 +362,11 @@ spots, not bad luck. When adding tests, remember what the existing ones cannot s write the test that uses two. - **Prefer failing loudly to guessing.** The parser used to absorb any token it didn't understand and invent a column from it. `CREATE TABLE` is now strict; keep it that way. +- **`toContainText` / `toBeVisible` do not see clipping.** The BROWSE validation message was + present, styled `visible`, and clipped to nothing by the cell's `overflow: hidden` — users + saw a red border and no reason, while the tests passed. Assert `toBeInViewport()` (backed by + an IntersectionObserver, so it accounts for ancestor clipping) for anything that must be + *readable*, and look at a screenshot of a UI change before believing it works. Run `npm run coverage` when touching an area you suspect is untested. **Never run `npm test` and `npx playwright test` concurrently** — both mutate `data/` and `data/system.sqlite3`, and diff --git a/README.md b/README.md index eca1685..bc951ad 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,16 @@ Wizards open in the main area with a live W3Script preview. --- +### BROWSE — per-cell validation + +Grid edits are checked against the column's declared type before they commit. An invalid +value keeps the cell in edit mode, outlined in red, and says why; the error clears as soon +as you fix it, and `Esc` abandons the edit. Here a `TIME(15)` column rejects `08:07`. + +![BROWSE rejecting an invalid TIME(15) cell edit](docs/screenshots/screenshot-grid-validation.png) + +--- + ### Aggregate commands & dBASE III parity `SUM`, `AVERAGE`, `? ROUND(…)`, `? MAX(…)`, and `SORT ON … TO` — numeric aggregates and sorted copies, honouring the active filter. diff --git a/docs/screenshots/demo.gif b/docs/screenshots/demo.gif index 94c7c75..7f0dd96 100644 Binary files a/docs/screenshots/demo.gif and b/docs/screenshots/demo.gif differ diff --git a/docs/screenshots/screenshot-assistant-wizard.png b/docs/screenshots/screenshot-assistant-wizard.png index 50bf4d6..0edd42e 100644 Binary files a/docs/screenshots/screenshot-assistant-wizard.png and b/docs/screenshots/screenshot-assistant-wizard.png differ diff --git a/docs/screenshots/screenshot-assistant.png b/docs/screenshots/screenshot-assistant.png index 2569418..52d8c26 100644 Binary files a/docs/screenshots/screenshot-assistant.png and b/docs/screenshots/screenshot-assistant.png differ diff --git a/docs/screenshots/screenshot-csv.png b/docs/screenshots/screenshot-csv.png index c6baf23..25fa95e 100644 Binary files a/docs/screenshots/screenshot-csv.png and b/docs/screenshots/screenshot-csv.png differ diff --git a/docs/screenshots/screenshot-grid-validation.png b/docs/screenshots/screenshot-grid-validation.png new file mode 100644 index 0000000..11af91e Binary files /dev/null and b/docs/screenshots/screenshot-grid-validation.png differ diff --git a/docs/screenshots/screenshot-index.png b/docs/screenshots/screenshot-index.png index 8409764..8884642 100644 Binary files a/docs/screenshots/screenshot-index.png and b/docs/screenshots/screenshot-index.png differ diff --git a/docs/screenshots/screenshot-list.png b/docs/screenshots/screenshot-list.png index 6354e2d..c635a5b 100644 Binary files a/docs/screenshots/screenshot-list.png and b/docs/screenshots/screenshot-list.png differ diff --git a/docs/screenshots/screenshot-parity.png b/docs/screenshots/screenshot-parity.png index 334d077..4ade517 100644 Binary files a/docs/screenshots/screenshot-parity.png and b/docs/screenshots/screenshot-parity.png differ diff --git a/docs/screenshots/screenshot-repl-session.png b/docs/screenshots/screenshot-repl-session.png index 5a4f4c1..f8bb091 100644 Binary files a/docs/screenshots/screenshot-repl-session.png and b/docs/screenshots/screenshot-repl-session.png differ diff --git a/docs/screenshots/screenshot-terminal.png b/docs/screenshots/screenshot-terminal.png index be3e18f..124140a 100644 Binary files a/docs/screenshots/screenshot-terminal.png and b/docs/screenshots/screenshot-terminal.png differ diff --git a/scripts/capture-screenshots.mjs b/scripts/capture-screenshots.mjs index 4ba2023..875809b 100644 --- a/scripts/capture-screenshots.mjs +++ b/scripts/capture-screenshots.mjs @@ -270,5 +270,30 @@ console.log('13. NEW: COPY TO CSV output'); await page.close(); } +// ── 14. screenshot-grid-validation.png (NEW in v1.2.0) ──────────────────── +console.log('14. NEW: BROWSE per-cell validation rejecting a TIME(15) edit'); +{ + const page = await newPage(); + await boot(page); + await cmd(page, 'USE DATABASE screenshotdb', 800); + await cmd(page, 'DROP TABLE shifts', 400); + await cmd(page, 'CREATE TABLE shifts (PERSON CHAR(20), STARTTIME TIME(15), DUE DATE)', 700); + await cmd(page, 'USE shifts', 500); + await cmd(page, 'APPEND RECORD', 500); + await cmd(page, 'REPLACE PERSON WITH "Ada Lovelace", STARTTIME WITH "08:15"', 700); + await cmd(page, 'BROWSE', 1200); + await page.waitForSelector('#grid-view:not(.hidden)', { timeout: 8000 }); + + // Type an off-quarter time into the TIME(15) column and let the grid reject it. + const td = page.locator('#grid-tbody td[data-ri="0"][data-ci="1"]'); + await td.dblclick(); + await td.locator('input.cell-ed').fill('08:07'); + await page.keyboard.press('Enter'); + await page.waitForSelector('#grid-tbody td.cell-invalid .cell-error', { timeout: 5000 }); + await page.waitForTimeout(300); + await snap(page, 'screenshot-grid-validation.png'); + await page.close(); +} + await browser.close(); console.log('\nDone. All screenshots written to docs/screenshots/'); diff --git a/src/styles/main.css b/src/styles/main.css index be6181f..ebd78b4 100644 --- a/src/styles/main.css +++ b/src/styles/main.css @@ -248,7 +248,10 @@ html, body { color: #ffffff; font-family: var(--font); font-size: 13px; outline: none; } -/* A rejected edit (#45): the cell stays in edit mode and explains why. */ +/* A rejected edit (#45): the cell stays in edit mode and explains why. + Cells clip their content (`overflow: hidden` above, for ellipsis), which would + swallow the message entirely — the user would see only a red border. */ +#grid-table td.cell-invalid { overflow: visible; } #grid-table td.cell-invalid input.cell-ed { border-color: #cc0000; background: #2a0000; } #grid-table td.cell-invalid .cell-error { position: absolute; left: 0; top: 100%; z-index: 20; diff --git a/tests/grid-validation.spec.ts b/tests/grid-validation.spec.ts index 1e077b9..b59d915 100644 --- a/tests/grid-validation.spec.ts +++ b/tests/grid-validation.spec.ts @@ -37,6 +37,11 @@ test.describe('BROWSE cell validation', () => { await page.keyboard.press('Enter'); await expect(td).toHaveClass(/cell-invalid/); await expect(td.locator('.cell-error')).toContainText('HH:MM'); + // The cell has `overflow: hidden`, so the message can be present, styled + // visible, and still clipped away from the user. toBeInViewport uses an + // IntersectionObserver and therefore accounts for ancestor clipping; + // toContainText / toBeVisible do not. + await expect(td.locator('.cell-error')).toBeInViewport(); await expect(td.locator('input.cell-ed')).toBeVisible(); // still editing // Off-granularity time — rejected for a different reason.