mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-06 15:39:41 +02:00
fix(tabs): report an edit dropped behind an in-flight save, respect the group cap (#525 landing)
- createEditCoordinator's finally block rebases the edits queued during a write; one that the write's 409 made inapplicable was dropped with no toast. It is now reported once, like the main loop and adoptExternal do (found by the PR bot's re-review; regression test fails without it). - At the 32-group server cap the row and group menus no longer offer a new group, which could only fail with an untranslated 'group limit reached'. MAX_GROUPS is exported from tab-layout-browser.js. - CLAUDE.md names the pagehide keepalive as the one deliberate exception to 'never PUT the layout outside the coordinator'. - The Dashboard wiki page describes tab groups in the vertical rail row. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -330,6 +330,29 @@ describe('edit coordinator', () => {
|
||||
expect(editor.hasPending()).toBe(false);
|
||||
});
|
||||
|
||||
it('reports an edit queued during a save that the save conflict no longer supports', async () => {
|
||||
const { put, calls } = controlledPut();
|
||||
const reportError = vi.fn();
|
||||
const { editor, applied } = makeEditor(put, { reportError });
|
||||
editor.enqueue({ type: 'moveRef', ref: s('c'), groupId: 'g1', index: 2 });
|
||||
await settle();
|
||||
// Queued behind the write in flight, for a group someone else deletes meanwhile.
|
||||
editor.enqueue({ type: 'renameGroup', groupId: 'g2', name: 'Mine' });
|
||||
await settle();
|
||||
expect(calls).toHaveLength(1);
|
||||
const server: Layout = { ...base(9), groups: [base().groups[0]], ungrouped: [...base().ungrouped, w('web')] };
|
||||
calls[0].resolve({ ok: false, status: 409, layout: server });
|
||||
await settle();
|
||||
expect(calls).toHaveLength(2);
|
||||
calls[1].resolve({ ok: true, status: 200, layout: { ...plain(calls[1].request.layout), version: 10 } });
|
||||
await settle();
|
||||
await settle();
|
||||
expect(calls).toHaveLength(2);
|
||||
expect(reportError).toHaveBeenCalledTimes(1);
|
||||
expect(applied.at(-1).layout.groups.map((g: any) => g.id)).toEqual(['g1']);
|
||||
expect(editor.hasPending()).toBe(false);
|
||||
});
|
||||
|
||||
it('gives up after bounded conflicts and asks the caller to re-read', async () => {
|
||||
const { put, calls } = controlledPut();
|
||||
const onFailure = vi.fn();
|
||||
@@ -691,6 +714,26 @@ describe('web tab rows', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('group cap', () => {
|
||||
it('stops offering a new group once the server cap is reached', () => {
|
||||
installFetch();
|
||||
const groups = Array.from({ length: 32 }, (_, i) => ({
|
||||
id: `g${i}`,
|
||||
name: `G${i}`,
|
||||
refs: i === 0 ? [s('s1'), s('s2'), s('s3')] : [],
|
||||
}));
|
||||
const app = makeApp({ ...serverLayout(), groups, ungrouped: [] });
|
||||
app.openTabRailActionMenu({ preventDefault() {}, stopPropagation() {}, currentTarget: row('s2') }, 's2');
|
||||
expect(menuLabels()).not.toContain('Move to new group');
|
||||
expect(menuLabels()).toContain('Move to "G1"');
|
||||
app.closeTabRailActionMenu();
|
||||
header('g0').focus();
|
||||
key(header('g0'), 'F10', { shiftKey: true });
|
||||
expect(menuLabels()).toEqual(['Rename group', 'Move group down', 'Delete group']);
|
||||
app.closeTabGroupMenu();
|
||||
});
|
||||
});
|
||||
|
||||
describe('group menu', () => {
|
||||
it('opens from Shift+F10 / ContextMenu on a header and runs each group operation', async () => {
|
||||
const puts = installFetch();
|
||||
|
||||
Reference in New Issue
Block a user