diff --git a/public/index.html b/public/index.html
index ff0fa3e..e3aa9e9 100644
--- a/public/index.html
+++ b/public/index.html
@@ -10,7 +10,6 @@
--columns: 1;
--font-size: 12px;
--terminal-height: 400px;
- --wrap-mode: pre-wrap;
}
* {
@@ -243,12 +242,28 @@
font-family: "SF Mono", Monaco, "Cascadia Code", "Courier New", monospace;
font-size: var(--font-size);
line-height: 1.3;
- white-space: var(--wrap-mode);
- word-wrap: normal;
- overflow-wrap: normal;
color: #e0e0e0;
}
+ /* Default: wrap mode - text wraps to fit container */
+ .terminal.wrap-mode {
+ white-space: break-spaces;
+ word-break: break-word;
+ overflow-wrap: anywhere;
+ }
+
+ /* Scroll mode - horizontal scroll, no wrapping */
+ .terminal.scroll-mode {
+ white-space: pre;
+ overflow-x: auto;
+ }
+
+ /* Box-drawing lines (detected via JS) should never wrap */
+ .terminal .rule-line {
+ white-space: pre;
+ display: block;
+ }
+
.prompt {
background: #2a2a2a;
border: 2px solid #ff9800;
@@ -1020,7 +1035,7 @@
columns: 1, // 1, 2, 3, or 'fit'
fontSize: 12, // px value
terminalHeight: 400, // px value
- wrapText: false, // true = pre-wrap, false = pre (horizontal scroll)
+ wrapText: true, // true = wrap (mobile-friendly), false = scroll
},
};
@@ -1458,15 +1473,50 @@
title="Scroll to bottom">
↓
-
${s.output}
+ ${s.output}
`).join('');
- // Initialize scroll handlers and button visibility
+ // Initialize scroll handlers, button visibility, and mark rule lines
sessionsToRender.forEach(s => {
initSessionOutputUI(s.id);
updateScrollButton(s.id);
+ // Mark box-drawing lines on initial render
+ if (s.expanded) {
+ const $output = document.getElementById(`output-${s.id}`);
+ if ($output) markRuleLines($output);
+ }
+ });
+ }
+
+ // Box-drawing character range: U+2500 to U+257F
+ const BOX_DRAWING_REGEX = /[\u2500-\u257F]/g;
+
+ /**
+ * Check if a line is primarily box-drawing characters (>= 60%)
+ * Used to prevent wrapping on separator/border lines
+ */
+ function isRuleLine(text) {
+ if (!text || text.length < 3) return false;
+ const visibleChars = text.replace(/\s/g, '');
+ if (visibleChars.length < 3) return false;
+ const boxChars = (visibleChars.match(BOX_DRAWING_REGEX) || []).length;
+ return boxChars / visibleChars.length >= 0.6;
+ }
+
+ /**
+ * Process terminal output to mark box-drawing lines as rule-lines
+ * This prevents wrapping on separator/border lines
+ */
+ function markRuleLines($terminal) {
+ // xterm serializeAsHTML outputs content in divs (one per row)
+ // Each div may contain spans for styling
+ const rows = $terminal.querySelectorAll(':scope > div');
+ rows.forEach(row => {
+ if (isRuleLine(row.textContent)) {
+ row.classList.add('rule-line');
+ }
});
}
@@ -1483,6 +1533,8 @@
// Only mutate DOM when expanded to reduce work
if (session.expanded) {
$output.innerHTML = session.output;
+ // Mark box-drawing lines to prevent wrapping
+ markRuleLines($output);
}
session.outputRenderedLength = session.output.length;
@@ -1979,8 +2031,16 @@
// Apply terminal height
root.style.setProperty('--terminal-height', `${state.settings.terminalHeight}px`);
- // Apply wrap mode
- root.style.setProperty('--wrap-mode', state.settings.wrapText ? 'pre-wrap' : 'pre');
+ // Apply wrap mode to all terminal elements
+ document.querySelectorAll('.terminal').forEach(el => {
+ if (state.settings.wrapText) {
+ el.classList.add('wrap-mode');
+ el.classList.remove('scroll-mode');
+ } else {
+ el.classList.add('scroll-mode');
+ el.classList.remove('wrap-mode');
+ }
+ });
}
function updateSettingsUI() {
diff --git a/src/server.ts b/src/server.ts
index 6f6b495..71f7485 100644
--- a/src/server.ts
+++ b/src/server.ts
@@ -458,8 +458,9 @@ const server = Bun.serve({
// Initialize in-memory session state
sessionStates.set(session.id, createDefaultSessionState());
- // Create terminal emulator with default size (will be resized later)
- const termSession = createTerminal(80, 24);
+ // Create terminal emulator wide enough to avoid premature wrapping
+ // Browser CSS handles responsive display; we just need ANSI processing
+ const termSession = createTerminal(300, 50);
sessionTerminals.set(session.id, termSession);
console.debug(