feat(core): add Discord presence service and extract Jellyfin runtime composition

Introduce Discord presence runtime support and continue composition-root decomposition by moving Jellyfin wiring into dedicated composer modules. This keeps main runtime orchestration thinner while preserving behavior and test coverage across config, runtime, and docs updates.
This commit is contained in:
2026-02-22 14:53:10 -08:00
parent 43a8a37f5b
commit edfe6640ac
52 changed files with 2222 additions and 317 deletions

View File

@@ -168,6 +168,7 @@ The composition root (`src/main.ts`) delegates to focused modules in `src/main/`
- `overlay-runtime.ts` — overlay window selection and modal state management
- `subsync-runtime.ts` — subsync command orchestration
- `runtime/composers/anilist-tracking-composer.ts` — AniList media tracking/probe/retry wiring
- `runtime/composers/jellyfin-runtime-composer.ts` — Jellyfin config/client/playback/command/setup composition wiring
- `runtime/composers/mpv-runtime-composer.ts` — MPV event/factory/tokenizer/warmup wiring
Composer modules share contract conventions via `src/main/runtime/composers/contracts.ts`:

View File

@@ -76,6 +76,7 @@ The configuration file includes several main sections:
- [**Jimaku**](#jimaku) - Jimaku API configuration and defaults
- [**AniList**](#anilist) - Optional post-watch progress updates
- [**Jellyfin**](#jellyfin) - Optional Jellyfin auth, library listing, and playback launch
- [**Discord Rich Presence**](#discord-rich-presence) - Optional Discord activity card updates
- [**Keybindings**](#keybindings) - MPV command shortcuts
- [**Runtime Option Palette**](#runtime-option-palette) - Live, session-only option toggles
- [**Secondary Subtitles**](#secondary-subtitles) - Dual subtitle track support
@@ -479,8 +480,6 @@ Jellyfin integration is optional and disabled by default. When enabled, SubMiner
"enabled": true,
"serverUrl": "http://127.0.0.1:8096",
"username": "",
"accessToken": "",
"userId": "",
"remoteControlEnabled": true,
"remoteControlAutoConnect": true,
"autoAnnounce": false,
@@ -535,6 +534,57 @@ Launcher subcommand equivalents:
Jellyfin remote auto-connect runs only when all three are `true`: `jellyfin.enabled`, `jellyfin.remoteControlEnabled`, and `jellyfin.remoteControlAutoConnect`.
### Discord Rich Presence
Discord Rich Presence is optional and disabled by default. When enabled, SubMiner publishes a polished activity card that reflects current media title, playback state, and session timer.
```json
{
"discordPresence": {
"enabled": true,
"clientId": "123456789012345678",
"detailsTemplate": "Watching {title}",
"stateTemplate": "{status}",
"largeImageKey": "subminer-logo",
"largeImageText": "SubMiner",
"smallImageKey": "study",
"smallImageText": "Sentence Mining",
"buttonLabel": "GitHub",
"buttonUrl": "https://github.com/sudacode/SubMiner",
"updateIntervalMs": 15000,
"debounceMs": 750
}
}
```
| Option | Values | Description |
| ------------------ | --------------- | ---------------------------------------------------------------------------------- |
| `enabled` | `true`, `false` | Enable Discord Rich Presence updates (default: `false`) |
| `clientId` | string | Discord application client ID |
| `detailsTemplate` | string | Card details line template. Supports `{title}`, `{file}`, `{subtitle}`, `{status}` |
| `stateTemplate` | string | Card state line template. Supports `{title}`, `{file}`, `{subtitle}`, `{status}` |
| `largeImageKey` | string | Large image asset key uploaded to your Discord app |
| `largeImageText` | string | Hover text for large image |
| `smallImageKey` | string | Small image asset key uploaded to your Discord app |
| `smallImageText` | string | Hover text for small image |
| `buttonLabel` | string | Optional button label (requires valid `buttonUrl`) |
| `buttonUrl` | string (URL) | Optional button URL shown on the activity card |
| `updateIntervalMs` | number | Minimum interval between activity updates in milliseconds |
| `debounceMs` | number | Debounce window for bursty playback events in milliseconds |
Setup steps:
1. Create a Discord application at <https://discord.com/developers/applications>.
2. Open **Rich Presence > Art Assets** and upload image assets referenced by `largeImageKey` / `smallImageKey`.
3. Copy the application ID into `discordPresence.clientId`.
4. Set `discordPresence.enabled` to `true` and restart SubMiner.
Troubleshooting:
- If the card does not appear, verify Discord desktop app is running and `clientId` is correct.
- If images do not render, confirm asset keys exactly match uploaded Discord asset names.
- If Discord is closed/not installed/disconnects, SubMiner continues running and quietly skips presence updates.
### Keybindings
Add a `keybindings` array to configure keyboard shortcuts that send commands to mpv:

View File

@@ -46,6 +46,125 @@
"level": "info" // Minimum log level for runtime logging. Values: debug | info | warn | error
}, // Controls logging verbosity.
// ==========================================
// Keyboard Shortcuts
// Overlay keyboard shortcuts. Set a shortcut to null to disable.
// Hot-reload: shortcut changes apply live and update the session help modal on reopen.
// ==========================================
"shortcuts": {
"toggleVisibleOverlayGlobal": "Alt+Shift+O", // Toggle visible overlay global setting.
"toggleInvisibleOverlayGlobal": "Alt+Shift+I", // Toggle invisible overlay global setting.
"copySubtitle": "CommandOrControl+C", // Copy subtitle setting.
"copySubtitleMultiple": "CommandOrControl+Shift+C", // Copy subtitle multiple setting.
"updateLastCardFromClipboard": "CommandOrControl+V", // Update last card from clipboard setting.
"triggerFieldGrouping": "CommandOrControl+G", // Trigger field grouping setting.
"triggerSubsync": "Ctrl+Alt+S", // Trigger subsync setting.
"mineSentence": "CommandOrControl+S", // Mine sentence setting.
"mineSentenceMultiple": "CommandOrControl+Shift+S", // Mine sentence multiple setting.
"multiCopyTimeoutMs": 3000, // Timeout for multi-copy/mine modes.
"toggleSecondarySub": "CommandOrControl+Shift+V", // Toggle secondary sub setting.
"markAudioCard": "CommandOrControl+Shift+A", // Mark audio card setting.
"openRuntimeOptions": "CommandOrControl+Shift+O", // Open runtime options setting.
"openJimaku": "Ctrl+Shift+J" // Open jimaku setting.
}, // Overlay keyboard shortcuts. Set a shortcut to null to disable.
// ==========================================
// Invisible Overlay
// Startup behavior for the invisible interactive subtitle mining layer.
// Invisible subtitle position edit mode: Ctrl/Cmd+Shift+P to toggle, arrow keys to move, Enter or Ctrl/Cmd+S to save, Esc to cancel.
// This edit-mode shortcut is fixed and is not currently configurable.
// ==========================================
"invisibleOverlay": {
"startupVisibility": "platform-default" // Startup visibility setting.
}, // Startup behavior for the invisible interactive subtitle mining layer.
// ==========================================
// Keybindings (MPV Commands)
// Extra keybindings that are merged with built-in defaults.
// Set command to null to disable a default keybinding.
// Hot-reload: keybinding changes apply live and update the session help modal on reopen.
// ==========================================
"keybindings": [], // Extra keybindings that are merged with built-in defaults.
// ==========================================
// Secondary Subtitles
// Dual subtitle track options.
// Used by subminer YouTube subtitle generation as secondary language preferences.
// Hot-reload: defaultMode updates live while SubMiner is running.
// ==========================================
"secondarySub": {
"secondarySubLanguages": [], // Secondary sub languages setting.
"autoLoadSecondarySub": false, // Auto load secondary sub setting. Values: true | false
"defaultMode": "hover" // Default mode setting.
}, // Dual subtitle track options.
// ==========================================
// Auto Subtitle Sync
// Subsync engine and executable paths.
// ==========================================
"subsync": {
"defaultMode": "auto", // Subsync default mode. Values: auto | manual
"alass_path": "", // Alass path setting.
"ffsubsync_path": "", // Ffsubsync path setting.
"ffmpeg_path": "" // Ffmpeg path setting.
}, // Subsync engine and executable paths.
// ==========================================
// Subtitle Position
// Initial vertical subtitle position from the bottom.
// ==========================================
"subtitlePosition": {
"yPercent": 10 // Y percent setting.
}, // Initial vertical subtitle position from the bottom.
// ==========================================
// Subtitle Appearance
// Primary and secondary subtitle styling.
// Hot-reload: subtitle style changes apply live without restarting SubMiner.
// ==========================================
"subtitleStyle": {
"enableJlpt": false, // Enable JLPT vocabulary level underlines. When disabled, JLPT tagging lookup and underlines are skipped. Values: true | false
"preserveLineBreaks": false, // Preserve line breaks in visible overlay subtitle rendering. When false, line breaks are flattened to spaces for a single-line flow. Values: true | false
"hoverTokenColor": "#c6a0f6", // Hex color used for hovered subtitle token highlight in mpv.
"fontFamily": "M PLUS 1, Noto Sans CJK JP Regular, Noto Sans CJK JP, Hiragino Sans, Hiragino Kaku Gothic ProN, Yu Gothic, Arial Unicode MS, Arial, sans-serif", // Font family setting.
"fontSize": 35, // Font size setting.
"fontColor": "#cad3f5", // Font color setting.
"fontWeight": "normal", // Font weight setting.
"fontStyle": "normal", // Font style setting.
"backgroundColor": "rgb(30, 32, 48, 0.88)", // Background color setting.
"nPlusOneColor": "#c6a0f6", // N plus one color setting.
"knownWordColor": "#a6da95", // Known word color setting.
"jlptColors": {
"N1": "#ed8796", // N1 setting.
"N2": "#f5a97f", // N2 setting.
"N3": "#f9e2af", // N3 setting.
"N4": "#a6e3a1", // N4 setting.
"N5": "#8aadf4" // N5 setting.
}, // Jlpt colors setting.
"frequencyDictionary": {
"enabled": false, // Enable frequency-dictionary-based highlighting based on token rank. Values: true | false
"sourcePath": "", // Optional absolute path to a frequency dictionary directory. If empty, built-in discovery search paths are used.
"topX": 1000, // Only color tokens with frequency rank <= topX (default: 1000).
"mode": "single", // single: use one color for all matching tokens. banded: use color ramp by frequency band. Values: single | banded
"singleColor": "#f5a97f", // Color used when frequencyDictionary.mode is `single`.
"bandedColors": [
"#ed8796",
"#f5a97f",
"#f9e2af",
"#a6e3a1",
"#8aadf4"
] // Five colors used for rank bands when mode is `banded` (from most common to least within topX).
}, // Frequency dictionary setting.
"secondary": {
"fontSize": 24, // Font size setting.
"fontColor": "#ffffff", // Font color setting.
"backgroundColor": "transparent", // Background color setting.
"fontWeight": "normal", // Font weight setting.
"fontStyle": "normal", // Font style setting.
"fontFamily": "M PLUS 1, Noto Sans CJK JP Regular, Noto Sans CJK JP, Hiragino Sans, Hiragino Kaku Gothic ProN, Yu Gothic, Arial Unicode MS, Arial, sans-serif" // Font family setting.
} // Secondary setting.
}, // Primary and secondary subtitle styling.
// ==========================================
// AnkiConnect Integration
// Automatic Anki updates and media generation options.
@@ -119,124 +238,6 @@
} // Is kiku setting.
}, // Automatic Anki updates and media generation options.
// ==========================================
// Keyboard Shortcuts
// Overlay keyboard shortcuts. Set a shortcut to null to disable.
// Hot-reload: shortcut changes apply live and update the session help modal on reopen.
// ==========================================
"shortcuts": {
"toggleVisibleOverlayGlobal": "Alt+Shift+O", // Toggle visible overlay global setting.
"toggleInvisibleOverlayGlobal": "Alt+Shift+I", // Toggle invisible overlay global setting.
"copySubtitle": "CommandOrControl+C", // Copy subtitle setting.
"copySubtitleMultiple": "CommandOrControl+Shift+C", // Copy subtitle multiple setting.
"updateLastCardFromClipboard": "CommandOrControl+V", // Update last card from clipboard setting.
"triggerFieldGrouping": "CommandOrControl+G", // Trigger field grouping setting.
"triggerSubsync": "Ctrl+Alt+S", // Trigger subsync setting.
"mineSentence": "CommandOrControl+S", // Mine sentence setting.
"mineSentenceMultiple": "CommandOrControl+Shift+S", // Mine sentence multiple setting.
"multiCopyTimeoutMs": 3000, // Timeout for multi-copy/mine modes.
"toggleSecondarySub": "CommandOrControl+Shift+V", // Toggle secondary sub setting.
"markAudioCard": "CommandOrControl+Shift+A", // Mark audio card setting.
"openRuntimeOptions": "CommandOrControl+Shift+O", // Open runtime options setting.
"openJimaku": "Ctrl+Shift+J" // Open jimaku setting.
}, // Overlay keyboard shortcuts. Set a shortcut to null to disable.
// ==========================================
// Invisible Overlay
// Startup behavior for the invisible interactive subtitle mining layer.
// Invisible subtitle position edit mode: Ctrl/Cmd+Shift+P to toggle, arrow keys to move, Enter or Ctrl/Cmd+S to save, Esc to cancel.
// This edit-mode shortcut is fixed and is not currently configurable.
// ==========================================
"invisibleOverlay": {
"startupVisibility": "platform-default" // Startup visibility setting.
}, // Startup behavior for the invisible interactive subtitle mining layer.
// ==========================================
// Keybindings (MPV Commands)
// Extra keybindings that are merged with built-in defaults.
// Set command to null to disable a default keybinding.
// Hot-reload: keybinding changes apply live and update the session help modal on reopen.
// ==========================================
"keybindings": [], // Extra keybindings that are merged with built-in defaults.
// ==========================================
// Subtitle Appearance
// Primary and secondary subtitle styling.
// Hot-reload: subtitle style changes apply live without restarting SubMiner.
// ==========================================
"subtitleStyle": {
"enableJlpt": false, // Enable JLPT vocabulary level underlines. When disabled, JLPT tagging lookup and underlines are skipped. Values: true | false
"preserveLineBreaks": false, // Preserve line breaks in visible overlay subtitle rendering. When false, line breaks are flattened to spaces for a single-line flow. Values: true | false
"fontFamily": "Noto Sans CJK JP Regular, Noto Sans CJK JP, Arial Unicode MS, Arial, sans-serif", // Font family setting.
"fontSize": 35, // Font size setting.
"fontColor": "#cad3f5", // Font color setting.
"fontWeight": "normal", // Font weight setting.
"fontStyle": "normal", // Font style setting.
"backgroundColor": "rgb(30, 32, 48, 0.88)", // Background color setting.
"nPlusOneColor": "#c6a0f6", // N plus one color setting.
"knownWordColor": "#a6da95", // Known word color setting.
"jlptColors": {
"N1": "#ed8796", // N1 setting.
"N2": "#f5a97f", // N2 setting.
"N3": "#f9e2af", // N3 setting.
"N4": "#a6e3a1", // N4 setting.
"N5": "#8aadf4" // N5 setting.
}, // Jlpt colors setting.
"frequencyDictionary": {
"enabled": false, // Enable frequency-dictionary-based highlighting based on token rank. Values: true | false
"sourcePath": "", // Optional absolute path to a frequency dictionary directory. If empty, built-in discovery search paths are used.
"topX": 1000, // Only color tokens with frequency rank <= topX (default: 1000).
"mode": "single", // single: use one color for all matching tokens. banded: use color ramp by frequency band. Values: single | banded
"singleColor": "#f5a97f", // Color used when frequencyDictionary.mode is `single`.
"bandedColors": [
"#ed8796",
"#f5a97f",
"#f9e2af",
"#a6e3a1",
"#8aadf4"
] // Five colors used for rank bands when mode is `banded` (from most common to least within topX).
}, // Frequency dictionary setting.
"secondary": {
"fontSize": 24, // Font size setting.
"fontColor": "#ffffff", // Font color setting.
"backgroundColor": "transparent", // Background color setting.
"fontWeight": "normal", // Font weight setting.
"fontStyle": "normal", // Font style setting.
"fontFamily": "Noto Sans CJK JP Regular, Noto Sans CJK JP, Arial Unicode MS, Arial, sans-serif" // Font family setting.
} // Secondary setting.
}, // Primary and secondary subtitle styling.
// ==========================================
// Secondary Subtitles
// Dual subtitle track options.
// Used by subminer YouTube subtitle generation as secondary language preferences.
// Hot-reload: defaultMode updates live while SubMiner is running.
// ==========================================
"secondarySub": {
"secondarySubLanguages": [], // Secondary sub languages setting.
"autoLoadSecondarySub": false, // Auto load secondary sub setting. Values: true | false
"defaultMode": "hover" // Default mode setting.
}, // Dual subtitle track options.
// ==========================================
// Auto Subtitle Sync
// Subsync engine and executable paths.
// ==========================================
"subsync": {
"defaultMode": "auto", // Subsync default mode. Values: auto | manual
"alass_path": "", // Alass path setting.
"ffsubsync_path": "", // Ffsubsync path setting.
"ffmpeg_path": "" // Ffmpeg path setting.
}, // Subsync engine and executable paths.
// ==========================================
// Subtitle Position
// Initial vertical subtitle position from the bottom.
// ==========================================
"subtitlePosition": {
"yPercent": 10 // Y percent setting.
}, // Initial vertical subtitle position from the bottom.
// ==========================================
// Jimaku
// Jimaku API configuration and defaults.
@@ -273,10 +274,8 @@
// ==========================================
// Jellyfin
// Optional Jellyfin integration for auth, browsing, and playback launch.
// Auth session (access token + user id) is stored in local encrypted storage after login/setup.
// Optional environment overrides:
// SUBMINER_JELLYFIN_ACCESS_TOKEN
// SUBMINER_JELLYFIN_USER_ID
// Access token is stored in local encrypted token storage after login/setup.
// jellyfin.accessToken remains an optional explicit override in config.
// ==========================================
"jellyfin": {
"enabled": false, // Enable optional Jellyfin integration and CLI control commands. Values: true | false
@@ -305,6 +304,26 @@
"transcodeVideoCodec": "h264" // Preferred transcode video codec when direct play is unavailable.
}, // Optional Jellyfin integration for auth, browsing, and playback launch.
// ==========================================
// Discord Rich Presence
// Optional Discord Rich Presence activity card updates for current playback/study session.
// Requires a Discord application client ID and uploaded asset keys.
// ==========================================
"discordPresence": {
"enabled": false, // Enable optional Discord Rich Presence updates. Values: true | false
"clientId": "", // Discord application client ID used for Rich Presence.
"detailsTemplate": "Mining Japanese", // Details line template for the activity card.
"stateTemplate": "Idle", // State line template for the activity card.
"largeImageKey": "subminer-logo", // Discord asset key for the large activity image.
"largeImageText": "SubMiner", // Hover text for the large activity image.
"smallImageKey": "study", // Discord asset key for the small activity image.
"smallImageText": "Sentence Mining", // Hover text for the small activity image.
"buttonLabel": "", // Optional button label shown on the Discord activity card.
"buttonUrl": "", // Optional button URL shown on the Discord activity card.
"updateIntervalMs": 15000, // Minimum interval between presence payload updates.
"debounceMs": 750 // Debounce delay used to collapse bursty presence updates.
}, // Optional Discord Rich Presence activity card updates for current playback/study session.
// ==========================================
// Immersion Tracking
// Enable/disable immersion tracking.

View File

@@ -81,3 +81,7 @@ Read first. Keep concise.
| `opencode-task106-immersion-modules-20260222T195109Z-r3m7` | `opencode-task106-immersion-modules` | `Execute TASK-106 decomposition of immersion tracker service into storage session and metadata modules end-to-end without commit` | `done` | `docs/subagents/agents/opencode-task106-immersion-modules-20260222T195109Z-r3m7.md` | `2026-02-22T21:58:45Z` |
| `codex-task105-sliceb-20260222T195423Z-w8n3` | `codex-task105-sliceb` | `Implement TASK-105 slice B runtime cast removal in targeted main/runtime modules without commit` | `done` | `docs/subagents/agents/codex-task105-sliceb-20260222T195423Z-w8n3.md` | `2026-02-22T20:02:06Z` |
| `codex-ts-build-errors-20260222T215411Z-h3k7` | `codex-ts-build-errors` | `Fix current TypeScript build failures in anki/runtime tests and deps typing contracts; keep behavior unchanged.` | `done` | `docs/subagents/agents/codex-ts-build-errors-20260222T215411Z-h3k7.md` | `2026-02-22T21:55:54Z` |
| `codex-kiku-modal-overlay-20260222T220502Z-r4m1` | `codex-kiku-modal-overlay` | `Fix Kiku field-grouping modal overlay visibility restore when modal closes` | `handoff` | `docs/subagents/agents/codex-kiku-modal-overlay-20260222T220502Z-r4m1.md` | `2026-02-22T22:07:38Z` |
| `codex-task103-jellyfin-main-composer-20260222T220441Z-m8p1` | `codex-task103-jellyfin-main-composer` | `Execute TASK-103 Jellyfin runtime wiring extraction from src/main.ts via plan-first workflow without commit.` | `done` | `docs/subagents/agents/codex-task103-jellyfin-main-composer-20260222T220441Z-m8p1.md` | `2026-02-22T22:49:30Z` |
| `codex-task109-discord-presence-20260222T220537Z-lkfv` | `codex-task109-discord-presence` | `Execute TASK-109 Discord Rich Presence integration end-to-end with plan-first workflow (no commit)` | `handoff` | `docs/subagents/agents/codex-task109-discord-presence-20260222T220537Z-lkfv.md` | `2026-02-22T22:36:40Z` |
| `opencode-task103-jellyfin-main-composer-20260222T221152Z-n3p7` | `opencode-task103-jellyfin-main-composer` | `Implement TASK-103 Jellyfin runtime wiring extraction from main.ts into composer module(s), tests, docs, and required validations (no commit).` | `in_progress` | `docs/subagents/agents/opencode-task103-jellyfin-main-composer-20260222T221152Z-n3p7.md` | `2026-02-22T22:11:52Z` |

View File

@@ -0,0 +1,28 @@
# Agent: `codex-discord-presence-task-20260222T194048Z-d7k2`
- alias: `codex-discord-presence-task`
- mission: `Add backlog task for Discord Rich Presence integration with polished activity card`
- status: `done`
- branch: `main`
- started_at: `2026-02-22T19:40:48Z`
- heartbeat_minutes: `5`
## Current Work (newest first)
- [2026-02-22T19:41:00Z] handoff: added `TASK-109` backlog ticket for Discord Rich Presence integration + polished activity card scope.
- [2026-02-22T19:40:56Z] progress: verified no existing Discord presence backlog ticket via search across backlog directories.
- [2026-02-22T19:40:48Z] intent: create ticket only; no runtime code implementation.
## Files Touched
- `backlog/tasks/task-109 - Add-Discord-Rich-Presence-integration-with-polished-activity-card.md`
- `docs/subagents/INDEX.md`
- `docs/subagents/agents/codex-discord-presence-task-20260222T194048Z-d7k2.md`
## Assumptions
- User request means backlog task creation, not immediate implementation.
- Existing markdown task format in `backlog/tasks` remains canonical.
## Open Questions / Blockers
- None.
## Next Step
- Wait for user follow-up (implement `TASK-109` when requested).

View File

@@ -0,0 +1,52 @@
# codex-field-grouping-autoupdate-race-20260222T193915Z-m8p4
- alias: `codex-field-grouping-autoupdate-race`
- mission: Fix Kiku field-grouping merge race with auto-update so note enrichment completes before duplicate merge.
- status: `handoff`
- last_update_utc: `2026-02-22T19:41:20Z`
- backlog_ticket: `TASK-94` (`backlog/tasks/task-94 - Fix-Kiku-duplicate-detection-for-Yomitan-marked-duplicates.md`) for duplicate/grouping workflow scope.
## Intent
- Reproduce reported ordering bug in `NoteUpdateWorkflow`.
- Add regression test first (TDD): update fields before field-grouping merge.
- Patch workflow ordering with minimal behavior change.
## Planned Files
- `src/anki-integration/note-update-workflow.test.ts`
- `src/anki-integration/note-update-workflow.ts`
## Assumptions
- Bug path: auto-update flow enters duplicate merge before sentence/audio/image enrichment completes.
- Existing field-grouping handlers remain source of merge/delete behavior; only invocation timing changes.
## Progress Log
- 2026-02-22T19:39:15Z: session start; read subagent index/collaboration; identified target workflow and tests.
- 2026-02-22T19:40:15Z: added failing TDD regression in `src/anki-integration/note-update-workflow.test.ts` asserting update-before-auto-merge order.
- 2026-02-22T19:40:45Z: patched `src/anki-integration/note-update-workflow.ts` to defer field-grouping execution until after enrichment/update; refresh note info before merge.
- 2026-02-22T19:41:20Z: verification green:
- `bun test src/anki-integration/note-update-workflow.test.ts`
- `bun test src/anki-integration/field-grouping-workflow.test.ts src/anki-integration.test.ts`
## Files Touched
- `src/anki-integration/note-update-workflow.ts`
- `src/anki-integration/note-update-workflow.test.ts`
- `docs/subagents/INDEX.md`
- `docs/subagents/agents/codex-field-grouping-autoupdate-race-20260222T193915Z-m8p4.md`
## Decisions
- Keep duplicate detection early (single lookup), but run merge/manual handlers only after card update path completes.
- Refresh `noteInfo` after update so grouping handlers operate on enriched fields.
## Blockers
- None.
## Next Step
- Optional: add an integration-level test for keep-both (`kikuDeleteDuplicateInAuto=false`) flow to assert both notes retain enriched data.

View File

@@ -0,0 +1,30 @@
# Agent: `codex-kiku-modal-overlay-20260222T220502Z-r4m1`
- alias: `codex-kiku-modal-overlay`
- mission: `Fix Kiku field-grouping modal overlay visibility restore when modal closes`
- status: `handoff`
- branch: `main`
- started_at: `2026-02-22T22:05:02Z`
- heartbeat_minutes: `5`
## Current Work (newest first)
- [2026-02-22T22:07:38Z] handoff: added failing regression test for hidden->modal->close restore path; patched `field-grouping-overlay` to sync visible overlay state for external senders; focused tests green.
- [2026-02-22T22:05:02Z] intent: investigate Kiku modal + overlay visibility restore regression; add failing test first, then minimal fix.
- [2026-02-22T22:06:10Z] progress: traced leak to `createFieldGroupingOverlayRuntime` path using `sendToActiveOverlayWindow` without syncing `visibleOverlayVisible` state.
## Files Touched
- `docs/subagents/INDEX.md`
- `docs/subagents/collaboration.md`
- `docs/subagents/agents/codex-kiku-modal-overlay-20260222T220502Z-r4m1.md`
- `src/core/services/field-grouping-overlay.ts`
- `src/core/services/field-grouping-overlay.test.ts`
## Assumptions
- Bug reproduces when visible overlay is initially hidden and Kiku modal auto-opens overlay.
- Existing callback restore path should remain source of truth (hide on resolve/cancel).
## Open Questions / Blockers
- none
## Next Step
- User validation in real overlay flow (hidden visible overlay -> Kiku modal open/close -> hidden restored).

View File

@@ -0,0 +1,40 @@
# Agent Session: codex-task103-jellyfin-main-composer-20260222T220441Z-m8p1
- alias: `codex-task103-jellyfin-main-composer`
- mission: `Execute TASK-103 Jellyfin runtime wiring extraction from src/main.ts composition root without commit.`
- status: `done`
- last_update_utc: `2026-02-22T22:49:30Z`
## Intent
- Load TASK-103 from Backlog MCP.
- Produce plan artifact via writing-plans skill.
- Execute plan end-to-end with tests (no commit).
## Planned Files (initial)
- `src/main.ts`
- `src/main/runtime/composers/*jellyfin*`
- `src/main/runtime/*jellyfin*`
- `src/main/runtime/composers/*.test.ts`
- `docs/architecture.md` (if ownership docs required)
- `docs/plans/2026-02-22-task-103-jellyfin-runtime-wiring.md`
## Assumptions
- Existing runtime composer patterns from TASK-94/TASK-97 remain canonical.
- No behavior changes expected; extraction/refactor only.
- User requested no commit in this run.
## Progress Log
- `2026-02-22T22:04:41Z` session created; backlog overview + task guides loaded; TASK-103 context loaded.
- `2026-02-22T22:10:20Z` wrote plan artifact `docs/plans/2026-02-22-task-103-jellyfin-runtime-wiring.md`; saved plan to TASK-103.
- `2026-02-22T22:36:15Z` implemented `src/main/runtime/composers/jellyfin-runtime-composer.ts` and `src/main/runtime/composers/jellyfin-runtime-composer.test.ts`; rewired Jellyfin block in `src/main.ts` to `composeJellyfinRuntimeHandlers(...)`; updated `docs/architecture.md` composer ownership.
- `2026-02-22T22:38:58Z` validations: focused composer tests PASS, `check:main-fanin` PASS, `test:core:src` PASS; `build` blocked by pre-existing duplicate/invalid imports in `src/main.ts`.
- `2026-02-22T22:49:05Z` user-reported build fix validated; reran required gates (`build`, `test:core:src`, `check:main-fanin`) all PASS; TASK-103 finalized Done in Backlog.
## Handoff Notes
- TASK-103 complete: AC1-4 and DoD1-3 checked; status Done.
- New files: `src/main/runtime/composers/jellyfin-runtime-composer.ts`, `src/main/runtime/composers/jellyfin-runtime-composer.test.ts`.

View File

@@ -0,0 +1,46 @@
# Agent: `codex-task104-launcher-config-20260222T194708Z-z9x1`
- alias: `codex-task104-launcher-config`
- mission: `Execute TASK-104 end-to-end with plan-first workflow and no commit`
- status: `done`
- branch: `main`
- started_at: `2026-02-22T19:47:08Z`
- heartbeat_minutes: `5`
## Current Work (newest first)
- [2026-02-22T19:47:08Z] intent: load backlog context, write execution plan with writing-plans skill, execute with executing-plans skill, and validate launcher test lanes.
- [2026-02-22T19:56:26Z] progress: extracted launcher config domain modules (`shared-config-reader`, `youtube-subgen-config`, `jellyfin-config`, `plugin-runtime-config`, `cli-parser-builder`, `args-normalizer`) and reduced `launcher/config.ts` to orchestration facade.
- [2026-02-22T19:56:26Z] test: `bun test launcher/config-domain-parsers.test.ts launcher/parse-args.test.ts`, `bun run test:launcher`, and `bun run test:fast` all passed.
- [2026-02-22T19:56:26Z] handoff: TASK-104 finalized Done in Backlog MCP with AC/DoD checks complete and final summary recorded.
## Files Touched
- `docs/subagents/agents/codex-task104-launcher-config-20260222T194708Z-z9x1.md`
- `docs/subagents/INDEX.md`
- `docs/subagents/collaboration.md`
- `docs/plans/2026-02-22-task-104-launcher-config-domain-parsers-cli-builder.md`
- `launcher/config.ts`
- `launcher/config/shared-config-reader.ts`
- `launcher/config/youtube-subgen-config.ts`
- `launcher/config/jellyfin-config.ts`
- `launcher/config/plugin-runtime-config.ts`
- `launcher/config/cli-parser-builder.ts`
- `launcher/config/args-normalizer.ts`
- `launcher/config-domain-parsers.test.ts`
- `launcher/parse-args.test.ts`
- `launcher/jellyfin.ts`
- `launcher/types.ts`
- `package.json`
## Assumptions
- Existing launcher CLI behavior can be preserved with modular extraction plus regression tests.
## Open Questions / Blockers
- none
## Next Step
- await user follow-up (optional: commit, docs wording update, or additional jellyfin session-source hardening).

View File

@@ -0,0 +1,57 @@
# Agent Session: codex-task105-sliceb-20260222T195423Z-w8n3
- alias: `codex-task105-sliceb`
- mission: `Implement TASK-105 slice B runtime cast removal in targeted main/runtime modules without commit.`
- status: `done`
- started_utc: `2026-02-22T19:54:23Z`
- last_update_utc: `2026-02-22T20:02:06Z`
## Intent
- Remove unsafe non-test casts in requested runtime modules using strict shared contracts.
- Keep behavior unchanged; limit scope to type-safety and minimal fallout tests.
- Run targeted tests for changed modules and report outputs.
## Planned Files
- `src/main/runtime/app-runtime-main-deps.ts`
- `src/main/runtime/cli-command-context-main-deps.ts`
- `src/main/runtime/subtitle-tokenization-main-deps.ts`
- `src/main/runtime/overlay-runtime-options-main-deps.ts`
- `src/main/runtime/dictionary-runtime-main-deps.ts`
- `src/main/runtime/mpv-jellyfin-defaults.ts`
- `src/main/runtime/jellyfin-playback-launch.ts`
- `src/main/runtime/cli-command-context-main-deps.test.ts`
- `src/main/runtime/subtitle-tokenization-main-deps.test.ts`
- `src/main/runtime/mpv-jellyfin-defaults.test.ts`
- `src/main/runtime/mpv-jellyfin-defaults-main-deps.test.ts`
- `src/main/runtime/jellyfin-playback-launch.test.ts`
- `src/main/runtime/jellyfin-playback-launch-main-deps.test.ts`
- `src/main/runtime/**/*.test.ts`
## Files Touched
- `docs/subagents/INDEX.md`
- `docs/subagents/agents/codex-task105-sliceb-20260222T195423Z-w8n3.md`
- `docs/subagents/collaboration.md`
- `src/main/runtime/app-runtime-main-deps.ts`
- `src/main/runtime/cli-command-context-main-deps.ts`
- `src/main/runtime/subtitle-tokenization-main-deps.ts`
- `src/main/runtime/overlay-runtime-options-main-deps.ts`
- `src/main/runtime/mpv-jellyfin-defaults.ts`
- `src/main/runtime/jellyfin-playback-launch.ts`
## Assumptions
- Existing runtime contract types already cover needed dependency boundaries.
- Requested slice B is limited to listed files plus minimal typing-test fallout.
## Phase Log
- `2026-02-22T19:54:23Z` Session started; loaded backlog workflow + task context + subagent docs; preparing cast hotspot edits.
- `2026-02-22T19:59:42Z` Completed slice B edits: removed unsafe casts in targeted runtime modules, tightened contracts to shared runtime types, and ran targeted runtime tests (16 pass / 0 fail).
- `2026-02-22T20:02:06Z` Updated focused tests for stricter contracts (typed stubs/status snapshots) and re-ran targeted suite (16 pass / 0 fail).
## Next Step
- Handoff ready.

View File

@@ -0,0 +1,80 @@
# Agent Log: codex-task109-discord-presence-20260222T220537Z-lkfv
- alias: `codex-task109-discord-presence`
- mission: `Execute TASK-109 Discord Rich Presence integration end-to-end with plan-first workflow (no commit).`
- status: `in_progress`
- started_utc: `2026-02-22T22:05:37Z`
- backlog_task: `TASK-109`
## Intent
- Load TASK-109 context from Backlog MCP.
- Write execution plan via `writing-plans` skill.
- Execute approved plan via `executing-plans` skill.
- Prefer parallel subagents for independent slices.
## Planned Files (initial)
- `src/config/definitions.ts`
- `src/config/service.ts`
- `src/main.ts`
- `src/main/runtime/*discord*`
- `src/core/services/*discord*`
- `docs/configuration.md`
## Assumptions
- Discord integration optional, default off.
- Existing logging standards: avoid noisy transient errors.
- Rich Presence client id/config wired via existing config loading.
## Progress
- `2026-02-22T22:06:00Z`: Loaded backlog workflow overview + TASK-109 details.
- `2026-02-22T22:18:00Z`: Wrote plan at `docs/plans/2026-02-22-task-109-discord-rich-presence.md` and started execution.
- `2026-02-22T22:25:00Z`: Completed config surface for `discordPresence` (types/defaults/resolve/option registry/template + config tests).
- `2026-02-22T22:32:00Z`: Added `src/core/services/discord-presence.ts` with lifecycle + payload mapping + debounce/interval throttling + focused tests.
- `2026-02-22T22:35:00Z`: Wired MPV event handlers + app cleanup hooks for presence updates/stop; focused runtime tests green.
- `2026-02-22T22:36:00Z`: Updated docs/config examples; docs build green; backlog TASK-109 set In Progress with implementation notes.
## Blockers
- Full gate `bun run build` blocked by pre-existing `src/main.ts` duplicate imports/symbol errors unrelated to TASK-109 scope in current dirty workspace.
## Handoff Notes
- Implemented files:
- `src/core/services/discord-presence.ts`
- `src/core/services/discord-presence.test.ts`
- `src/config/definitions/defaults-integrations.ts`
- `src/config/definitions/options-integrations.ts`
- `src/config/definitions/template-sections.ts`
- `src/config/resolve/integrations.ts`
- `src/config/resolve/jellyfin.test.ts`
- `src/config/config.test.ts`
- `src/types.ts`
- `src/main/state.ts`
- `src/main/runtime/mpv-main-event-actions.ts`
- `src/main/runtime/mpv-main-event-actions.test.ts`
- `src/main/runtime/mpv-client-event-bindings.ts`
- `src/main/runtime/mpv-client-event-bindings.test.ts`
- `src/main/runtime/mpv-main-event-bindings.ts`
- `src/main/runtime/mpv-main-event-bindings.test.ts`
- `src/main/runtime/mpv-main-event-main-deps.ts`
- `src/main/runtime/mpv-main-event-main-deps.test.ts`
- `src/main/runtime/app-lifecycle-actions.ts`
- `src/main/runtime/app-lifecycle-actions.test.ts`
- `src/main/runtime/app-lifecycle-main-cleanup.ts`
- `src/main/runtime/app-lifecycle-main-cleanup.test.ts`
- `src/main/runtime/composers/startup-lifecycle-composer.test.ts`
- `src/main/runtime/composers/mpv-runtime-composer.test.ts`
- `src/core/services/field-grouping-overlay.test.ts`
- `src/main.ts`
- `docs/configuration.md`
- `config.example.jsonc`
- `docs/public/config.example.jsonc`
- `package.json`
- `bun.lock`
- Remaining work:
- Resolve unrelated `src/main.ts` duplicate import/symbol breakage in workspace to unblock full build/test gate.
- Manual Discord desktop validation still pending (DoD #2).

View File

@@ -0,0 +1,50 @@
# Agent Log: codex-ts-build-errors-20260222T215411Z-h3k7
- alias: `codex-ts-build-errors`
- mission: `Fix current TypeScript build failures in anki/runtime tests and deps typing contracts; keep behavior unchanged.`
- backlog: `TASK-105 (runtime cast/type tightening fallout) + current build-break triage`
- status: `in_progress`
- last_update_utc: `2026-02-22T21:54:29Z`
## Intent
- Triage all listed TS2322/TS2741/TS2353 errors.
- Prefer test stub/type fixes; minimal production changes only if contract mismatch in runtime composer wiring.
- Re-run `bun run tsc --noEmit` (or project build target) to confirm green.
## Planned Files
- `src/anki-integration/note-update-workflow.test.ts`
- `src/main/runtime/cli-command-context-factory.test.ts`
- `src/main/runtime/composers/app-ready-composer.test.ts`
- `src/main/runtime/composers/mpv-runtime-composer.test.ts`
- `src/main/runtime/composers/mpv-runtime-composer.ts`
- `src/main/runtime/overlay-runtime-bootstrap-handlers.test.ts`
- `src/main/runtime/overlay-runtime-options-main-deps.test.ts`
## Assumptions
- Failures are strict typing drift after recent runtime contract hardening.
- No functional behavior change intended.
## Activity
- `2026-02-22T21:54:29Z` started; reading failing files and applying minimal type-aligned fixes.
## Result
- status: `done`
- last_update_utc: `2026-02-22T21:55:54Z`
- files_touched:
- `src/anki-integration/note-update-workflow.test.ts`
- `docs/subagents/agents/codex-ts-build-errors-20260222T215411Z-h3k7.md`
- `docs/subagents/INDEX.md`
- `docs/subagents/collaboration.md`
- key_decisions:
- Typed harness deps as `NoteUpdateWorkflowDeps` to avoid literal over-narrowing in test overrides.
- Kept fixes test-only; no runtime behavior changes.
- verification:
- `bun run tsc --noEmit` passed.
- `make build` passed.
- blockers: none.
- next_step: optional commit/changelog by user preference.

View File

@@ -134,3 +134,12 @@ Shared notes. Append-only.
- [2026-02-22T21:55:54Z] [codex-ts-build-errors-20260222T215411Z-h3k7|codex-ts-build-errors] completed compile-fix pass: widened `note-update-workflow.test.ts` harness deps to `NoteUpdateWorkflowDeps`, aligned stub callback signatures, and verified `bun run tsc --noEmit` + `make build` green.
- [2026-02-22T21:56:30Z] [opencode-task105-unsafe-casts-20260222T194704Z-zfcm|opencode-task105-unsafe-casts] finalized TASK-105: re-ran `bun run build` after compile-fix pass (green), confirmed cast scan 42->0 in scope, and moved backlog task to Done with AC/DoD + final summary.
- [2026-02-22T22:06:20Z] [codex-task109-discord-presence-20260222T220537Z-lkfv|codex-task109-discord-presence] starting TASK-109 via Backlog MCP + writing-plans/executing-plans workflow; scope Discord Rich Presence config/runtime/docs/tests, no commit.
- [2026-02-22T22:36:40Z] [codex-task109-discord-presence-20260222T220537Z-lkfv|codex-task109-discord-presence] implemented Discord presence config/service/runtime/docs slices with focused tests + docs build green; full build gate blocked by pre-existing `src/main.ts` duplicate import/symbol errors outside TASK-109 scope.
- [2026-02-22T22:04:41Z] [codex-task103-jellyfin-main-composer-20260222T220441Z-m8p1|codex-task103-jellyfin-main-composer] starting TASK-103 via Backlog MCP + writing-plans/executing-plans workflow; scope jellyfin wiring extraction from `src/main.ts` into runtime composer modules + focused tests, no commit.
- [2026-02-22T22:39:40Z] [codex-task103-jellyfin-main-composer-20260222T220441Z-m8p1|codex-task103-jellyfin-main-composer] TASK-103 implementation pass done: added `jellyfin-runtime-composer` + seam test, rewired `src/main.ts` Jellyfin block to single composer invocation, updated architecture docs, and validated focused composer tests + `check:main-fanin` + `test:core:src`; `bun run build` blocked by pre-existing duplicate/invalid import state already present in `src/main.ts`.
- [2026-02-22T22:49:30Z] [codex-task103-jellyfin-main-composer-20260222T220441Z-m8p1|codex-task103-jellyfin-main-composer] closure: user fixed prior build blocker; reran required TASK-103 gates (`bun run build`, `bun run test:core:src`, `bun run check:main-fanin`) all pass and finalized TASK-103 to Done in Backlog.
- [2026-02-22T22:10:10Z] [codex-kiku-modal-overlay-20260222T220502Z-r4m1|codex-kiku-modal-overlay] overlap note: touching `src/core/services/field-grouping-overlay.ts` + tests to fix Kiku modal auto-shown visible overlay restore when modal closes.
- [2026-02-22T22:07:38Z] [codex-kiku-modal-overlay-20260222T220502Z-r4m1|codex-kiku-modal-overlay] completed fix: synchronized visible-overlay state when Kiku request opens via external sender; added regression test for hidden->open->resolve->hidden visibility restoration; focused field-grouping/overlay tests passing.
- [2026-02-22T22:11:52Z] [opencode-task103-jellyfin-main-composer-20260222T221152Z-n3p7|opencode-task103-jellyfin-main-composer] overlap note: implementing user-requested TASK-103 extraction in `src/main.ts`, `src/main/runtime/composers/jellyfin-*.ts`, composer tests, and `docs/architecture.md`; coordinating with active `codex-task103-...` session to avoid clobber.