fix: restore jlpt subtitle underlines

This commit is contained in:
2026-04-26 19:21:28 -07:00
parent b10a7b3e98
commit af86ce2341
4 changed files with 106 additions and 0 deletions

View File

@@ -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
<!-- SECTION:DESCRIPTION:BEGIN -->
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.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [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.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
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.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
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`.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
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`.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -0,0 +1,4 @@
type: fixed
area: overlay
- Overlay: Restored persistent JLPT subtitle underlines while keeping hover JLPT labels and annotation color priority intact.

View File

@@ -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(

View File

@@ -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`);