Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/styles/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
24 changes: 24 additions & 0 deletions src/test/monospaceLigatures.test.ts
Original file line number Diff line number Diff line change
@@ -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/);
});
});
Loading