host: relate removes just the picked candidate row in place (no reload)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 33s

Picking a candidate to relate it no longer does a full POST -> 303 -> reload.
The candidate <li> now carries an id and its relate form is an AJAX sx-post
(sx-target="#cand-<kind>-<other>", sx-swap="delete"): on success the engine
deletes just that one row — the item is now related, so it leaves the candidate
pool with no reload and no candidate-list refetch. host/blog-relate-submit returns
an empty 200 for an SX request (so the delete swap fires) and still 303s for a
plain POST (no-JS fallback via the form's method+action).

relate-picker.spec.js test 4 updated to assert the in-place row delete + no reload
+ the relation still persists (shows on the post page). 6/6 + conformance 272/272.

(Symmetric unrelate-in-place was prototyped but backed out: the current-links
form, bound via boot's process-elements rather than post-swap, didn't fire the
AJAX delete despite identical markup — a binding quirk to chase separately. Unrelate
keeps its plain POST -> reload for now, no regression.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-29 15:49:03 +00:00
parent b21ae05e8f
commit c0007740e7
2 changed files with 25 additions and 14 deletions

View File

@@ -78,19 +78,20 @@ test.describe('relate picker', () => {
await expect(page.locator(RELR)).toContainText('Picker Item 13');
});
test('clicking a candidate relates it (and it shows on the post page)', async ({ page }) => {
// heavier than the others: a full-reload relate (sx-disable) then a goto to the
// post page — two WASM kernel boots — so it needs more than the 30s default.
test('clicking a candidate relates it and removes just that row (no reload)', async ({ page }) => {
test.setTimeout(75000);
await loginTo(page, `/${HOST}/edit`);
await waitReady(page);
await page.fill(RELF, 'Item 07');
await expect.poll(() => page.locator(RELROWS).count(), { timeout: 10000 }).toBe(1);
// sentinel survives ONLY if there is no full-page reload
await page.evaluate(() => { window.__noReload = true; });
await page.locator(`${RELROWS} button`).first().click();
// form POST -> 303 back to the edit page; the related list now links the slug
await expect(page).toHaveURL(new RegExp(`/${HOST}/edit`));
await expect(page.locator('a[href="/picker-item-07/"]')).toHaveCount(1);
// and the public post page shows the Related posts block with the title
// the AJAX relate deletes just that candidate row in place — no reload, no
// candidate-list refetch
await expect.poll(() => page.locator(RELROWS).count(), { timeout: 10000 }).toBe(0);
expect(await page.evaluate(() => window.__noReload)).toBe(true);
// and the relation actually persisted (shows on the public post page)
await page.goto(`/${HOST}/`);
await expect(page.getByRole('heading', { name: 'Related posts' })).toBeVisible();
await expect(page.locator('body')).toContainText('Picker Item 07');