fix: trim trailing blank lines BEFORE slice, not after
Sessions with cursor near the top (e.g. a fresh tunnel/ssh session) have content at rows 1-2 and rows 3-40 blank in a 40-row terminal. The previous fix trimmed AFTER slice(-20), but slice(-20) of a 40-line snapshot grabs the last 20 rows — all blank. Trimming after slice then removed everything, leaving an empty <pre>. Fix: trim trailing blank lines from the full snapshot first, then slice the last N lines of what remains. This way a session with 2 content lines at the top is reduced to those 2 lines before slicing, and both appear in the preview. Fixes both buildTileHTML (grid tiles) and buildSidebarHTML (sidebar cards). Tests updated to cover the 40-row terminal scenario.
This commit is contained in:
+16
-14
@@ -526,14 +526,15 @@ function buildTileHTML(session, index, mobile) {
|
||||
// Last N lines of snapshot — show more in fit mode so tall tiles fill
|
||||
const snapshot = session.snapshot || '';
|
||||
var _lineCount = (ds.viewMode === 'fit') ? -80 : -20;
|
||||
var lines = snapshot.split('\n').slice(_lineCount);
|
||||
// Trim trailing blank lines — sessions with cursor near the top have mostly empty
|
||||
// lines at the bottom, making the preview appear blank. Removing them lets the
|
||||
// CSS `position: absolute; bottom: 0` anchor the meaningful content correctly.
|
||||
while (lines.length > 0 && lines[lines.length - 1].trim() === '') {
|
||||
lines.pop();
|
||||
// Trim trailing blank lines from the FULL snapshot FIRST — sessions with the cursor
|
||||
// near the top (e.g. fresh tunnel/ssh session) have content at rows 1-2 and rows 3-40
|
||||
// blank. slice(-20) would grab the last 20 rows (all blank); trimming after slice
|
||||
// then removes everything → empty preview. Trim first so slice sees only content rows.
|
||||
var allLines = snapshot.split('\n');
|
||||
while (allLines.length > 0 && allLines[allLines.length - 1].trim() === '') {
|
||||
allLines.pop();
|
||||
}
|
||||
const lastLines = lines.join('\n');
|
||||
const lastLines = allLines.slice(_lineCount).join('\n');
|
||||
|
||||
const sourceUrlAttr = session.sourceUrl ? ` data-source-url="${escapeHtml(session.sourceUrl)}"` : '';
|
||||
return (
|
||||
@@ -582,15 +583,16 @@ function buildSidebarHTML(session, currentSession) {
|
||||
// Timestamp for meta line
|
||||
const timeStr = formatTimestamp(session.last_activity_at || null);
|
||||
|
||||
// Last 20 lines of snapshot
|
||||
// Last 20 lines of snapshot — trim trailing blanks from the FULL snapshot FIRST,
|
||||
// then slice. Sessions with the cursor near the top have content at rows 1-2 and
|
||||
// rows 3-40 blank; slice(-20) would return only blank rows, then trim-after-slice
|
||||
// removes everything → empty preview. Trim first to keep meaningful content.
|
||||
const snapshot = session.snapshot || '';
|
||||
var sidebarLines = snapshot.split('\n').slice(-20);
|
||||
// Trim trailing blank lines — sessions with cursor near the top have mostly empty
|
||||
// lines at the bottom, making the preview appear blank.
|
||||
while (sidebarLines.length > 0 && sidebarLines[sidebarLines.length - 1].trim() === '') {
|
||||
sidebarLines.pop();
|
||||
var allLines = snapshot.split('\n');
|
||||
while (allLines.length > 0 && allLines[allLines.length - 1].trim() === '') {
|
||||
allLines.pop();
|
||||
}
|
||||
const lastLines = sidebarLines.join('\n');
|
||||
const lastLines = allLines.slice(-20).join('\n');
|
||||
|
||||
return (
|
||||
`<article class="${classes}" data-session="${escapedName}" data-source-url="${escapeHtml(session.sourceUrl || '')}" tabindex="0" role="listitem">` +
|
||||
|
||||
@@ -4354,3 +4354,55 @@ test('buildSidebarHTML renders empty pre when snapshot is entirely blank lines',
|
||||
assert.ok(preMatch, '<pre> element must exist');
|
||||
assert.strictEqual(preMatch[1], '', 'sidebar pre should be empty when snapshot has only blank lines');
|
||||
});
|
||||
|
||||
// --- Trim BEFORE slice: 40-row terminal with content at top ---
|
||||
// The real bug: a 40-row terminal with content at rows 1-2 and rows 3-40 blank.
|
||||
// slice(-20) grabs the LAST 20 rows → all blank. Then trim-after-slice removes
|
||||
// everything → empty pre. Fix: trim trailing blanks from the full snapshot FIRST,
|
||||
// then slice the last 20 of what remains.
|
||||
|
||||
test('buildTileHTML shows content from top of 40-row terminal (trim BEFORE slice)', () => {
|
||||
// 40 lines: 2 content lines + 38 trailing blank lines (realistic 40-row terminal,
|
||||
// cursor near top — e.g. a fresh ssh tunnel session)
|
||||
const snapshot = 'TUNNEL_CONTENT_LINE\nstatus: connected\n' + '\n'.repeat(38);
|
||||
const session = { name: 'tunnel', snapshot };
|
||||
const html = app.buildTileHTML(session, 0, false);
|
||||
assert.ok(
|
||||
html.includes('TUNNEL_CONTENT_LINE'),
|
||||
'tile must show content from top of terminal even with 38 trailing blank lines — trim full snapshot BEFORE slice(-20)',
|
||||
);
|
||||
});
|
||||
|
||||
test('buildSidebarHTML shows content from top of 40-row terminal (trim BEFORE slice)', () => {
|
||||
const snapshot = 'TUNNEL_CONTENT_LINE\nstatus: connected\n' + '\n'.repeat(38);
|
||||
const session = { name: 'tunnel', snapshot, bell: { unseen_count: 0 } };
|
||||
const html = app.buildSidebarHTML(session, '');
|
||||
assert.ok(
|
||||
html.includes('TUNNEL_CONTENT_LINE'),
|
||||
'sidebar must show content from top of terminal even with 38 trailing blank lines — trim full snapshot BEFORE slice(-20)',
|
||||
);
|
||||
});
|
||||
|
||||
test('buildTileHTML trim happens BEFORE slice in source (structural order check)', () => {
|
||||
const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8');
|
||||
const fnStart = source.indexOf('function buildTileHTML');
|
||||
const fnBody = source.substring(fnStart, fnStart + 2000);
|
||||
const trimIdx = fnBody.indexOf('.pop()');
|
||||
const sliceIdx = fnBody.indexOf('.slice(');
|
||||
assert.ok(
|
||||
trimIdx < sliceIdx,
|
||||
'trailing blank trim (.pop()) must appear BEFORE .slice() in buildTileHTML — trim the full snapshot first, then slice the last N lines',
|
||||
);
|
||||
});
|
||||
|
||||
test('buildSidebarHTML trim happens BEFORE slice in source (structural order check)', () => {
|
||||
const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8');
|
||||
const fnStart = source.indexOf('function buildSidebarHTML');
|
||||
const fnBody = source.substring(fnStart, fnStart + 2000);
|
||||
const trimIdx = fnBody.indexOf('.pop()');
|
||||
const sliceIdx = fnBody.indexOf('.slice(');
|
||||
assert.ok(
|
||||
trimIdx < sliceIdx,
|
||||
'trailing blank trim (.pop()) must appear BEFORE .slice() in buildSidebarHTML — trim the full snapshot first, then slice the last 20 lines',
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user