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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ Versions follow [Semantic Versioning](https://semver.org/) — minor bump per su
- A bare `INPUT "prompt" TO <var>` 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
Expand Down
5 changes: 5 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Binary file modified docs/screenshots/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/screenshot-assistant-wizard.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/screenshot-assistant.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/screenshot-csv.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshots/screenshot-grid-validation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/screenshot-index.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/screenshot-list.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/screenshot-parity.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/screenshot-repl-session.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/screenshot-terminal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions scripts/capture-screenshots.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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/');
5 changes: 4 additions & 1 deletion src/styles/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions tests/grid-validation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading