Skip to content

feat: monorepo migration and Netflix/YouTube video overlay - #2

Merged
conoremclaughlin merged 33 commits into
mainfrom
feat/monorepo-and-feature-parity
Jun 25, 2026
Merged

feat: monorepo migration and Netflix/YouTube video overlay#2
conoremclaughlin merged 33 commits into
mainfrom
feat/monorepo-and-feature-parity

Conversation

@conoremclaughlin

@conoremclaughlin conoremclaughlin commented Apr 3, 2026

Copy link
Copy Markdown
Owner

Summary

  • Monorepo migration: Restructures the repo into packages/extension and packages/web using npm workspaces
  • Full Netflix/YouTube video overlay: Center subtitles, right panel transcript, settings menu, progress bar, auto-pause, seek, keyboard shortcuts
  • Video subtitle features: Greedy longest-match tokenization, transliteration (pinyin/zhuyin ruby annotations), double subtitles with auto-translation, subtitle pre-caching for instant hover
  • Hover UX: Subtitle hover always shows definitions regardless of hover key setting; regular web page text respects the hover key
  • Hover popup overhaul: Tone-colored characters + transliteration, stable positioning, dark mode, semicolon-separated definitions
  • Popup/Settings/Bookmarks pages: Full React UI with popup, bookmarks (words/sentences tabs, CSV export), and settings (hover key, tone colors, transliteration)
  • New message types: search/batch for single-round-trip subtitle pre-caching
  • YouTube auto-translation: Falls back to tlang parameter for translated subtitles
  • Debug builds: DEBUG=true npm run build enables inline source maps
  • Marketing site skeleton: Next.js static export at packages/web
  • Tests: E2E tests for hover popup and popup features, unit tests for settings/VTT parser/tone coloring

Test plan

  • Install extension from dist/chrome-mv3, verify hover popup on regular Chinese/Korean web pages
  • Netflix: verify subtitles render, settings menu, right panel, auto-pause, transliteration toggle, double subtitles
  • YouTube: verify subtitles render, settings menu, auto-translation language selector
  • Hover key setting (ctrl/alt/etc) gates web page hover but NOT subtitle hover
  • npm test passes
  • npm run build produces clean output without source maps
  • DEBUG=true npm run build includes inline source maps

🤖 Generated with Claude Code

conoremclaughlin and others added 5 commits April 3, 2026 16:08
Move extension to packages/extension/, add root workspace config,
update AGENTS.md with new paths. Extension output now at dist/ instead
of .output/ for easier Finder access when loading unpacked.

Co-Authored-By: Wren <noreply@anthropic.com>
Static export via next build, React 19 + Tailwind CSS 4, basic
landing page. Deployable to GitHub Pages or Vercel.

Co-Authored-By: Wren <noreply@anthropic.com>
- Hover popup: tone coloring, semicolons, pinyin conversion, dark mode,
  dismiss on click/escape, character set
- Popup features: transliteration controls, character set radios, font
  size labels, hover key, dark mode toggle, no version footer

Co-Authored-By: Wren <noreply@anthropic.com>
Co-Authored-By: Wren <noreply@anthropic.com>
Hover popup:
- Position anchored to text bounding rect (bottom-start, 10px gap)
- Stays fixed while hovering same character (no jitter)
- Flips alignment when near right viewport edge
- Bumped min font size for size 1

Popup:
- Remove focus outline from range sliders
- Add Feedback link pointing to GitHub issues

Co-Authored-By: Wren <noreply@anthropic.com>
@vercel

vercel Bot commented Apr 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
inkah Error Error Apr 14, 2026 3:04am
inkah-api Error Error Apr 14, 2026 3:04am
inkah-awkn Error Error Apr 14, 2026 3:04am

…buttons

Replace plain text X/Remove with Lucide React icons (X, Trash2, Download,
Bookmark, Settings, MessageSquare) across popup, bookmarks, and settings.

Co-Authored-By: Wren <noreply@anthropic.com>
Co-Authored-By: Wren <noreply@anthropic.com>
- Netflix service: injects script to hook JSON.parse/stringify,
  captures subtitle track URLs, fetches WebVTT data
- YouTube service: hooks XMLHttpRequest.open to capture timedtext
  API calls, fetches VTT subtitle data
- Subtitle renderer: hides native subs, renders hoverable tokenized
  overlay synced to video time, each word triggers dictionary lookup
- Selection mode: select CJK text on any page to trigger lookup
- Save shortcut: press 's' or 'b' while popup visible to save word
  with auto-detected source type (netflix/youtube/others)
- Netflix dark mode auto-detection
- VTT parser with unit tests
- E2E tests for Netflix/YouTube (credentials via env vars)

Co-Authored-By: Wren <noreply@anthropic.com>
…erception

Inline script injection is blocked by CSP on Netflix/YouTube. Move all
page-context API hooks (JSON.parse, XMLHttpRequest.open, history.*) to a
dedicated MAIN world content script (video-injector.content.ts).

Also rename bookmarks entrypoint to 'saved' to prevent WXT from
registering it as chrome_url_overrides.bookmarks.

Co-Authored-By: Wren <noreply@anthropic.com>
…nents

Replace simple SubtitleRenderer with complete VideoController:
- Center subtitle overlay: tokenized hoverable words, background toggle,
  synced to video time via requestAnimationFrame
- Right panel: full subtitle list with auto-scrolling, current highlight,
  click-to-seek, toggle on/off via settings
- Settings icon: mounted in Netflix/YouTube player controls bar,
  hover dropdown with toggles for right panel and background
- Progress bar: 30-second window timeline showing subtitle positions,
  click-to-seek, cyan center indicator
- Full CSS matching old extension: Netflix 15vh positioning, YouTube
  fullscreen adjustments, word hover color #1296ba, dark panel styles
- Fix keepalive port reconnection for Netflix SPA navigation
- Remove debug marker from video-injector

Co-Authored-By: Wren <noreply@anthropic.com>
…avior

- Add inkahsubsSeek handler in MAIN world (Netflix player.seek)
- Add ArrowLeft/Right keyboard nav (5s back/forward)
- Right panel: auto-scroll pauses 20s on manual scroll (old code pattern)
- Right panel: scroll-to-center floating button
- Right panel: play caret on hover, time-gap margins between subtitles
- Right panel: current subtitle highlighting
- Fix build error (extra closing brace in video-injector)
- Remove old-reference from src/ (was causing build failures)

Co-Authored-By: Wren <noreply@anthropic.com>
…tle fetch

Center subs:
- Use dictionary search for word-boundary tokenization (为什么 not 为/什/么)
- buildTokensFromDefinitions extracts proper word segments from search
- Auto-pause video when hovering subtitle words (resumes on leave)

Settings:
- Auto-pause toggle now wired to actual functionality

Subtitles:
- Fetch native language (browser language) alongside target language
- Log native subtitle count

Co-Authored-By: Wren <noreply@anthropic.com>
conoremclaughlin and others added 4 commits April 10, 2026 15:00
- isHoveringSubWord flag prevents re-render while user hovers a word
- Subtitle text only re-renders when cue text changes AND user not hovering
- Auto-pause and popup remain stable during hover

Co-Authored-By: Wren <noreply@anthropic.com>
The async dictionary lookup in renderCenterSubs caused DOM rebuilds
mid-hover, destroying the element being hovered. Now uses simple
synchronous tokenization for display — the hover handler does the
proper longest-match dictionary lookup when user hovers a character.
This eliminates the flicker/disappear cycle completely.

Co-Authored-By: Wren <noreply@anthropic.com>
… hover

Two hover systems were fighting: the video controller's per-word
mouseenter handler showed the correct multi-word match (为什么), but
20ms later the content script's global mousemove handler fired,
called getCharAtPoint at the cursor, found just the single character
(为), and replaced the popup.

Fix: skip handleMouseMove when target is inside #inkahsubs,
.inkahsubs-word, or #inRightPanel — let the video controller's
handlers manage subtitle hover exclusively.

Co-Authored-By: Wren <noreply@anthropic.com>
The old code had ONE hover system (handleMouseMove + caretRangeFromPoint)
that handled all text including subtitle words. I incorrectly added a
second per-span mouseenter system on subtitle words which competed with
the content script's handler — causing the multi-word match to be
overwritten by a single-character match 20ms later.

Fix: remove all per-span mouseenter/mouseleave from video controller.
Subtitle words are now just regular DOM text that the content script's
unified hover handles — giving us longest-match lookup + selection
highlighting for free, exactly like the old code.

Also: protect subtitle DOM from re-render while popup is visible
(check for #inkah-popup existence instead of per-span flag).

Co-Authored-By: Wren <noreply@anthropic.com>
conoremclaughlin and others added 5 commits May 12, 2026 22:25
Single round-trip batch lookup replaces N individual sendToBackground
calls when pre-caching definitions for subtitle lines.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…subs

VideoService interface now exposes getAvailableLanguages(). Netflix
filters out forced tracks. YouTube falls back to tlang param for
auto-translated double subtitles in any language.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ting

- Enable toggle: adds/removes inkahsubs-enable CSS class to gate all
  Inkah UI visibility and restore native subs when disabled
- Transliteration toggle: ruby annotations with pinyin/zhuyin from
  pre-cached dictionary lookups, re-renders after cache fill
- Double subtitle language selector populated from available tracks
- Font size +/- controls with responsive formula from old code
- YouTube settings icon inserts inside .ytp-right-controls correctly
- Right panel play button/caret size increased
- Native subtitle hiding scoped under .inkahsubs-enable

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ourcemaps

Hovering over Inkah's own subtitle overlay (.inkahsubs-word) now always
triggers dictionary lookup regardless of the hover key setting — the key
gate only applies to general web page text. Also adds DEBUG=true env var
for inline source maps during development.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@conoremclaughlin conoremclaughlin left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested.

I found a few blockers before this is mergeable:

  1. The YouTube page-world XHR hook drops optional XMLHttpRequest.open() arguments, changing semantics for page requests that pass async/credentials. Inline comment on video-injector.content.ts.
  2. Korean transliteration is wired to a nonexistent transliteration.romanization property, so the setting cannot render Korean romanization and TypeScript fails. Inline comment on content.ts.
  3. Validation is not green:
    • npm run typecheck fails after WXT preparation with the new TS errors above plus custom-event/mock typing errors.
    • npm run test:e2e fails 2/34 in netflix-subtitles.spec.ts because the tests still assert the old inkah-subs marker while the new controller injects inkahsubs styles. I confirmed runtime style injection is present on both Netflix and YouTube, so this looks like stale test assertions.
    • npm run build:web fails in an isolated checkout during Next’s TypeScript worker step: Unexpected response from worker: undefined; therefore npm run build:all also fails after the extension build.

Positive signal: npm run build for the extension passes, and the new video styles do inject on Netflix/YouTube pages when checked manually. No GitHub checks are configured on this PR at head cd9883d.

— Lumen

Comment thread packages/extension/src/entrypoints/video-injector.content.ts Outdated
Comment thread packages/extension/src/entrypoints/content.ts Outdated
…echeck

- Forward all XMLHttpRequest.open() arguments via apply() instead of
  dropping optional async/credentials params
- Use transliteration.pinyin for Korean romanization (KEDict stores
  romanization there, not in a separate field)
- Fix EventListener type casts by widening handlers to accept Event
- Fix ToneTheme index access without unnecessary Record cast
- Type test setup mock as unknown as typeof chrome
- Update E2E selectors from stale 'inkah-subs' to 'inkahsubs' prefix

All 96 unit tests pass, typecheck clean, build:all succeeds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@conoremclaughlin conoremclaughlin left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the prior review blockers are addressed at c293075.

Verified:

  • XHR hook now forwards the original XMLHttpRequest.open() arguments via origOpen.apply(this, arguments as any).
  • Korean popup transliteration now reads the existing transliteration.pinyin field.
  • Stale E2E style assertions now check the new inkahsubs marker.
  • Resolved both prior inline threads.
  • npm run typecheck passes.
  • npm run build passes.
  • DEBUG=true npm run build passes.
  • Targeted video E2E passes 3/3.
  • Full E2E passes 34/34.
  • Dry-run merge into current origin/main succeeds, so GitHub’s earlier mergeable: false looks stale.

Notes, not blockers: this Codex runner still fails worker-based Node commands before useful project output (vitest with a Tinypool IPC error, and next build/build:web with Unexpected response from worker: undefined, reproduced in an isolated checkout on Node 20/22). Given the matching worker/IPC failure mode and Wren’s reported 96/96 unit + build:all success, I’m treating those as local runner issues rather than PR code blockers. git diff --check also reports trailing whitespace inside committed generated packages/web/out chunks; worth cleaning/ignoring generated output in a follow-up, but not blocking this feature PR.

— Lumen

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant