mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
Fix mobile horizontal fit with Fira Code font consistency
- Remove debug logging after confirming font fix works - Fira Code provides consistent character width across platforms - Horizontal fit now works reliably on real devices and emulators - Minimum font size of 4px enables proper mobile fitting 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
4dd6d941d8
commit
12eaa39d73
2 changed files with 12 additions and 30 deletions
|
|
@ -188,7 +188,7 @@
|
||||||
content += '\x1b[37m┌──────────────┬──────────────┬──────────────┬──────────────┐\x1b[0m\r\n';
|
content += '\x1b[37m┌──────────────┬──────────────┬──────────────┬──────────────┐\x1b[0m\r\n';
|
||||||
content += '\x1b[37m│\x1b[1m Name \x1b[0m\x1b[37m│\x1b[1m Status \x1b[0m\x1b[37m│\x1b[1m CPU% \x1b[0m\x1b[37m│\x1b[1m Memory% \x1b[0m\x1b[37m│\x1b[0m\r\n';
|
content += '\x1b[37m│\x1b[1m Name \x1b[0m\x1b[37m│\x1b[1m Status \x1b[0m\x1b[37m│\x1b[1m CPU% \x1b[0m\x1b[37m│\x1b[1m Memory% \x1b[0m\x1b[37m│\x1b[0m\r\n';
|
||||||
content += '\x1b[37m├──────────────┼──────────────┼──────────────┼──────────────┤\x1b[0m\r\n';
|
content += '\x1b[37m├──────────────┼──────────────┼──────────────┼──────────────┤\x1b[0m\r\n';
|
||||||
|
|
||||||
const processes = [
|
const processes = [
|
||||||
{ name: 'web-server', status: 'running', cpu: '12.3', mem: '45.2', statusColor: '32' },
|
{ name: 'web-server', status: 'running', cpu: '12.3', mem: '45.2', statusColor: '32' },
|
||||||
{ name: 'database', status: 'running', cpu: '8.7', mem: '62.1', statusColor: '32' },
|
{ name: 'database', status: 'running', cpu: '8.7', mem: '62.1', statusColor: '32' },
|
||||||
|
|
@ -196,7 +196,7 @@
|
||||||
{ name: 'cache', status: 'running', cpu: '2.1', mem: '18.9', statusColor: '32' },
|
{ name: 'cache', status: 'running', cpu: '2.1', mem: '18.9', statusColor: '32' },
|
||||||
{ name: 'auth-svc', status: 'warning', cpu: '15.6', mem: '78.3', statusColor: '33' }
|
{ name: 'auth-svc', status: 'warning', cpu: '15.6', mem: '78.3', statusColor: '33' }
|
||||||
];
|
];
|
||||||
|
|
||||||
processes.forEach(proc => {
|
processes.forEach(proc => {
|
||||||
content += `\x1b[37m│\x1b[0m ${proc.name.padEnd(12)} \x1b[37m│\x1b[0m \x1b[${proc.statusColor}m${proc.status.padEnd(12)}\x1b[0m \x1b[37m│\x1b[0m ${proc.cpu.padStart(12)} \x1b[37m│\x1b[0m ${proc.mem.padStart(12)} \x1b[37m│\x1b[0m\r\n`;
|
content += `\x1b[37m│\x1b[0m ${proc.name.padEnd(12)} \x1b[37m│\x1b[0m \x1b[${proc.statusColor}m${proc.status.padEnd(12)}\x1b[0m \x1b[37m│\x1b[0m ${proc.cpu.padStart(12)} \x1b[37m│\x1b[0m ${proc.mem.padStart(12)} \x1b[37m│\x1b[0m\r\n`;
|
||||||
});
|
});
|
||||||
|
|
@ -227,7 +227,7 @@
|
||||||
{ hash: 'u7v8w9x', msg: 'chore: update dependencies to latest versions', author: 'bob', time: '2 days ago' },
|
{ hash: 'u7v8w9x', msg: 'chore: update dependencies to latest versions', author: 'bob', time: '2 days ago' },
|
||||||
{ hash: 'y1z2a3b', msg: 'feat: add dark mode support', author: 'charlie', time: '3 days ago' }
|
{ hash: 'y1z2a3b', msg: 'feat: add dark mode support', author: 'charlie', time: '3 days ago' }
|
||||||
];
|
];
|
||||||
|
|
||||||
commits.forEach(commit => {
|
commits.forEach(commit => {
|
||||||
content += `\x1b[33m${commit.hash}\x1b[0m \x1b[32m${commit.msg}\x1b[0m \x1b[90m(${commit.author}, ${commit.time})\x1b[0m\r\n`;
|
content += `\x1b[33m${commit.hash}\x1b[0m \x1b[32m${commit.msg}\x1b[0m \x1b[90m(${commit.author}, ${commit.time})\x1b[0m\r\n`;
|
||||||
});
|
});
|
||||||
|
|
@ -242,7 +242,7 @@
|
||||||
{ id: 'b4a8f2d9c1e7', image: 'redis:alpine', cmd: 'docker-entrypoint.sh', created: '1 hour ago', status: 'Exited (0)', ports: '', statusColor: '31' },
|
{ id: 'b4a8f2d9c1e7', image: 'redis:alpine', cmd: 'docker-entrypoint.sh', created: '1 hour ago', status: 'Exited (0)', ports: '', statusColor: '31' },
|
||||||
{ id: '9c1e4b8f2a5d', image: 'node:16-alpine', cmd: 'npm start', created: '30 min ago', status: 'Up 30 minutes', ports: '0.0.0.0:3000->3000/tcp', statusColor: '32' }
|
{ id: '9c1e4b8f2a5d', image: 'node:16-alpine', cmd: 'npm start', created: '30 min ago', status: 'Up 30 minutes', ports: '0.0.0.0:3000->3000/tcp', statusColor: '32' }
|
||||||
];
|
];
|
||||||
|
|
||||||
containers.forEach(container => {
|
containers.forEach(container => {
|
||||||
content += `${container.id.substring(0, 12)} ${container.image.padEnd(18)} ${container.cmd.padEnd(24)} ${container.created.padEnd(14)} \x1b[${container.statusColor}m${container.status.padEnd(24)}\x1b[0m ${container.ports}\r\n`;
|
content += `${container.id.substring(0, 12)} ${container.image.padEnd(18)} ${container.cmd.padEnd(24)} ${container.created.padEnd(14)} \x1b[${container.statusColor}m${container.status.padEnd(24)}\x1b[0m ${container.ports}\r\n`;
|
||||||
});
|
});
|
||||||
|
|
@ -266,7 +266,7 @@
|
||||||
'File not found: /var/log/application.log',
|
'File not found: /var/log/application.log',
|
||||||
'SSL certificate expires in 7 days'
|
'SSL certificate expires in 7 days'
|
||||||
];
|
];
|
||||||
|
|
||||||
errors.forEach((error, i) => {
|
errors.forEach((error, i) => {
|
||||||
content += `\x1b[90m[${new Date().toISOString()}]\x1b[0m \x1b[31mERROR\x1b[0m ${error}\r\n`;
|
content += `\x1b[90m[${new Date().toISOString()}]\x1b[0m \x1b[31mERROR\x1b[0m ${error}\r\n`;
|
||||||
});
|
});
|
||||||
|
|
@ -281,7 +281,7 @@
|
||||||
{ query: 'SELECT COUNT(*) FROM sessions WHERE expires_at > NOW()', time: '15ms', rows: '1' },
|
{ query: 'SELECT COUNT(*) FROM sessions WHERE expires_at > NOW()', time: '15ms', rows: '1' },
|
||||||
{ query: 'DELETE FROM temp_files WHERE created_at < ?', time: '23ms', rows: '47' }
|
{ query: 'DELETE FROM temp_files WHERE created_at < ?', time: '23ms', rows: '47' }
|
||||||
];
|
];
|
||||||
|
|
||||||
queries.forEach(q => {
|
queries.forEach(q => {
|
||||||
content += `\x1b[37m[\x1b[32m${q.time.padStart(5)}\x1b[37m]\x1b[0m \x1b[35m${q.query}\x1b[0m \x1b[90m(${q.rows} rows)\x1b[0m\r\n`;
|
content += `\x1b[37m[\x1b[32m${q.time.padStart(5)}\x1b[37m]\x1b[0m \x1b[35m${q.query}\x1b[0m \x1b[90m(${q.rows} rows)\x1b[0m\r\n`;
|
||||||
});
|
});
|
||||||
|
|
@ -299,7 +299,7 @@
|
||||||
{ method: 'POST', path: '/api/v1/orders', description: 'Create order', auth: 'Bearer' },
|
{ method: 'POST', path: '/api/v1/orders', description: 'Create order', auth: 'Bearer' },
|
||||||
{ method: 'GET', path: '/health', description: 'Health check', auth: 'None' }
|
{ method: 'GET', path: '/health', description: 'Health check', auth: 'None' }
|
||||||
];
|
];
|
||||||
|
|
||||||
endpoints.forEach(ep => {
|
endpoints.forEach(ep => {
|
||||||
const methodColor = ep.method === 'GET' ? '32' : ep.method === 'POST' ? '33' : ep.method === 'PUT' ? '34' : '31';
|
const methodColor = ep.method === 'GET' ? '32' : ep.method === 'POST' ? '33' : ep.method === 'PUT' ? '34' : '31';
|
||||||
const authColor = ep.auth === 'None' ? '90' : ep.auth === 'Admin' ? '31' : '36';
|
const authColor = ep.auth === 'None' ? '90' : ep.auth === 'Admin' ? '31' : '36';
|
||||||
|
|
@ -318,7 +318,7 @@
|
||||||
{ name: 'lodash', version: '^4.17.21', type: 'runtime' },
|
{ name: 'lodash', version: '^4.17.21', type: 'runtime' },
|
||||||
{ name: 'axios', version: '^1.4.0', type: 'runtime' }
|
{ name: 'axios', version: '^1.4.0', type: 'runtime' }
|
||||||
];
|
];
|
||||||
|
|
||||||
deps.forEach(dep => {
|
deps.forEach(dep => {
|
||||||
const typeColor = dep.type === 'dev' ? '90' : '32';
|
const typeColor = dep.type === 'dev' ? '90' : '32';
|
||||||
content += `\x1b[36m${dep.name.padEnd(15)}\x1b[0m \x1b[33m${dep.version.padEnd(12)}\x1b[0m \x1b[${typeColor}m${dep.type}\x1b[0m\r\n`;
|
content += `\x1b[36m${dep.name.padEnd(15)}\x1b[0m \x1b[33m${dep.version.padEnd(12)}\x1b[0m \x1b[${typeColor}m${dep.type}\x1b[0m\r\n`;
|
||||||
|
|
@ -380,7 +380,7 @@
|
||||||
// ASCII art
|
// ASCII art
|
||||||
content += '\x1b[1;31m🎭 ASCII Art:\x1b[0m\r\n';
|
content += '\x1b[1;31m🎭 ASCII Art:\x1b[0m\r\n';
|
||||||
content += '\x1b[33m ╭────────────────────────────────────╮\r\n';
|
content += '\x1b[33m ╭────────────────────────────────────╮\r\n';
|
||||||
content += '\x1b[33m │ \x1b[36m*\x1b[32m+\x1b[35m#\x1b[31mo \x1b[1;37mDOM Terminal Ready! \x1b[31mo\x1b[35m#\x1b[32m+\x1b[36m*\x1b[33m │\r\n';
|
content += '\x1b[33m │ \x1b[36m*\x1b[32m+\x1b[35m#\x1b[31mo \x1b[1;37mDOM Terminal Ready! \x1b[31mo\x1b[35m#\x1b[32m+\x1b[36m*\x1b[33m │\r\n';
|
||||||
content += '\x1b[33m ╰────────────────────────────────────╯\x1b[0m\r\n\r\n';
|
content += '\x1b[33m ╰────────────────────────────────────╯\x1b[0m\r\n\r\n';
|
||||||
|
|
||||||
// Interactive instructions
|
// Interactive instructions
|
||||||
|
|
@ -400,7 +400,7 @@
|
||||||
content += '\x1b[37m╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\x1b[0m\r\n';
|
content += '\x1b[37m╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\x1b[0m\r\n';
|
||||||
|
|
||||||
terminal.write(content);
|
terminal.write(content);
|
||||||
|
|
||||||
// Scroll to bottom after content is written
|
// Scroll to bottom after content is written
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (terminal.terminal && terminal.terminal.buffer) {
|
if (terminal.terminal && terminal.terminal.buffer) {
|
||||||
|
|
@ -410,7 +410,7 @@
|
||||||
terminal.renderBuffer();
|
terminal.renderBuffer();
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
console.log('Mock data generation completed!');
|
console.log('Mock data generation completed!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -145,10 +145,7 @@ export class Terminal extends LitElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
private measureCharacterWidth(): number {
|
private measureCharacterWidth(): number {
|
||||||
if (!this.container) {
|
if (!this.container) return 8;
|
||||||
console.log('measureCharacterWidth: no container, returning default 8px');
|
|
||||||
return 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create temporary element with same styles as terminal content, attached to container
|
// Create temporary element with same styles as terminal content, attached to container
|
||||||
const measureEl = document.createElement('div');
|
const measureEl = document.createElement('div');
|
||||||
|
|
@ -167,16 +164,12 @@ export class Terminal extends LitElement {
|
||||||
const testContent = testString.repeat(repeatCount).substring(0, this.cols);
|
const testContent = testString.repeat(repeatCount).substring(0, this.cols);
|
||||||
measureEl.textContent = testContent;
|
measureEl.textContent = testContent;
|
||||||
|
|
||||||
console.log(`measureCharacterWidth: measuring ${this.cols} chars with fontSize=${this.fontSize}px, testContent length: ${testContent.length}`);
|
|
||||||
|
|
||||||
// Attach to container so it inherits all the proper CSS context
|
// Attach to container so it inherits all the proper CSS context
|
||||||
this.container.appendChild(measureEl);
|
this.container.appendChild(measureEl);
|
||||||
const measureRect = measureEl.getBoundingClientRect();
|
const measureRect = measureEl.getBoundingClientRect();
|
||||||
const actualCharWidth = measureRect.width / this.cols;
|
const actualCharWidth = measureRect.width / this.cols;
|
||||||
this.container.removeChild(measureEl);
|
this.container.removeChild(measureEl);
|
||||||
|
|
||||||
console.log(`measureCharacterWidth: measureRect.width=${measureRect.width}px, cols=${this.cols}, calculated charWidth=${actualCharWidth}px`);
|
|
||||||
|
|
||||||
return actualCharWidth;
|
return actualCharWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -189,8 +182,6 @@ export class Terminal extends LitElement {
|
||||||
const containerHeight = this.container.clientHeight;
|
const containerHeight = this.container.clientHeight;
|
||||||
const targetCharWidth = containerWidth / this.cols;
|
const targetCharWidth = containerWidth / this.cols;
|
||||||
|
|
||||||
console.log(`Horizontal fit: container ${containerWidth}x${containerHeight}px, target charWidth=${targetCharWidth}px for ${this.cols} cols`);
|
|
||||||
|
|
||||||
// Calculate fontSize needed for target character width
|
// Calculate fontSize needed for target character width
|
||||||
// Use current font size as starting point and measure actual character width
|
// Use current font size as starting point and measure actual character width
|
||||||
const currentCharWidth = this.measureCharacterWidth();
|
const currentCharWidth = this.measureCharacterWidth();
|
||||||
|
|
@ -198,16 +189,12 @@ export class Terminal extends LitElement {
|
||||||
const calculatedFontSize = this.fontSize * scaleFactor;
|
const calculatedFontSize = this.fontSize * scaleFactor;
|
||||||
const newFontSize = Math.max(4, Math.min(32, calculatedFontSize));
|
const newFontSize = Math.max(4, Math.min(32, calculatedFontSize));
|
||||||
|
|
||||||
console.log(`Horizontal fit: currentCharWidth=${currentCharWidth}px, scaleFactor=${scaleFactor}, calculatedFontSize=${calculatedFontSize}px, clampedFontSize=${newFontSize}px`);
|
|
||||||
|
|
||||||
this.fontSize = newFontSize;
|
this.fontSize = newFontSize;
|
||||||
|
|
||||||
// Also fit rows to use full container height with the new font size
|
// Also fit rows to use full container height with the new font size
|
||||||
const lineHeight = this.fontSize * 1.2;
|
const lineHeight = this.fontSize * 1.2;
|
||||||
const fittedRows = Math.max(1, Math.floor(containerHeight / lineHeight));
|
const fittedRows = Math.max(1, Math.floor(containerHeight / lineHeight));
|
||||||
|
|
||||||
console.log(`Horizontal fit: lineHeight=${lineHeight}px, fittedRows=${fittedRows}`);
|
|
||||||
|
|
||||||
// Update both actualRows and the terminal's actual row count
|
// Update both actualRows and the terminal's actual row count
|
||||||
this.actualRows = fittedRows;
|
this.actualRows = fittedRows;
|
||||||
this.rows = fittedRows;
|
this.rows = fittedRows;
|
||||||
|
|
@ -216,16 +203,11 @@ export class Terminal extends LitElement {
|
||||||
if (this.terminal) {
|
if (this.terminal) {
|
||||||
this.terminal.resize(this.cols, this.rows);
|
this.terminal.resize(this.cols, this.rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(
|
|
||||||
`Horizontal fit: FINAL fontSize ${this.fontSize}px, ${this.cols}x${this.rows} in ${containerWidth}x${containerHeight}px`
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// Normal mode: just calculate how many rows fit in the viewport
|
// Normal mode: just calculate how many rows fit in the viewport
|
||||||
const containerHeight = this.container.clientHeight;
|
const containerHeight = this.container.clientHeight;
|
||||||
const lineHeight = this.fontSize * 1.2;
|
const lineHeight = this.fontSize * 1.2;
|
||||||
this.actualRows = Math.max(1, Math.floor(containerHeight / lineHeight));
|
this.actualRows = Math.max(1, Math.floor(containerHeight / lineHeight));
|
||||||
console.log(`Viewport fits ${this.actualRows} rows`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue