Skip to content

Commit 286e368

Browse files
dreamrecclaude
andcommitted
add: chat font-size toggle (small/large) with retro glyph control (PR-27)
The v2.0 hero feature. A two-state toggle at the far right of the chat status bar — `a` (lowercase, 9px) for small mode, `A` (uppercase, 13px) for large mode. The active size lights up in `--accent`, the inactive in `--muted`. Click swaps; Cmd/Ctrl+`+` / `-` work as keyboard shortcuts independent of browser zoom. Design choices (per v2.0_plan.md §3.1 + §8 defaults): - Two states only, not three. The glyph sizes ARE the legend; no "small / large" labels required. - Lowercase + uppercase pair (`a A`), matching the existing CRT-ish glyph vocabulary (▰▰▰ wave, █ blink cursor, [ ... ] brackets). - Placement: far right of the status bar, AFTER the existing `[ idle · model · tokens · █ ]` group. The toggle sits OUTSIDE the bracket pseudo-elements so it reads as a separate control. - Persistence: `localStorage["tdpilot.fontMode"]`, applied before `$history.innerHTML = WELCOME_HTML;` so users with a saved 'large' setting never see a small-text flash on reload. - Default: `small` (today's behavior). Existing users see no change unless they opt in. - Scope: scales `#history` (13px → 17px) + `#input` (13px → 17px) only. Status bar, buttons, modal headers, badges all stay at their designed scale so the layout doesn't reflow. Layout note: adding the toggle as a 3rd flex child of #status-bar (which uses justify-content: space-between) would push `.rhs` to the middle. `.rhs` now carries `margin-left: auto` to absorb free space and keep the right-edge cluster intact. No-op for the pre-PR-27 2-child layout. Implementation footprint: - `td_component/tdpilot_api_chat.html` — +79 LOC across HTML (button + 2 spans), CSS (~35 LOC of toggle/scaling rules), and JS (FONT_KEY + applyFontMode() + initial restoration + click handler + 2 keyboard-shortcut branches in the existing document-scope shortcuts handler). - No backend changes, no WS protocol changes, no new deps. Tests: `tests/test_chat_html_font_toggle.py` — 21 structural assertions following `tests/test_chat_html_state.py`'s served-HTML-as-string pattern. Pins the DOM (button + glyphs + status-bar order), the CSS (scaling rules, active-state colors, chrome stays fixed, .rhs has margin-left: auto), and the JS (localStorage namespacing + try/catch guards, restoration order, click handler logic, both keyboard shortcuts, default-to-small fallback). Pytest: 1623 pass / 12 deselected (delta +21 from 1602). Standalone .tox (`tdpilot_API.tox`) carries the baked v1.10.0 chat HTML; rebuild on main as a follow-up commit before v2.0 ships, mirroring the v1.10.0 release pattern (commit 7456eb5). The freshness gate doesn't track the standalone tox today. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 7e32054 commit 286e368

2 files changed

Lines changed: 369 additions & 0 deletions

File tree

td_component/tdpilot_api_chat.html

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@
9898
display: inline-flex;
9999
align-items: center;
100100
gap: 6px;
101+
/* PR-27: keeps .rhs (and the font toggle that follows it as a
102+
sibling) clustered at the right edge in the 3-child status bar.
103+
No-op for the pre-PR-27 2-child layout. */
104+
margin-left: auto;
101105
}
102106
#status-bar .rhs::before {
103107
content: "[";
@@ -145,6 +149,42 @@
145149
visibility: hidden;
146150
}
147151
#status-bar.working .cursor { visibility: visible; }
152+
/* PR-27 (v2.0) — chat font-size toggle. Two-state glyph control
153+
(`a` / `A`) at the far right of the status bar; the active size
154+
lights up in --accent, the inactive in --muted. Click swaps the
155+
size class on <html>; the body has no other knowledge of the
156+
toggle, so the rest of the chat keeps its existing scale.
157+
Defaults to small (today's behavior) so existing users see no
158+
change unless they opt in. */
159+
#font-toggle {
160+
background: transparent;
161+
border: 1px solid var(--rule);
162+
color: var(--muted);
163+
font-family: inherit;
164+
padding: 1px 7px 2px;
165+
margin-left: 10px;
166+
cursor: pointer;
167+
display: inline-flex;
168+
align-items: baseline;
169+
gap: 3px;
170+
letter-spacing: 0; /* override status-bar uppercase tracking */
171+
text-transform: none; /* lowercase 'a' must stay lowercase */
172+
line-height: 1;
173+
}
174+
#font-toggle:hover {
175+
border-color: var(--accent);
176+
background: var(--bg-3);
177+
}
178+
#font-toggle .fs-a { font-size: 9px; color: var(--muted); }
179+
#font-toggle .fs-A { font-size: 13px; color: var(--muted); }
180+
:root.fs-small #font-toggle .fs-a { color: var(--accent); }
181+
:root.fs-large #font-toggle .fs-A { color: var(--accent); }
182+
/* Scales chat content + input only — chrome (status bar, buttons,
183+
modal headers, badges) stays fixed at its designed scale so the
184+
layout doesn't reflow awkwardly. Tool-call summaries inherit
185+
from #history so they scale automatically. */
186+
:root.fs-large #history { font-size: 17px; }
187+
:root.fs-large #input { font-size: 17px; }
148188
@keyframes blink {
149189
0%, 49% { opacity: 1; }
150190
50%, 100% { opacity: 0; }
@@ -670,6 +710,11 @@
670710
<span class="rhs">
671711
<span id="status-text">idle</span><span id="model-badge"></span><span id="token-meter"></span><span class="cursor"></span>
672712
</span>
713+
<button id="font-toggle" type="button"
714+
title="Toggle chat font size (small / large)"
715+
aria-label="Toggle chat font size">
716+
<span class="fs-a">a</span><span class="fs-A">A</span>
717+
</button>
673718
</div>
674719
<!--
675720
History is intentionally empty on initial DOM. The IIFE injects the
@@ -758,6 +803,27 @@ <h4>quickstart</h4>
758803
const $modelBadge = document.getElementById('model-badge');
759804
const $tokenMeter = document.getElementById('token-meter');
760805
const $scrollJump = document.getElementById('scroll-jump');
806+
const $fontToggle = document.getElementById('font-toggle');
807+
808+
// PR-27 (v2.0) — chat font-size toggle. Two states; the active one
809+
// is reflected as a class on <html>. Persisted per-browser via
810+
// localStorage so the choice survives reloads. Restored before any
811+
// history rendering happens so users with a saved 'large' setting
812+
// never see a small-text flash on page load.
813+
const FONT_KEY = 'tdpilot.fontMode';
814+
function applyFontMode(mode) {
815+
if (mode !== 'small' && mode !== 'large') mode = 'small';
816+
document.documentElement.classList.remove('fs-small', 'fs-large');
817+
document.documentElement.classList.add('fs-' + mode);
818+
try { localStorage.setItem(FONT_KEY, mode); } catch (_) { /* private mode */ }
819+
}
820+
let _savedFontMode = 'small';
821+
try { _savedFontMode = localStorage.getItem(FONT_KEY) || 'small'; } catch (_) { /* private mode */ }
822+
applyFontMode(_savedFontMode);
823+
$fontToggle.addEventListener('click', () => {
824+
const cur = document.documentElement.classList.contains('fs-large') ? 'large' : 'small';
825+
applyFontMode(cur === 'large' ? 'small' : 'large');
826+
});
761827

762828
// Render the initial welcome from the same template fullSync uses, so
763829
// the wizard DOM is guaranteed present.
@@ -1822,6 +1888,19 @@ <h4>quickstart</h4>
18221888
$input.focus();
18231889
return;
18241890
}
1891+
// PR-27 (v2.0) — Cmd/Ctrl + `+` / `-` toggles chat font size
1892+
// independently of the browser-native zoom (which still works).
1893+
// `=` matches `+` for the unshifted keycap on US/EU layouts.
1894+
if (cmd && (e.key === '+' || e.key === '=')) {
1895+
e.preventDefault();
1896+
applyFontMode('large');
1897+
return;
1898+
}
1899+
if (cmd && e.key === '-') {
1900+
e.preventDefault();
1901+
applyFontMode('small');
1902+
return;
1903+
}
18251904
// End — jump to bottom of history. Useful after scrolling up to
18261905
// read older content.
18271906
if (e.key === 'End' && !inInput) {

0 commit comments

Comments
 (0)