f7d2214c7a
The fourth widget reported, and the one with a different answer: there is no wire vocabulary for drill. reduce.rs contains no Drill variant at all, neither Command nor ModelCommand. That is improvise-gd28, and it is a decision rather than an oversight — drilling writes drill_state, which gotchas 30 and 1 rule session-local, and a Command::Model is routed with no SessionId. Adding ModelCommand::Drill would reintroduce exactly what improvise-3x3u removed. Everything below the command works, verified in the browser: CellId::Aggregate is emitted and received, editable answers false for it, and edit_or_drill reaches the aggregate arm. The TUI is the control and passes 12 drill tests, so this is isolated to the client/wire. The finding gd28 did not record is that the two entry points fail DIFFERENTLY. `i`/`a` declines out loud with a status. `>` — the key the hint line actually advertises as ">:drill" — is a name resolve::step does not handle, so it goes KeyOutcome::Upstream -> ClientFrame::Key -> Host::handle's empty vec: a black hole, not a slow path (gotcha 47). Silent. That asymmetry is why this reads as broken rather than refused. These tests document what is true rather than leaving folklore. They MAKE an aggregate through the tile bar (web/demo.improv has none by construction — gotcha 46) and assert the precondition, so they cannot pass against a grid of Records. Both proved by injection: silencing the aggregate arm fails both, the second on its check_the_checker control; making `>` resolve fails only the second. Evidence added to improvise-gd28 rather than filing a duplicate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
77 lines
3.6 KiB
JavaScript
77 lines
3.6 KiB
JavaScript
// Drill in the browser: what it does today, which is refuse. improvise-gd28
|
|
// says why, and context/test-evidence/clients.md records the layer it stops at.
|
|
|
|
import { test, expect } from "@playwright/test";
|
|
|
|
/// `web/demo.improv` has NO aggregate cell by construction — gotcha 46 puts
|
|
/// exactly one category on each visible axis so every cell is a Record, and
|
|
/// `tests/web_demo_model.rs` asserts it. So a drill test must MAKE one first.
|
|
async function anAggregateGrid(page) {
|
|
await page.goto("/app/index.html");
|
|
await expect(page.locator("#grid table")).toBeVisible({ timeout: 20_000 });
|
|
await page.locator("#grid").focus();
|
|
await expect(page.locator("#grid td.editable").first()).toBeVisible();
|
|
|
|
// Quarter is on Page; one cycle takes it to None, so every cell aggregates
|
|
// over the four quarters. The tile bar is how a user would do it.
|
|
await page.keyboard.press("Shift+T");
|
|
const tiles = page.locator("#tiles .tile");
|
|
const quarter = tiles.filter({ hasText: "Quarter" });
|
|
await expect(quarter).toHaveText("Quarter Pag");
|
|
const names = await tiles.allTextContents();
|
|
for (let i = 0; i < names.findIndex((t) => t.startsWith("Quarter")); i++) {
|
|
await page.keyboard.press("ArrowRight");
|
|
}
|
|
await expect(quarter).toHaveClass(/selected/);
|
|
await page.keyboard.press("Enter");
|
|
await expect(quarter).toHaveText("Quarter ·", { timeout: 10_000 });
|
|
await page.keyboard.press("Escape");
|
|
|
|
// The precondition, asserted rather than assumed: nothing is editable now,
|
|
// which is `CellId::Aggregate` reaching `render.rs::editable` (ADR 002).
|
|
await expect(page.locator("#grid td.editable")).toHaveCount(0);
|
|
await expect(page.locator("#grid tbody td").first()).toBeVisible();
|
|
}
|
|
|
|
test.describe("drill is refused in the browser, not broken", () => {
|
|
// The honest half: `i`/`a` on an aggregate declines OUT LOUD, because
|
|
// `edit_or_drill` answers the `CellId::Aggregate` arm with a status.
|
|
test("editing an aggregate says drill needs the host", async ({ page }) => {
|
|
await anAggregateGrid(page);
|
|
|
|
await page.keyboard.press("i");
|
|
await expect(page.locator("#status")).toContainText(
|
|
"drilling into an aggregate needs the host",
|
|
);
|
|
// And it really is a refusal: no editor opened, no mode change.
|
|
await expect(page.locator("#mode")).toHaveText("NORMAL");
|
|
await expect(page.locator("#grid input")).toHaveCount(0);
|
|
});
|
|
|
|
// The silent half, and the one worth pinning. `>` is `drill-into-cell`,
|
|
// which `resolve::step` does not name, so it becomes `KeyOutcome::Upstream`
|
|
// -> `ClientFrame::Key` -> `Host::handle`, whose Key arm is an empty vec
|
|
// (gotcha 47). An upstream key is a black hole, not a slow path.
|
|
test("the drill key does nothing at all, and the page is not merely dead", async ({
|
|
page,
|
|
}) => {
|
|
await anAggregateGrid(page);
|
|
const before = await page.locator("#status").textContent();
|
|
|
|
await page.keyboard.press(">");
|
|
// Nothing moves: not the mode, not the status, not the grid.
|
|
await expect(page.locator("#mode")).toHaveText("NORMAL");
|
|
await expect(page.locator("#status")).toHaveText(before);
|
|
await expect(page.locator("#grid td.editable")).toHaveCount(0);
|
|
|
|
// check_the_checker: a test that asserts "nothing happened" passes against
|
|
// a page that has stopped listening. This proves the keyboard is live, so
|
|
// `>`'s silence is specific to `>` — the refusal below DOES change #status.
|
|
await page.keyboard.press("i");
|
|
await expect(page.locator("#status")).not.toHaveText(before);
|
|
await expect(page.locator("#status")).toContainText(
|
|
"drilling into an aggregate needs the host",
|
|
);
|
|
});
|
|
});
|