The app shell's primary navigation: a collapsible icon rail that expands on hover (desktop). On the immersive Player route it hides and is reachable via a floating menu button as a left drawer. Profile lives at the foot (not in the nav).
The active-profile control in the sidebar foot. Shows the current profile and opens a small dialog to set or clear it. Profile data is just a localStorage id (see ProfileContext); richer management lives on the Dashboard.
The task tray: a discrete floating dock (bottom-right) that surfaces the active profile's uploads and jobs from the TaskContext. It collapses to a small pill (hidden entirely when there is nothing to show) and expands into a panel with per-task progress, cancel, and dismiss. Reuses the shell's Sumi & Shu surface language so it reads as part of the chrome.
Context for the token bundling mode (Words / Grammar / Morphemes), a client-side view preference persisted to localStorage and sent to the server tokenizer on every request.
Profile-scoped operation settings: the curated Whisper transcription options, the default clean_audio flag, and the conversion options that the single-op and batch flows send to the server. Persisted per profile in localStorage. An unset Whisper field is omitted from requests, so the server keeps its own tuned default rather than being overridden.
This file defines the context for managing the state of the video player. It allows the player's state (e.g., loaded video, subtitles, settings) to persist across component mounts and unmounts, enabling navigating between pages.
Global task tracking: holds the active profile's jobs (server-side) and in-flight uploads (client-side), polls running jobs, and exposes submit / cancel / dismiss for the task tray and the features that enqueue work. The server is the source of truth, so active jobs survive navigation and reload (re-seeded on mount).
The Home page — a quick-start launcher: active-profile status, primary actions, a glance at recent clips/transcripts, and links to the Guide / repo / docs.
Player video API helpers. The long-running operations (SRT generation, MP4 convert, LLM fix) now run as jobs via the task tray; this only keeps the direct SRT-save helper.
The slim player top bar: load media, subtitle style, furigana toggle, and the SRT actions (generate / convert / LLM fix). The long-running actions are submitted as jobs and tracked in the task tray, which loads each result back into the player when it finishes.
The subtitle-navigation panel: the full cue list with search, click-to-seek, and an active-cue highlight that auto-scrolls into view. Collapsible to a thin rail so the video can reclaim the width.
The player's video surface: composes the shared VideoPlayer and layers click-through overlay subtitles on top, anchored to the painted frame (computed by useVideoBox) rather than the element box.
Tracks the active subtitle cue from a video element's playback time. Binds to the element value (callback-ref) so the listener attaches exactly when it mounts, and drives updates from a requestAnimationFrame loop while playing — timeupdate alone fires sparsely and can stall, which froze the subtitle on a cue. setActiveIdx bails out when unchanged, so the rAF loop costs nothing until the cue actually changes.
Computes the painted video rectangle inside an object-contain video element (the element fills the stage, but the actual frame is letterboxed within it). Used to anchor overlay subtitles to the frame rather than the element box.
The Player page: a video stage with overlay subtitles, a collapsible subtitle-navigation panel, and a slim toolbar. Pulls media + style from the global contexts so state persists across navigation.
The Advanced tab: profile-scoped operation settings, split into Transcription (curated Whisper opts + clean-audio) and Conversion sub-tabs. Each field carries a tooltip and a faded placeholder showing the server default; an empty field is omitted from requests. A draft is edited locally and committed with Save (or Reset to defaults).
The batch operation dialog: pick one operation to run across the selected files. The options themselves come from the Advanced settings (Whisper / conversion) or the LLM Template (subtitle fix), so this is just a picker. Submitting drops a batch job into the Tasks tray.
The Files tab: the batch hub. An Upload dropdown adds files or a folder (tracked in the Tasks tray); files list in collapsible folder trays; and an Options dropdown runs a batch, deletes, or clears the current selection.
The LLM Template tab. Two sub-tabs: "Word Breakdown" (model + system message + prompt, used when clicking a word) and "Subtitle Fix" (model + system message, used by the player's Fix SRT). One Save persists the whole template.
The Tasks tab: the durable record of the profile's jobs. Lists the full job history (server) overlaid with the live task state, expands a batch job to its per-file children, applies a finished result, and deletes tasks. Files are the source of truth, so deleting a file cascades its jobs away server-side rather than leaving stale rows to detect here.
A transcribe-chat message: the audio renders as the themed AudioPlayer; a transcript renders as clickable furigana tokens; an LLM explanation renders as markdown — each in an aligned bubble.
This is the entry point of the application. It sets up the React application, including providers and routing. It also includes a fix for handling client-side routing on static hosting services like GitHub Pages.
A reusable Server-Sent Events client over fetch (an EventSource can't POST a JSON body), mirroring apiFetch (profile header, ApiError). Each data: frame is JSON; a named error event aborts with an ApiError, and the stream ends on the done event.
A small provider mark for the LLM provider picker / model chips. Providers with a brand SVG render it as a CSS mask tinted to the surrounding text color (so the monochrome marks stay visible on the dark theme and adopt disabled/active text colors); other providers fall back to a brand-tinted monogram or a generic icon.
Cross-browser recording of a MediaStream from an HTMLVideoElement. Uses native captureStream where available and falls back to a canvas-based approach (iOS Safari). Also picks a supported MIME type.
A selector for the token bundling mode (Words / Grammar / Morphemes), wired to BundleSettingsContext. The choice is a global view preference, so changing it re-tokenizes everywhere.
Presentational components for dictionary data (JMdict / JMnedict / KANJIDIC entries, examples), shared by the WordDialog lookup and the Dictionary page. Example sentences are tokenized into clickable furigana.
The shared loading skeleton for a streamed LLM explanation: a heading bar over a few text lines, shown until the first token arrives. Used by the word dialog and the transcribe chat bubble so both present the same placeholder.
Renders stitched JapaneseWords as clickable tokens with optional furigana. Shared by the text analyzer, subtitle player, and transcribe chat. Font size + color are inherited from the parent (so the subtitle style settings apply); the interaction accent is the theme's vermilion. No blanket underline — affordance is a subtle hover highlight.
Custom, on-theme video controls (the native ones can't be themed cross-browser). Renders over the bottom of the video: scrub bar, play/pause, volume, time, and playback speed. No fullscreen -> native fullscreen would drop the overlay subtitles. Reads/writes the
A styled, reusable video player: an object-contain video on a black stage with the on-theme VideoControls (auto-hiding while playing). Used on its own (e.g. clip preview) so a plain video matches the player's look instead of the unstyled native controls, and composed by the player feature, which layers its subtitle overlay on top via overlay.
A draggable word lookup: a streamed LLM nuance explanation + dictionary definitions, with an optional "save clip" action when opened from a video. Shared by the player, text analyzer, and transcribe pages.
Screen-size hooks. useMediaQuery subscribes to a CSS media query. useIsMobile is the shared "below Tailwind's lg breakpoint" check used to switch between desktop and mobile component layouts.
Display helpers for jobs: human labels per operation type, the "apply result" action labels, the active-status test, and the status line (including batch progress + outcome summaries). Shared by the task tray and the dashboard Tasks tab.
Types for the async job system: server-side jobs and the client-side uploads that feed them, shared by the task tray and the features that enqueue work.
A hook that applies a finished job's result: loads an SRT or a converted video into the player (and routes there), or sends a transcript to the dashboard. Works for a single-op job and for a batch child (whose type is the single-op type). Shared by the task tray and the Tasks tab.