From af86ce2341bdc2a01dc74ac1882dc83928981b49 Mon Sep 17 00:00:00 2001 From: sudacode Date: Sun, 26 Apr 2026 19:21:28 -0700 Subject: [PATCH] fix: restore jlpt subtitle underlines --- ...ore-persistent-JLPT-subtitle-underlines.md | 54 +++++++++++++++++++ changes/308-jlpt-underlines.md | 4 ++ src/renderer/style.css | 40 ++++++++++++++ src/renderer/subtitle-render.test.ts | 8 +++ 4 files changed, 106 insertions(+) create mode 100644 backlog/tasks/task-308 - Restore-persistent-JLPT-subtitle-underlines.md create mode 100644 changes/308-jlpt-underlines.md diff --git a/backlog/tasks/task-308 - Restore-persistent-JLPT-subtitle-underlines.md b/backlog/tasks/task-308 - Restore-persistent-JLPT-subtitle-underlines.md new file mode 100644 index 00000000..3ca96fd8 --- /dev/null +++ b/backlog/tasks/task-308 - Restore-persistent-JLPT-subtitle-underlines.md @@ -0,0 +1,54 @@ +--- +id: TASK-308 +title: Restore persistent JLPT subtitle underlines +status: Done +assignee: + - Codex +created_date: '2026-04-27 02:03' +updated_date: '2026-04-27 02:07' +labels: + - overlay + - jlpt + - renderer +dependencies: [] +priority: medium +--- + +## Description + + +JLPT tagging currently exposes the JLPT level on hover, but the persistent subtitle underline is missing. When JLPT annotation is enabled and a rendered subtitle token has a JLPT level, users should see the configured JLPT color underline without needing to hover. + + +## Acceptance Criteria + +- [x] #1 JLPT-tagged subtitle tokens render a persistent underline for N1-N5 levels when JLPT tagging is enabled. +- [x] #2 Hover and keyboard-selected JLPT labels continue to appear for tagged tokens. +- [x] #3 Higher-priority annotation colors such as known words, N+1, names, and frequency styling are not overridden by JLPT text color. +- [x] #4 Regression coverage verifies the CSS contract for persistent JLPT underlines. + + +## Implementation Plan + + +1. Add a focused renderer CSS regression asserting each `word-jlpt-n*` class provides persistent underline decoration while preserving existing typography constraints. +2. Run the focused renderer test to confirm the regression fails before production changes. +3. Restore underline CSS for JLPT classes without broadening JLPT text-color precedence over known/N+1/name/frequency tokens. +4. Re-run the focused renderer test and update acceptance criteria/task notes. + + +## Implementation Notes + + +Verified red/green regression: tightened `src/renderer/subtitle-render.test.ts` first failed because base `word-jlpt-n*` selectors had no underline decoration, then passed after moving JLPT underline decoration to unconditional base selectors while leaving JLPT text color priority-scoped. + +Checks: `bun test src/renderer/subtitle-render.test.ts`; `bun run changelog:lint`; `bun run typecheck`. + + +## Final Summary + + +Restored persistent JLPT subtitle underlines by adding underline decoration to each base `word-jlpt-n*` renderer CSS class. JLPT text color remains in the existing priority-scoped selectors, so known/N+1/name/frequency coloring is not overridden while the underline still appears on any JLPT-tagged token. + +Updated renderer CSS regression coverage to assert underline decoration for N1-N5 and added a fixed changelog fragment. Verified with `bun test src/renderer/subtitle-render.test.ts`, `bun run changelog:lint`, and `bun run typecheck`. + diff --git a/changes/308-jlpt-underlines.md b/changes/308-jlpt-underlines.md new file mode 100644 index 00000000..61d41b17 --- /dev/null +++ b/changes/308-jlpt-underlines.md @@ -0,0 +1,4 @@ +type: fixed +area: overlay + +- Overlay: Restored persistent JLPT subtitle underlines while keeping hover JLPT labels and annotation color priority intact. diff --git a/src/renderer/style.css b/src/renderer/style.css index c6e3a116..53a50524 100644 --- a/src/renderer/style.css +++ b/src/renderer/style.css @@ -793,6 +793,14 @@ body.settings-modal-open [data-subminer-yomitan-popup-host='true'] { color: var(--subtitle-name-match-color, #f5bde6); } +#subtitleRoot .word.word-jlpt-n1 { + text-decoration-line: underline; + text-decoration-thickness: 2px; + text-underline-offset: 4px; + text-decoration-color: var(--subtitle-jlpt-n1-color, #ed8796); + text-decoration-style: solid; +} + #subtitleRoot .word.word-jlpt-n1:not( :is( @@ -814,6 +822,14 @@ body.settings-modal-open [data-subminer-yomitan-popup-host='true'] { color: var(--subtitle-jlpt-n1-color, #ed8796); } +#subtitleRoot .word.word-jlpt-n2 { + text-decoration-line: underline; + text-decoration-thickness: 2px; + text-underline-offset: 4px; + text-decoration-color: var(--subtitle-jlpt-n2-color, #f5a97f); + text-decoration-style: solid; +} + #subtitleRoot .word.word-jlpt-n2:not( :is( @@ -835,6 +851,14 @@ body.settings-modal-open [data-subminer-yomitan-popup-host='true'] { color: var(--subtitle-jlpt-n2-color, #f5a97f); } +#subtitleRoot .word.word-jlpt-n3 { + text-decoration-line: underline; + text-decoration-thickness: 2px; + text-underline-offset: 4px; + text-decoration-color: var(--subtitle-jlpt-n3-color, #f9e2af); + text-decoration-style: solid; +} + #subtitleRoot .word.word-jlpt-n3:not( :is( @@ -856,6 +880,14 @@ body.settings-modal-open [data-subminer-yomitan-popup-host='true'] { color: var(--subtitle-jlpt-n3-color, #f9e2af); } +#subtitleRoot .word.word-jlpt-n4 { + text-decoration-line: underline; + text-decoration-thickness: 2px; + text-underline-offset: 4px; + text-decoration-color: var(--subtitle-jlpt-n4-color, #a6e3a1); + text-decoration-style: solid; +} + #subtitleRoot .word.word-jlpt-n4:not( :is( @@ -877,6 +909,14 @@ body.settings-modal-open [data-subminer-yomitan-popup-host='true'] { color: var(--subtitle-jlpt-n4-color, #a6e3a1); } +#subtitleRoot .word.word-jlpt-n5 { + text-decoration-line: underline; + text-decoration-thickness: 2px; + text-underline-offset: 4px; + text-decoration-color: var(--subtitle-jlpt-n5-color, #8aadf4); + text-decoration-style: solid; +} + #subtitleRoot .word.word-jlpt-n5:not( :is( diff --git a/src/renderer/subtitle-render.test.ts b/src/renderer/subtitle-render.test.ts index a8df27d7..2ed6ee3a 100644 --- a/src/renderer/subtitle-render.test.ts +++ b/src/renderer/subtitle-render.test.ts @@ -901,6 +901,14 @@ test('subtitle annotation CSS changes token color without overriding typography' for (let level = 1; level <= 5; level += 1) { const plainJlptBlock = extractClassBlock(cssText, `#subtitleRoot .word.word-jlpt-n${level}`); assert.doesNotMatch(plainJlptBlock, /(?:^|\n)\s*color\s*:/m); + assert.match(plainJlptBlock, /text-decoration-line:\s*underline;/); + assert.match(plainJlptBlock, /text-decoration-thickness:\s*2px;/); + assert.match(plainJlptBlock, /text-underline-offset:\s*4px;/); + assert.match( + plainJlptBlock, + new RegExp(`text-decoration-color:\\s*var\\(--subtitle-jlpt-n${level}-color,`), + ); + assert.match(plainJlptBlock, /text-decoration-style:\s*solid;/); const block = extractClassBlock(cssText, buildJlptColorSelector(level)); assert.ok(block.length > 0, `word-jlpt-n${level} class should exist`);