diff --git a/src/styles/theme.css b/src/styles/theme.css index 12b1f5ad..c907d9e1 100644 --- a/src/styles/theme.css +++ b/src/styles/theme.css @@ -217,6 +217,30 @@ max-width: 100vw; } + /* + * Monospace text is what learners read and type character by character. + * JetBrains Mono ships programming ligatures (contextual alternates): `--` + * is drawn as one continuous line, `->` as an arrow, `!=` as `≠`. In a + * terminal course that hides the very characters being taught — a learner + * typing `--no-ff` saw one of the two dashes "disappear" as soon as the + * space was typed, and had to paste the command to succeed (reported + * 24 September 2026). The property is inherited, so this covers the + * terminal output, its input and every code block or inline code chip. + * + * Known limit: WebKit (Safari macOS / iOS) is documented as unable to turn + * off contextual alternates from CSS. Verified in Chrome only; if Safari + * still merges `--`, the fix is a ligature-free font (JetBrains Mono NL), + * tracked in THI-354. + */ + .font-mono, + code, + kbd, + samp, + pre { + font-variant-ligatures: none; + font-feature-settings: "liga" 0, "calt" 0; + } + /** * Default typography styles for HTML elements (h1-h4, p, label, button, input). * These are in @layer base, so Tailwind utility classes (like text-sm, text-lg) automatically override them. diff --git a/src/test/monospaceLigatures.test.ts b/src/test/monospaceLigatures.test.ts new file mode 100644 index 00000000..f1e309b1 --- /dev/null +++ b/src/test/monospaceLigatures.test.ts @@ -0,0 +1,24 @@ +/** + * Regression guard (24 September 2026): JetBrains Mono draws `--` as one + * continuous line, so a learner typing `--no-ff` saw a dash "disappear" and had + * to paste the command. jsdom does not apply stylesheets, so this checks the + * rule itself; the visual effect was verified in Chrome. + */ +import { describe, it, expect } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +const css = readFileSync(resolve(__dirname, '../styles/theme.css'), 'utf8'); + +describe('monospace text shows every character as typed', () => { + it('JetBrains Mono is the monospace font (the reason this rule exists)', () => { + expect(css).toMatch(/--font-mono:\s*'JetBrains Mono'/); + }); + + it('disables ligatures and contextual alternates for .font-mono, code, kbd, samp and pre', () => { + const rule = css.match(/\.font-mono,\s*code,\s*kbd,\s*samp,\s*pre\s*\{([^}]*)\}/); + expect(rule, 'monospace ligature rule missing from theme.css').not.toBeNull(); + expect(rule![1]).toMatch(/font-variant-ligatures:\s*none/); + expect(rule![1]).toMatch(/"calt"\s*0/); + }); +});