Checkpoint before line-grid scrolling refactor

This commit is contained in:
2026-05-16 13:44:03 +02:00
parent 42582352d6
commit fe33e4f0ab
25 changed files with 1989 additions and 840 deletions
+84 -14
View File
@@ -9,10 +9,11 @@ class SocketClientModule extends BaseModule {
super('socket-client', 'Socket Client');
// Dependencies
this.dependencies = ['text-buffer', 'markup-parser'];
this.dependencies = ['text-buffer', 'markup-parser', 'story-history'];
this.socket = null;
this.textBuffer = null;
this.storyHistory = null;
this.isConnected = false;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
@@ -20,6 +21,8 @@ class SocketClientModule extends BaseModule {
this.url = null;
this.eventListeners = {};
this.defaultHost = 'localhost:3000';
this.receivedBlockCounter = 0;
this.receivedParagraphCounter = 0;
// Bind methods using parent's bindMethods utility
this.bindMethods([
@@ -44,6 +47,8 @@ class SocketClientModule extends BaseModule {
'setupGameEventHandlers',
'processTurnResult',
'processParagraphResult',
'storeAndQueueBlocks',
'normalizeHistoryBlock',
'dispatchTurnTags',
'isTimedCueTag',
'cueMarkersFromTags',
@@ -93,6 +98,11 @@ class SocketClientModule extends BaseModule {
console.error("Socket Client: Failed to get text-buffer module");
return false;
}
this.storyHistory = this.getModule('story-history');
if (!this.storyHistory) {
console.error("Socket Client: Failed to get story-history module");
return false;
}
this.reportProgress(50, "Setting up connection parameters");
@@ -191,7 +201,7 @@ class SocketClientModule extends BaseModule {
});
}
processTurnResult(data) {
async processTurnResult(data) {
if (!data) return;
const turnId = Number(data.turnId);
@@ -199,6 +209,10 @@ class SocketClientModule extends BaseModule {
console.error('Socket Client: Invalid TurnResult received', data);
return;
}
if (turnId === 1) {
this.receivedBlockCounter = 0;
this.receivedParagraphCounter = 0;
}
if (Array.isArray(data.globalTags) && data.globalTags.length > 0) {
document.dispatchEvent(new CustomEvent('story:global-tags', {
@@ -214,12 +228,24 @@ class SocketClientModule extends BaseModule {
role: null,
cueTags: []
};
const turnBlocks = [];
data.paragraphs.forEach((paragraph) => {
pendingParagraph = this.processParagraphResult(paragraph, turnId, pendingParagraph);
const result = this.processParagraphResult(paragraph, turnId, pendingParagraph);
pendingParagraph = result.pendingParagraph;
turnBlocks.push(...result.blocks);
});
this.dispatchChoices(Array.isArray(data.choices) ? data.choices : []);
this.dispatchInputMode(data.inputMode || (Array.isArray(data.choices) && data.choices.length > 0 ? 'choice' : 'text'));
await this.storeAndQueueBlocks(turnBlocks);
const choices = Array.isArray(data.choices) ? data.choices : [];
const inputMode = data.inputMode || (choices.length > 0 ? 'choice' : 'text');
this.dispatchChoices(choices);
this.dispatchInputMode(inputMode);
if (turnBlocks.length === 0 && choices.length > 0) {
document.dispatchEvent(new CustomEvent('story:process-state', {
detail: { state: 'ready', reason: 'choice-only-turn', turnId }
}));
}
}
dispatchTurnTags(tags, paragraph = null) {
@@ -259,15 +285,16 @@ class SocketClientModule extends BaseModule {
const immediateTags = tags.filter(tag => !this.isStructuralTag(tag) && !this.isTimedCueTag(tag));
this.dispatchTurnTags(immediateTags, paragraph);
blocks.forEach(block => this.enqueueStructuredBlock(block));
if (!text) {
return {
role: paragraphRole || pending.role || null,
cueTags: [
...(Array.isArray(pending.cueTags) ? pending.cueTags : []),
...cueTags
]
blocks,
pendingParagraph: {
role: paragraphRole || pending.role || null,
cueTags: [
...(Array.isArray(pending.cueTags) ? pending.cueTags : []),
...cueTags
]
}
};
}
@@ -279,7 +306,7 @@ class SocketClientModule extends BaseModule {
...cueTags
])
];
this.enqueueStructuredBlock({
blocks.push({
type: 'paragraph',
text,
layoutText: paragraph.layoutText || text,
@@ -291,7 +318,50 @@ class SocketClientModule extends BaseModule {
turnId
});
return { role: null, cueTags: [] };
return { blocks, pendingParagraph: { role: null, cueTags: [] } };
}
async storeAndQueueBlocks(blocks = []) {
if (!Array.isArray(blocks) || blocks.length === 0) return;
if (!this.storyHistory) {
this.storyHistory = this.getModule('story-history');
}
const normalizedBlocks = blocks.map(block => this.normalizeHistoryBlock(block));
let records = normalizedBlocks;
if (this.storyHistory && typeof this.storyHistory.recordBlocks === 'function') {
records = await this.storyHistory.recordBlocks(normalizedBlocks);
document.dispatchEvent(new CustomEvent('story:history-updated', {
detail: {
gameId: this.storyHistory.currentGameId || null,
latestBlockId: this.storyHistory.nextBlockId - 1
}
}));
} else {
console.warn('Socket Client: Story history unavailable; queueing unstored blocks');
}
records.forEach(block => this.enqueueStructuredBlock(block));
}
normalizeHistoryBlock(block) {
const type = String(block?.type || 'paragraph');
this.receivedBlockCounter += 1;
const normalized = {
...block,
type,
id: block.id || `${type}-${block.turnId || 'turn'}-${this.receivedBlockCounter}`
};
if (type === 'paragraph') {
normalized.paragraphIndex = Number.isInteger(block.paragraphIndex)
? block.paragraphIndex
: this.receivedParagraphCounter;
this.receivedParagraphCounter += 1;
}
return normalized;
}
isStructuralTag(tag) {