mirror of
https://github.com/Darkatse/TauriTavern.git
synced 2026-10-19 07:35:14 +00:00
fix (windowed chat): Fix a regression introduced by the windowed state merging logic.
This commit is contained in:
parent
40818607e8
commit
1cacb1fa17
5 changed files with 107 additions and 16 deletions
|
|
@ -7773,7 +7773,7 @@ export async function saveChat({ chatName, withMetadata, mesId, force = false }
|
|||
|
||||
const activeWindowState = getWindowedChatState();
|
||||
if (getWindowedChatKey(activeWindowState) === expectedWindowKey) {
|
||||
const mergedCursor = mergeWindowedChatCursorOffset(activeWindowState?.cursor, cursor);
|
||||
const mergedCursor = mergeWindowedChatCursorOffset(activeWindowState?.cursor, cursor, expectedCursorOffset);
|
||||
const nextWindowState = {
|
||||
...activeWindowState,
|
||||
cursor: mergedCursor,
|
||||
|
|
|
|||
|
|
@ -752,7 +752,7 @@ async function saveGroupChat(groupId, shouldSaveGroup, force = false) {
|
|||
|
||||
const activeWindowState = getWindowedChatState();
|
||||
if (getWindowedChatKey(activeWindowState) === expectedWindowKey) {
|
||||
const mergedCursor = mergeWindowedChatCursorOffset(activeWindowState?.cursor, cursor);
|
||||
const mergedCursor = mergeWindowedChatCursorOffset(activeWindowState?.cursor, cursor, expectedCursorOffset);
|
||||
const nextWindowState = {
|
||||
...activeWindowState,
|
||||
cursor: mergedCursor,
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export function getWindowedChatKey(windowState) {
|
|||
return `character:${String(windowState.characterName || '').trim()}|${String(windowState.avatarUrl || '').trim()}|${String(windowState.fileName || '').trim()}`;
|
||||
}
|
||||
|
||||
export function mergeWindowedChatCursorOffset(activeCursor, nextCursor) {
|
||||
export function mergeWindowedChatCursorOffset(activeCursor, nextCursor, expectedBaseOffset) {
|
||||
if (!nextCursor) {
|
||||
return activeCursor ?? null;
|
||||
}
|
||||
|
|
@ -47,9 +47,16 @@ export function mergeWindowedChatCursorOffset(activeCursor, nextCursor) {
|
|||
return nextCursor;
|
||||
}
|
||||
|
||||
const baseOffset = Number(expectedBaseOffset);
|
||||
if (!Number.isFinite(baseOffset) || baseOffset < 0) {
|
||||
throw new Error('Windowed chat cursor base offset is missing');
|
||||
}
|
||||
|
||||
const delta = Number(nextCursor.offset) - baseOffset;
|
||||
|
||||
return {
|
||||
...nextCursor,
|
||||
offset: activeCursor.offset,
|
||||
offset: Number(activeCursor.offset) + delta,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,21 @@ test('windowed payload: showMoreMessages implements single-flight + CAS commit',
|
|||
assert.match(source, /getWindowedChatState\(\)\s*!==\s*windowState/);
|
||||
});
|
||||
|
||||
test('windowed payload: showMoreMessages reindexes DOM and shifts windowed save counters after prepend', async () => {
|
||||
const source = await readFile(path.join(REPO_ROOT, 'src/script.js'), 'utf8');
|
||||
|
||||
const showMoreStart = source.indexOf('export async function showMoreMessages');
|
||||
assert.ok(showMoreStart >= 0);
|
||||
const slice = source.slice(showMoreStart, showMoreStart + 3000);
|
||||
|
||||
assert.match(slice, /updateViewMessageIds\(\s*0\s*\)\s*;/);
|
||||
assert.match(
|
||||
slice,
|
||||
/const\s+shiftedState\s*=\s*shiftWindowedMessageSaveState\(\s*windowState\s*,\s*messages\.length\s*,\s*['"]chat['"]\s*\)\s*;/,
|
||||
);
|
||||
assert.match(slice, /\.\.\.\s*shiftedState\s*,/);
|
||||
});
|
||||
|
||||
test('windowed payload: clearChat(clearData:true) invalidates windowed state immediately', async () => {
|
||||
const source = await readFile(path.join(REPO_ROOT, 'src/script.js'), 'utf8');
|
||||
|
||||
|
|
@ -43,12 +58,31 @@ test('windowed payload: group tail-load does not mutate window state for backgro
|
|||
assert.match(source, /if\s*\(\s*updateWindowState[\s\S]*?setWindowedChatState\s*\(/s);
|
||||
});
|
||||
|
||||
test('windowed payload: windowed patch commit is guarded and preserves active cursor offset', async () => {
|
||||
test('windowed payload: windowed patch commit is guarded and merges cursor offsets', async () => {
|
||||
const script = await readFile(path.join(REPO_ROOT, 'src/script.js'), 'utf8');
|
||||
const groupChats = await readFile(path.join(REPO_ROOT, 'src/scripts/group-chats.js'), 'utf8');
|
||||
|
||||
assert.match(script, /\bgetWindowedChatKey\b/);
|
||||
assert.match(script, /\bmergeWindowedChatCursorOffset\b/);
|
||||
assert.match(groupChats, /\bgetWindowedChatKey\b/);
|
||||
assert.match(groupChats, /\bmergeWindowedChatCursorOffset\b/);
|
||||
const saveChatStart = script.indexOf('export async function saveChat');
|
||||
assert.ok(saveChatStart >= 0);
|
||||
const saveChatSlice = script.slice(saveChatStart, saveChatStart + 3200);
|
||||
|
||||
assert.match(saveChatSlice, /\bgetWindowedChatKey\b/);
|
||||
assert.match(saveChatSlice, /const\s+expectedCursorOffset\s*=\s*windowState\.cursor\.offset\s*;/);
|
||||
assert.match(
|
||||
saveChatSlice,
|
||||
/mergeWindowedChatCursorOffset\(\s*activeWindowState\?\.\s*cursor\s*,\s*cursor\s*,\s*expectedCursorOffset\s*\)/,
|
||||
);
|
||||
assert.match(saveChatSlice, /activeWindowState\?\.\s*cursor\?\.\s*offset\s*===\s*expectedCursorOffset/);
|
||||
|
||||
const saveGroupStart = groupChats.indexOf('async function saveGroupChat');
|
||||
assert.ok(saveGroupStart >= 0);
|
||||
const saveGroupSlice = groupChats.slice(saveGroupStart, saveGroupStart + 2400);
|
||||
|
||||
assert.match(saveGroupSlice, /\bgetWindowedChatKey\b/);
|
||||
assert.match(saveGroupSlice, /const\s+expectedCursorOffset\s*=\s*windowState\.cursor\.offset\s*;/);
|
||||
assert.match(
|
||||
saveGroupSlice,
|
||||
/mergeWindowedChatCursorOffset\(\s*activeWindowState\?\.\s*cursor\s*,\s*cursor\s*,\s*expectedCursorOffset\s*\)/,
|
||||
);
|
||||
assert.match(saveGroupSlice, /activeWindowState\?\.\s*cursor\?\.\s*offset\s*===\s*expectedCursorOffset/);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -44,27 +44,77 @@ test('windowed-state: getWindowedChatKey trims and separates kinds', async () =>
|
|||
}), 'character:Alice|/User%20Avatars/a.png|chat-1');
|
||||
});
|
||||
|
||||
test('windowed-state: mergeWindowedChatCursorOffset preserves active offset', async () => {
|
||||
test('windowed-state: mergeWindowedChatCursorOffset applies header delta to active offset', async () => {
|
||||
const mod = await importFresh(path.join(REPO_ROOT, 'src/scripts/tauri/chat/windowed-state.js'));
|
||||
const { mergeWindowedChatCursorOffset } = mod;
|
||||
|
||||
assert.equal(mergeWindowedChatCursorOffset(null, null), null);
|
||||
assert.equal(mergeWindowedChatCursorOffset(null, null, 0), null);
|
||||
assert.deepEqual(
|
||||
mergeWindowedChatCursorOffset(null, { offset: 10, size: 1, modifiedMillis: 2 }),
|
||||
mergeWindowedChatCursorOffset(null, { offset: 10, size: 1, modifiedMillis: 2 }, 10),
|
||||
{ offset: 10, size: 1, modifiedMillis: 2 },
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
mergeWindowedChatCursorOffset({ offset: 10, size: 1, modifiedMillis: 2 }, null),
|
||||
mergeWindowedChatCursorOffset({ offset: 10, size: 1, modifiedMillis: 2 }, null, 10),
|
||||
{ offset: 10, size: 1, modifiedMillis: 2 },
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
mergeWindowedChatCursorOffset(
|
||||
{ offset: 10, size: 1, modifiedMillis: 2 },
|
||||
{ offset: 999, size: 2, modifiedMillis: 3 },
|
||||
{ offset: 20, size: 1, modifiedMillis: 2 },
|
||||
{ offset: 10, size: 2, modifiedMillis: 3 },
|
||||
10,
|
||||
),
|
||||
{ offset: 10, size: 2, modifiedMillis: 3 },
|
||||
{ offset: 20, size: 2, modifiedMillis: 3 },
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
mergeWindowedChatCursorOffset(
|
||||
{ offset: 20, size: 1, modifiedMillis: 2 },
|
||||
{ offset: 12, size: 2, modifiedMillis: 3 },
|
||||
10,
|
||||
),
|
||||
{ offset: 22, size: 2, modifiedMillis: 3 },
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
mergeWindowedChatCursorOffset(
|
||||
{ offset: 20, size: 1, modifiedMillis: 2 },
|
||||
{ offset: 8, size: 2, modifiedMillis: 3 },
|
||||
10,
|
||||
),
|
||||
{ offset: 18, size: 2, modifiedMillis: 3 },
|
||||
);
|
||||
});
|
||||
|
||||
test('windowed-state: mergeWindowedChatCursorOffset throws on missing or invalid base offset', async () => {
|
||||
const mod = await importFresh(path.join(REPO_ROOT, 'src/scripts/tauri/chat/windowed-state.js'));
|
||||
const { mergeWindowedChatCursorOffset } = mod;
|
||||
|
||||
assert.throws(
|
||||
() => mergeWindowedChatCursorOffset(
|
||||
{ offset: 10, size: 1, modifiedMillis: 2 },
|
||||
{ offset: 12, size: 2, modifiedMillis: 3 },
|
||||
),
|
||||
/base offset/i,
|
||||
);
|
||||
|
||||
assert.throws(
|
||||
() => mergeWindowedChatCursorOffset(
|
||||
{ offset: 10, size: 1, modifiedMillis: 2 },
|
||||
{ offset: 12, size: 2, modifiedMillis: 3 },
|
||||
NaN,
|
||||
),
|
||||
/base offset/i,
|
||||
);
|
||||
|
||||
assert.throws(
|
||||
() => mergeWindowedChatCursorOffset(
|
||||
{ offset: 10, size: 1, modifiedMillis: 2 },
|
||||
{ offset: 12, size: 2, modifiedMillis: 3 },
|
||||
-1,
|
||||
),
|
||||
/base offset/i,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue