Typography round 2 (owner): lh-input (caret at the font-metric floor) + 14px session titles

- lh-input: lh-prose's 1.5 reading leading made typing carets tower
  (~25px box on 16.5px text, ~2.1× the glyphs — the caret fills the line
  box in Chromium). Inputs are not paragraphs: 1.25 leading drops the
  caret to ~21px, the font-metric floor (as close to the glyphs as
  Chromium will draw). Swapped on every typing surface — composer
  (chat + home), the thinking field, answer + editable-Other inputs, the
  model search; rendered prose (message.tsx container, option
  descriptions, chat.tsx pre-wrap) keeps lh-prose 1.5. Same family and
  size as prose; only leading differs.
- Session titles: 14px (text-base) — Source Sans at 12.5px read as
  small as the 11px mono meta under them; the title now holds its rank.
  Live: title "Source Sans 3" 14px vs meta "JetBrains Mono" 11px, row
  52px; composer line box 20.625px (was 24.75px).

Gates: typecheck, lint, 469/469.
This commit is contained in:
amosblomqvist
2026-09-17 13:15:35 +03:00
parent 1b7dbe2898
commit 5e6d5f124c
6 changed files with 20 additions and 7 deletions
+13
View File
@@ -164,6 +164,19 @@ body {
color: var(--lh-ink-italic); color: var(--lh-ink-italic);
} }
/* lh-input (owner, typography round 2): lh-prose's 1.5 READING leading
makes typing carets tower over the glyphs — the caret fills the line
box (24.75px at 16.5px text, ~2.1× the glyph height). Inputs are not
paragraphs: a tighter 1.25 leading brings the caret to the font-metric
floor (~21px, as close to the glyphs as Chromium will draw it) while
wrapped lines in growing textareas still read fine. Rendered prose keeps
1.5 — the two share family and size, they differ only in leading. */
.lh-input {
font-family: var(--font-prose);
font-size: var(--lh-prose-size);
line-height: 1.25;
}
/* Thin dark native scrollbar (app-wide): dark thumb, transparent track. */ /* Thin dark native scrollbar (app-wide): dark thumb, transparent track. */
* { * {
scrollbar-width: thin; scrollbar-width: thin;
+1 -1
View File
@@ -59,7 +59,7 @@ export function Composer({
placeholder="Ask, answer, or steer the lesson…" placeholder="Ask, answer, or steer the lesson…"
autoFocus={autoFocus} autoFocus={autoFocus}
rows={1} rows={1}
className="field-sizing-content lh-prose max-h-48 min-h-10 flex-1 resize-none bg-transparent px-2.5 py-2 outline-none placeholder:text-muted-foreground" className="field-sizing-content lh-input max-h-48 min-h-10 flex-1 resize-none bg-transparent px-2.5 py-2 outline-none placeholder:text-muted-foreground"
/> />
{verdict && !busy ? ( {verdict && !busy ? (
// The plan verdict (docs/probe-teach-split.md §4): accept // The plan verdict (docs/probe-teach-split.md §4): accept
+1 -1
View File
@@ -91,7 +91,7 @@ export function Home() {
placeholder="What do you want to learn?" placeholder="What do you want to learn?"
autoFocus autoFocus
rows={1} rows={1}
className="field-sizing-content lh-prose max-h-48 min-h-10 flex-1 resize-none bg-transparent px-2.5 py-2 outline-none placeholder:text-muted-foreground" className="field-sizing-content lh-input max-h-48 min-h-10 flex-1 resize-none bg-transparent px-2.5 py-2 outline-none placeholder:text-muted-foreground"
/> />
<button <button
type="submit" type="submit"
+1 -1
View File
@@ -222,7 +222,7 @@ export function ModelControl({
value={query} value={query}
onChange={(e) => setQuery(e.target.value)} onChange={(e) => setQuery(e.target.value)}
placeholder="Search models…" placeholder="Search models…"
className="lh-prose mb-1.5 w-full rounded-lg border bg-background px-2 py-1.5 text-foreground outline-none focus:ring-1 focus:ring-ring" className="lh-input mb-1.5 w-full rounded-lg border bg-background px-2 py-1.5 text-foreground outline-none focus:ring-1 focus:ring-ring"
/> />
{error && <div className="mb-1.5 text-destructive">{error}</div>} {error && <div className="mb-1.5 text-destructive">{error}</div>}
{!catalog && ( {!catalog && (
+3 -3
View File
@@ -387,7 +387,7 @@ function QuestionDockForm({
setSel((prev) => (prev.otherSelected ? prev : toggleOther(selectMode, prev))); setSel((prev) => (prev.otherSelected ? prev : toggleOther(selectMode, prev)));
}} }}
placeholder="Type your own answer…" placeholder="Type your own answer…"
className="lh-prose w-full min-w-0 flex-1 bg-transparent outline-none placeholder:text-muted-foreground" className="lh-input w-full min-w-0 flex-1 bg-transparent outline-none placeholder:text-muted-foreground"
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
/> />
</div> </div>
@@ -429,7 +429,7 @@ function QuestionDockForm({
}} }}
placeholder="Type your answer…" placeholder="Type your answer…"
rows={1} rows={1}
className="field-sizing-content lh-prose max-h-48 min-h-10 w-full resize-none bg-transparent px-2.5 py-2 outline-none placeholder:text-muted-foreground" className="field-sizing-content lh-input max-h-48 min-h-10 w-full resize-none bg-transparent px-2.5 py-2 outline-none placeholder:text-muted-foreground"
/> />
) : ( ) : (
<textarea <textarea
@@ -446,7 +446,7 @@ function QuestionDockForm({
placeholder="Why this answer? What are you unsure about? Say as much as you like — the teacher reads all of this." placeholder="Why this answer? What are you unsure about? Say as much as you like — the teacher reads all of this."
rows={1} rows={1}
className={cn( className={cn(
"field-sizing-content lh-prose max-h-48 w-full resize-none bg-transparent px-2.5 py-2 outline-none placeholder:text-muted-foreground/70", "field-sizing-content lh-input max-h-48 w-full resize-none bg-transparent px-2.5 py-2 outline-none placeholder:text-muted-foreground/70",
// Phase-scaled prominence: teaching invites depth, // Phase-scaled prominence: teaching invites depth,
// probing stays quick. // probing stays quick.
phase === "teach" ? "min-h-[4.5rem]" : "min-h-10", phase === "teach" ? "min-h-[4.5rem]" : "min-h-10",
+1 -1
View File
@@ -138,7 +138,7 @@ export function SessionRow({
the meta line below stays JetBrains Mono (machine data). One the meta line below stays JetBrains Mono (machine data). One
rule, finally applied to the picker too (owner, typography rule, finally applied to the picker too (owner, typography
round): mono = chrome, Source Sans = human words. */} round): mono = chrome, Source Sans = human words. */}
<span className="block truncate font-prose">{chat.title}</span> <span className="block truncate font-prose text-base">{chat.title}</span>
<span className="block text-xs text-muted-foreground">{sessionMeta(chat)}</span> <span className="block text-xs text-muted-foreground">{sessionMeta(chat)}</span>
</span> </span>
</button> </button>