fix(server-storage): adopted edits keep yielding through conflicts; newest view per item; same-token sign-ins

A conflict replay collapsed every step into one edit, so a closed tab's
adopted step lost `yields` after the first conflict and a second one
replayed its old position over a newer one. Its steps now stay as they
are, and only this page's own collapse into one edit on top of them.

The move combines recently viewed items one per item, keeping the newest
view and its artwork, then newest first; before, the server's older copy
of an item won over a later browser view.

A failed sign-in clears the stored token only on a 401: a network
failure says nothing about the token, and another tab may have stored
the same one meanwhile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-10-14 00:30:40 +02:00
1 parent 6f1fe7929c
commit c8afa382fe
8 files changed
+108 -30

No files matched your search

@@ -98,6 +98,20 @@ describe('ServerSignInComponent', () => {
expect(readServerAccessToken('i-1')).toBe('right');
});
it('leaves a token another tab stored to it after a network failure', async () => {
session.fetchStatus.mockImplementation(async () => {
// Another tab signs in with the same token meanwhile.
writeServerAccessToken('right', 'i-1');
throw new ServerApiError(0, 'network', 'offline');
});
component.edit('right');
await component.submit(new Event('submit'));
expect(component.feedback()).toBe('unreachable');
expect(readServerAccessToken('i-1')).toBe('right');
});
it('tells an unreachable server apart and clears the message on edit', async () => {
session.fetchStatus.mockRejectedValue(
new ServerApiError(0, undefined, 'offline')
@@ -83,12 +83,14 @@ export class ServerSignInComponent {
await this.session.fetchStatus();
this.openWorkspace();
} catch (error) {
forgetAttempt(token);
this.feedback.set(
error instanceof ServerApiError && error.status === 401
? 'rejected'
: 'unreachable'
);
const rejected =
error instanceof ServerApiError && error.status === 401;
// A refused token is no one's. A network failure says nothing
// about it, and another tab may have stored the same one since.
if (rejected) {
forgetAttempt(token);
}
this.feedback.set(rejected ? 'rejected' : 'unreachable');
} finally {
this.busy.set(false);
}