diff --git a/cmd/qrunweb/static/index.html b/cmd/qrunweb/static/index.html
index f91a579..a2361f5 100644
--- a/cmd/qrunweb/static/index.html
+++ b/cmd/qrunweb/static/index.html
@@ -201,7 +201,26 @@ function render(data) {
}
function addRow(cells, rowClass) {
- rows.push({ cells, rowClass: rowClass || '' });
+ rowClass = rowClass || '';
+ if (rows.length > 0) {
+ const last = rows[rows.length - 1];
+ if (last.rowClass === rowClass) {
+ let canMerge = true;
+ for (let i = 0; i < cells.length; i++) {
+ if ((cells[i].event || cells[i].cueLabel) && (last.cells[i].event || last.cells[i].cueLabel)) {
+ canMerge = false;
+ break;
+ }
+ }
+ if (canMerge) {
+ for (let i = 0; i < cells.length; i++) {
+ if (cells[i].event || cells[i].cueLabel) last.cells[i] = cells[i];
+ }
+ return;
+ }
+ }
+ }
+ rows.push({ cells, rowClass });
}
function flush() {
@@ -243,42 +262,57 @@ function render(data) {
return null;
}
- function processTrigger(trigger) {
- const src = trigger.source;
- if (src.signal === 'START') return;
- const isCue = src.signal === 'GO';
-
- if (isChain(trigger)) {
- const tid = blockTrack(src.block);
- const tgt = trigger.targets[0].block;
- const sourceHandled = active.get(tid) !== src.block && !pendingEnds.has(src.block);
- if (!sourceHandled) flush();
- if (active.get(tid) === src.block || pendingEnds.has(src.block)) {
- pendingEnds.delete(src.block);
- const endCells = trackIds.map(t =>
- t === tid ? { blockId: src.block, segment: 'end', event: 'END' } : mid(t)
- );
- addRow(endCells);
+ function processChainBatch(chains) {
+ flush();
+ const chainEnds = [];
+ for (const trigger of chains) {
+ const src = trigger.source.block;
+ const tid = blockTrack(src);
+ if (active.get(tid) === src || pendingEnds.has(src)) {
+ pendingEnds.delete(src);
+ chainEnds.push({ block: src, track: tid });
active.delete(tid);
}
+ }
+ if (chainEnds.length > 0) {
+ const endCells = trackIds.map(t => {
+ const e = chainEnds.find(ce => ce.track === t);
+ return e ? { blockId: e.block, segment: 'end', event: 'END' } : mid(t);
+ });
+ addRow(endCells);
+ }
+ const chainStarts = new Map();
+ const combinedTargets = new Map();
+ for (const trigger of chains) {
+ const tgt = trigger.targets[0].block;
+ const tid = blockTrack(tgt);
active.set(tid, tgt);
const targetMap = new Map();
const extras = startSignalMap.get(tgt);
if (extras) extras.forEach(et => targetMap.set(et.block, et.hook));
expandTargets(targetMap);
- const hasTargets = targetMap.size > 0;
- const directEnds = [];
- noTitleAfter.add(rows.length - 1);
- const startCells = trackIds.map(t => {
- if (t === tid) return { blockId: tgt, segment: 'start', event: 'START', isSignal: hasTargets };
- const cell = applyTargets(targetMap, t, false);
- if (cell) { if (cell.directEnd) { directEnds.push(cell.blockId); delete cell.directEnd; } return cell; }
- return mid(t);
- });
- addRow(startCells, hasTargets ? 'sig-row' : '');
- directEnds.forEach(bid => active.delete(blockTrack(bid)));
- return;
+ chainStarts.set(tid, { blockId: tgt, hasTargets: targetMap.size > 0 });
+ for (const [bid, hook] of targetMap) {
+ if (!combinedTargets.has(bid)) combinedTargets.set(bid, hook);
+ }
}
+ const hasAnyTargets = [...chainStarts.values()].some(cs => cs.hasTargets);
+ noTitleAfter.add(rows.length - 1);
+ const directEnds = [];
+ const startCells = trackIds.map(t => {
+ const cs = chainStarts.get(t);
+ if (cs) return { blockId: cs.blockId, segment: 'start', event: 'START', isSignal: cs.hasTargets };
+ const cell = applyTargets(combinedTargets, t, false);
+ if (cell) { if (cell.directEnd) { directEnds.push(cell.blockId); delete cell.directEnd; } return cell; }
+ return mid(t);
+ });
+ addRow(startCells, hasAnyTargets ? 'sig-row' : '');
+ directEnds.forEach(bid => active.delete(blockTrack(bid)));
+ }
+
+ function processTrigger(trigger) {
+ const src = trigger.source;
+ const isCue = src.signal === 'GO';
flush();
@@ -318,7 +352,35 @@ function render(data) {
}
}
- data.triggers.forEach(t => processTrigger(t));
+ let triggerIdx = 0;
+ while (triggerIdx < data.triggers.length) {
+ const t = data.triggers[triggerIdx];
+ if (t.source.signal === 'START') { triggerIdx++; continue; }
+ if (isChain(t)) {
+ if (startSignalMap.has(t.targets[0].block)) {
+ processChainBatch([t]);
+ triggerIdx++;
+ continue;
+ }
+ const batch = [t];
+ const batchTracks = new Set([blockTrack(t.source.block)]);
+ let j = triggerIdx + 1;
+ while (j < data.triggers.length) {
+ if (data.triggers[j].source.signal === 'START') { j++; continue; }
+ if (!isChain(data.triggers[j])) break;
+ if (batchTracks.has(blockTrack(data.triggers[j].source.block))) break;
+ if (startSignalMap.has(data.triggers[j].targets[0].block)) break;
+ batchTracks.add(blockTrack(data.triggers[j].source.block));
+ batch.push(data.triggers[j]);
+ j++;
+ }
+ processChainBatch(batch);
+ triggerIdx = j;
+ continue;
+ }
+ processTrigger(t);
+ triggerIdx++;
+ }
flush();
const stillActive = [];