Keep a place, and come back to it

Restores the commit reverted off the routing branch, which is where it was
committed by mistake. The change is unmodified; only its branch is.

The map had no memory. Every visit started at the same overview, and a system
worth returning to had to be found again by name each time. A mark on the
readout and on a body's panel now keeps it, a Bookmarks tab lists what has been
kept, and choosing one goes there — a star by flying into its system, a body by
opening its page.

Local storage, not an account. This map asks nobody to sign in, and a list of
stars somebody liked is not worth a server. Every read of that store is
defensive, because it is a string a person can edit, another tab can write, and
a browser can refuse to hand over at all: a bad entry is skipped rather than
losing the rest, duplicates are collapsed since two entries for one place would
each toggle the other's control, the list is bounded so a hand-edited store
cannot decide how much this renders, and where storage is denied outright the
bookmarks still work for the visit — they just do not outlive it.

The name is stored alongside the id rather than looked up, so the list reads
before the catalogues have loaded, and a bookmark to something a later
catalogue no longer holds still says what it was instead of decaying into a
bare number.

The tab is offered even when it is empty, and says what the mark does: a tab
that appears only once you have already found the feature is a tab that never
taught anyone anything.

Choosing a kept place hands the panel back to the readout, the same move as
choosing a search result and for the same reason. That behaviour is what the
end-to-end spec caught missing — the readout it asserted on did not exist,
because the panel just used was still covering it.

Verified: build clean, 587/587 unit, 11/11 end-to-end, design detector clean,
screenshots at 1440x900 and 390x844.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi
This commit is contained in:
2026-08-20 19:12:58 +02:00
co-authored by Claude Fable 5
parent 9cdd8f9388
commit 307fd41be8
10 changed files with 513 additions and 18 deletions
+56
View File
@@ -0,0 +1,56 @@
import { expect, test } from '@playwright/test';
import { openSearch } from './support/open-search';
test.describe('Bookmarks', () => {
test('keeps a body, comes back to it in a later visit, and forgets it', async ({ page }) => {
test.setTimeout(90_000);
await page.goto('/body/earth');
await expect(page.getByRole('heading', { name: 'Earth' })).toBeVisible({ timeout: 30_000 });
await page.getByRole('button', { name: 'Keep Earth' }).click();
await expect(page.getByRole('button', { name: 'Forget Earth' })).toHaveAttribute('aria-pressed', 'true');
// A later visit, on a different page: kept places outlive the one they were kept from.
await page.goto('/');
await expect(page.getByTestId('scene-canvas')).toBeVisible();
await page.getByRole('tab', { name: 'Bookmarks' }).click();
const kept = page.locator('#dock-panel-bookmarks li').filter({ hasText: 'Earth' });
await expect(kept).toHaveCount(1);
await kept.getByRole('button').first().click();
// Choosing it goes there, which for a body is its page.
await expect(page).toHaveURL(/\/body\/earth$/, { timeout: 30_000 });
await expect(page.getByRole('heading', { name: 'Earth' })).toBeVisible({ timeout: 30_000 });
await page.getByRole('button', { name: 'Forget Earth' }).click();
await expect(page.getByRole('button', { name: 'Keep Earth' })).toBeVisible();
await page.getByRole('tab', { name: 'Bookmarks' }).click();
await expect(page.locator('#dock-panel-bookmarks')).toContainText('Nothing kept yet');
});
test('keeps the system the view is inside, and flies back to it', async ({ page }) => {
test.setTimeout(120_000);
await page.goto('/?stars=4000');
const searchInput = await openSearch(page);
await searchInput.fill('Proxima Centauri');
await page.getByRole('button', { name: /Proxima Centauri/ }).first().click();
const readout = page.getByTestId('hud-title');
await expect(readout).toHaveText('Proxima Centauri', { timeout: 30_000 });
await page.getByRole('button', { name: 'Keep Proxima Centauri' }).click();
// Back out to the field, then return by what was kept rather than by searching again.
await page.getByRole('button', { name: 'Solar Neighbourhood' }).click();
await expect(readout).toHaveText('Local Stars', { timeout: 30_000 });
await page.getByRole('tab', { name: 'Bookmarks' }).click();
await page.locator('#dock-panel-bookmarks li').filter({ hasText: 'Proxima Centauri' }).getByRole('button').first().click();
await expect(readout).toHaveText('Proxima Centauri', { timeout: 45_000 });
});
});