diff --git a/e2e/db/profile-sections.spec.ts b/e2e/db/profile-sections.spec.ts index a537829..27af050 100644 --- a/e2e/db/profile-sections.spec.ts +++ b/e2e/db/profile-sections.spec.ts @@ -192,7 +192,7 @@ test("the places are put in order and stay in it, for the owner and for a visito await page.getByRole("button", { name: "Edytuj profil" }).click(); const chips = page.locator("li:has(button[aria-label^='Przesuń miejsce'])"); - expect(await chips.allInnerTexts()).toEqual(["Warszawa", "Nowa Wieś"]); + await expect(chips).toHaveText(["Warszawa", "Nowa Wieś"]); // The arrow keys on the grip, which is the whole of this for anyone not // using a mouse — and, unlike a drag, it says what it did out loud. @@ -205,16 +205,13 @@ test("the places are put in order and stay in it, for the owner and for a visito await expect( page.getByText("Miejsce Nowa Wieś jest teraz na pozycji 1"), ).toHaveCount(1); - expect(await chips.allInnerTexts()).toEqual(["Nowa Wieś", "Warszawa"]); + await expect(chips).toHaveText(["Nowa Wieś", "Warszawa"]); await page.reload(); const section = page.locator("section", { has: page.getByRole("heading", { name: "Siedziba i obszar działania" }), }); - expect(await section.locator("li").allInnerTexts()).toEqual([ - "Nowa Wieś", - "Warszawa", - ]); + await expect(section.locator("li")).toHaveText(["Nowa Wieś", "Warszawa"]); const visitor = await browser.newPage({ locale: "pl-PL" }); try { @@ -224,10 +221,7 @@ test("the places are put in order and stay in it, for the owner and for a visito name: "Siedziba i obszar działania", }), }); - expect(await theirs.locator("li").allInnerTexts()).toEqual([ - "Nowa Wieś", - "Warszawa", - ]); + await expect(theirs.locator("li")).toHaveText(["Nowa Wieś", "Warszawa"]); } finally { await visitor.close(); } diff --git a/e2e/db/works-order.spec.ts b/e2e/db/works-order.spec.ts index 13bc524..8370e7b 100644 --- a/e2e/db/works-order.spec.ts +++ b/e2e/db/works-order.spec.ts @@ -35,9 +35,15 @@ const THIRD = "Dom w lesie"; let page: Page; let identity: Identity; -/** The work names as the page lists them, top to bottom. */ -async function namesOn(target: Page): Promise { - return target.locator("article h3").allInnerTexts(); +/** + * The work names as the page lists them, top to bottom — as a retrying + * assertion, not a snapshot. Reading the names straight after a key press + * asks the page what it looks like before React has finished putting it + * there: green on a quiet machine, red about one run in three on CI, and the + * difference is not the product. + */ +function namesOn(target: Page) { + return expect(target.locator("article h3")); } test.beforeAll(async ({ browser }) => { @@ -55,7 +61,7 @@ test.afterAll(async () => { test("the grips appear only while editing", async () => { await page.goto(`/${identity.handle}`); - expect(await namesOn(page)).toEqual([FIRST, SECOND, THIRD]); + await namesOn(page).toHaveText([FIRST, SECOND, THIRD]); await expect( page.getByRole("button", { name: /^Przesuń realizację/ }), ).toHaveCount(0); @@ -84,14 +90,21 @@ test("the arrow keys on a grip move a work, and the order survives a reload", as await expect( page.getByText(`Realizacja ${THIRD} jest teraz na pozycji 2`), ).toHaveCount(1); - expect(await namesOn(page)).toEqual([FIRST, THIRD, SECOND]); + await namesOn(page).toHaveText([FIRST, THIRD, SECOND]); // The keyboard stays on the work it moved, or a second press would move // whatever took its place. + const savedAgain = page.waitForResponse((response) => + response.url().includes("/api/works/order"), + ); await page.keyboard.press("ArrowUp"); - expect(await namesOn(page)).toEqual([THIRD, FIRST, SECOND]); + await namesOn(page).toHaveText([THIRD, FIRST, SECOND]); + // Waited for before the reload, not out of tidiness: the order is sent + // once the moving stops, so a reload racing that timer would cancel the + // request in flight and the test would be about the wrong thing. + expect((await savedAgain).status()).toBe(200); await page.reload(); - expect(await namesOn(page)).toEqual([THIRD, FIRST, SECOND]); + await namesOn(page).toHaveText([THIRD, FIRST, SECOND]); }); test("a work dragged by its grip lands where it was dropped", async () => { @@ -121,16 +134,16 @@ test("a work dragged by its grip lands where it was dropped", async () => { await page.mouse.up(); expect((await saved).status()).toBe(200); - expect(await namesOn(page)).toEqual([SECOND, THIRD, FIRST]); + await namesOn(page).toHaveText([SECOND, THIRD, FIRST]); await page.reload(); - expect(await namesOn(page)).toEqual([SECOND, THIRD, FIRST]); + await namesOn(page).toHaveText([SECOND, THIRD, FIRST]); }); test("a visitor sees the owner's order", async ({ browser }) => { const visitor = await browser.newPage({ locale: "pl-PL" }); try { await visitor.goto(`/${identity.handle}`); - expect(await namesOn(visitor)).toEqual([SECOND, THIRD, FIRST]); + await namesOn(visitor).toHaveText([SECOND, THIRD, FIRST]); // Nothing to take hold of on someone else's profile. await expect( visitor.getByRole("button", { name: /^Przesuń realizację/ }), diff --git a/tasks/plan.md b/tasks/plan.md index 964b066..2af44c4 100644 --- a/tasks/plan.md +++ b/tasks/plan.md @@ -79,11 +79,24 @@ in under 5 minutes (manual walkthrough); e2e green. - ~~[#31](https://github.com/Devski/platform-lite/issues/31) PR preview deployments on the dev instance~~ — **done 05.09.2026**: `pr-.dev.architektow3d.pl`, named sites over HTTP-01 (no wildcard certificate, so no DNS plugin), shared dev database and a `pr-/` key prefix. Two at a time — the instance has one core and no swap. Previews never send e-mail. - [#113](https://github.com/Devski/platform-lite/issues/113) PR previews: a database cloned - from dev per preview, not the shared one (`deployment`). Filed 09.09.2026 when the preview - of #112 answered with a server error: it ran the pull request's image against dev's - schema, and previews never migrate. Sequenced after the R360 trial on dev. + from dev per preview, not the shared one (`deployment`). **Raised in priority 12.09.2026** + after it cost a second review cycle. Filed 09.09.2026 when the preview of #112 answered + with a server error: it runs the pull request's image against dev's database, and previews + never migrate. #170 hit it again — every page listing works answered 500 on that preview + because the migration adding `works.position` had not run there. It is not a flake but a + rule: **any pull request carrying a migration looks broken on its own preview**, exactly + when the preview exists to be looked at. The workaround both times was to apply the + migration to dev by hand first, which is only safe while migrations stay expand-only (G6) + and someone is there to reason about it. - [#111](https://github.com/Devski/platform-lite/issues/111) Preview cleanup loses the race with a CI run still in flight, and the orphan blocks the two-preview cap (`bug`). +- [#172](https://github.com/Devski/platform-lite/issues/172) The database has no deadlines + (`infra`, `deployment`). Found 12.09.2026 in the security review of #66: the pool is built + with pg's defaults, so `connectionTimeoutMillis` is 0 — a request waiting for a free + connection waits for ever — and neither `statement_timeout` nor `lock_timeout` is set + anywhere, on either side. Every limit in this system is a rate limit on the way IN; past + it, nothing bounds how long a request holds a connection or a lock, so contention queues + silently instead of failing with something to read. - [#119](https://github.com/Devski/platform-lite/issues/119) The dev instance keeps every image it ever pulled (`infra`). Filed 09.09.2026 when its root filesystem reached 100%: 129 images, 21.8 GB, three of them in use. Previews stopped starting at all, and dev's @@ -290,6 +303,21 @@ recorded so it is not rediscovered later. - ~~[#36](https://github.com/Devski/platform-lite/issues/36) The public profile showed the user's e-mail address as their name~~ — **done 05.09.2026**: registration stopped inventing a name from the address; onboarding asks for one in two steps and derives the address from it. A migration cleared what the old flow wrote, keeping handles — they may already have been shared. Found two faults of my own on the way, both recorded on the issue and generalised as #39. - [#39](https://github.com/Devski/platform-lite/issues/39) Make the UI airtight against what the backend and the database will accept (`enhancement`) — opened 05.09.2026: a rule can live in the form, the API schema and a database constraint, and nothing keeps the three in agreement. Two instances on one screen in #36: a submit the form could not know would fail, and a `CHECK` nothing above the database could see — both surfacing as "try again". - [#44](https://github.com/Devski/platform-lite/issues/44) Where personal data lives: which of it is sensitive, and does it belong in its own store (`compliance`) — opened 06.09.2026, four questions to answer in writing before production. The name is public by design; the sensitive thing is its LINK to the private address. Also carries four gaps found while surveying: disk encryption unverified, no dev backups, session tokens in plaintext, recipient address possibly reaching a log line. +- ~~[#66](https://github.com/Devski/platform-lite/issues/66) Small things to polish~~ — + **done 12.09.2026** (PR #170), four of them: the logo above the auth cards stopped being a + link (a visitor part-way through making an account was being offered a way out, and after + verification that link looped to the page it was on); Tab commits a typed place, because a + phone's keyboard offers "next" where a desktop offers Enter and the typed text went with + the focus; and both the places and the works can be dragged into order, one piece of + machinery for a wrapped row of chips and a grid of cards. Works needed a `position` column + — expand-only, backfilled from `created_at`, so nothing shuffled. The reviews found what + the tests could not: a refused save that resurrected a deleted work, saves racing each + other under a repeating arrow key, and grips whose keys a screen reader would never guess. +- [#173](https://github.com/Devski/platform-lite/issues/173) Dragging a list should look like + dragging (`ux`). Dawid, 12.09.2026, on seeing #66 work: the order is right, the movement is + not — the held item fades in place and everything jumps on the drop. For now it stays. The + boxes needed for it are already measured at pointerdown, so this is a transform and a + transition rather than a rewrite. - Three from Dawid on 11.09.2026, deliberately outside the R360 milestone. One is already settled: ~~[#151](https://github.com/Devski/platform-lite/issues/151) an orbit from a RAR archive~~ — **dropped 12.09.2026** at his word, we do not support RAR for now; a zip is