Compare commits

..

35 Commits

Author SHA1 Message Date
sudacode 4cb7b53b69 fix: retain frequency rank for honorific prefix-noun tokens
- Add `shouldAllowHonorificPrefixNounFrequency` to exempt お/ご/御 + noun merged tokens from frequency exclusion
- Add regression test for `ご機嫌` asserting rank 5484 is preserved after MeCab enrichment and annotation
- Close TASK-341
2026-05-04 19:12:22 -07:00
sudacode 4d5bf3de41 fix: align Hyprland overlay windows to mpv and stop pinning them
- Force-apply exact Hyprland move/resize/setprop dispatches when bounds are provided
- Stop pinning overlay windows; toggle pin off when Hyprland reports pinned=true
- Compensate stats overlay outer placement for Electron/Wayland content insets
- Make stats overlay window and page opaque so mpv cannot show through transparent insets
- Constrain stats app to h-screen with internal scroll so content covers mpv from y=0
- Lock overlay/stats window titles against page-title-updated events
- Add regression coverage for placement dispatches, inset compensation, and CSS overlay mode
2026-05-04 00:06:27 -07:00
sudacode 8342fa0c0e fix: restore subtitle playback keybindings 2026-05-04 00:06:27 -07:00
sudacode 745996c72d fix: align Hyprland fullscreen overlays 2026-05-04 00:06:27 -07:00
sudacode 95277f30bd fix: hide overlay focus ring 2026-05-04 00:06:27 -07:00
sudacode 08dc8871d3 fix: retry transient AniList safeStorage failures 2026-05-04 00:06:27 -07:00
sudacode 402b58385d fix: suppress known highlights for subtitle particles 2026-05-04 00:06:27 -07:00
sudacode b8dc5db14a fix: stop AniList setup reopening on Linux when keyring token exists
- Gate setup success on token persistence: `saveToken` now returns `boolean`; on failure, keeps the setup window open instead of reporting success
- Config reload passes `allowSetupPrompt: false` so playback reloads don't re-open the setup window
- Add regression test for persistence-failure path
2026-05-04 00:06:27 -07:00
sudacode 47161cd8a5 fix: address PR #57 CodeRabbit feedback
- Acquire AniList post-watch in-flight lock before async gating to prevent duplicate writes
- Isolate manual watched mark result from AniList post-watch callback failures
- Report known-word cache clears as mutations during immediate append when state existed
- Add regression tests for each fix
2026-05-04 00:06:27 -07:00
sudacode 9bcea2fc5f fix: preserve known highlighting for filtered tokens 2026-05-04 00:06:27 -07:00
sudacode 00a94d6bd1 fix: preserve ordinal frequency annotations 2026-05-04 00:06:27 -07:00
sudacode 69d5cc7557 fix: sync AniList after seeked completion 2026-05-04 00:06:27 -07:00
sudacode 040741cf57 fix: address coderabbit feedback 2026-05-04 00:06:27 -07:00
sudacode b245ca642d Fix JLPT underline color drift and AniList skipped-threshold sync
- Replace JLPT `text-decoration` underlines with `border-bottom` so Chromium selection/hover cannot repaint them to another annotation's color
- Lock JLPT underline color for combined annotation selectors (known, n+1, frequency) and character hover/selection states
- Trigger AniList post-watch check on every mpv time-position update to catch skipped completion thresholds
- Fall back to filename-parser season/episode when guessit omits them
2026-05-04 00:06:27 -07:00
sudacode cd057d1a4f fix: keep subtitle prefetch alive after cache hits 2026-05-04 00:06:27 -07:00
sudacode 6b9cb13b07 fix: restore stats daemon deferral 2026-05-04 00:06:27 -07:00
sudacode a9c3a5e679 Preserve overlay across macOS flaps and mpv playlist changes
- keep visible overlays alive during transient macOS tracker loss
- reuse the running mpv overlay path on playlist navigation
- update regression coverage and changelog fragments
2026-05-04 00:06:27 -07:00
sudacode b926f97578 fix: CI changelog, annotation options threading, and Jellyfin quit
- Add `type: fixed` / `area:` frontmatter to `changes/319` to pass `changelog:lint`
- Thread `TokenizerAnnotationOptions` through `stripSubtitleAnnotationMetadata` so `sourceText` is honored
- Include `jellyfinPlay` in `shouldQuitOnDisconnectWhenOverlayRuntimeInitialized` predicate
- Make mouse test `elementFromPoint` stubs coordinate-sensitive
- Make Lua test `.tmp` mkdir portable on Windows
2026-05-04 00:06:27 -07:00
sudacode a9625f8777 Replace grammar-ending permutations with shared matcher; preserve word a
- Extract `grammar-ending.ts` with `isStandaloneGrammarEndingText` / `isSubtitleGrammarEndingText` pattern matchers
- Replace `STANDALONE_GRAMMAR_ENDINGS` set in parser-selection-stage with shared matcher
- Replace generated phrase sets in subtitle-annotation-filter with shared matcher
- Remove stale duplicate subtitle-exclusion constants and helpers from annotation-stage
- Manual clipboard card updates now write only to the sentence audio field, leaving word/expression audio untouched
2026-05-04 00:06:27 -07:00
sudacode f83005bf70 fix: preserve jlpt underline color after lookup 2026-05-04 00:06:27 -07:00
sudacode 508f243d76 fix: suppress sigh interjection annotations 2026-05-04 00:06:27 -07:00
sudacode f96467a1d6 fix: refresh current subtitle after known-word mining 2026-05-04 00:06:27 -07:00
sudacode 6607b06437 Fix managed playback exit and tokenizer grammar splits
- Ignore background stats daemons during regular app startup
- Split standalone grammar endings before applying annotations
- Clear helper-span annotations for auxiliary-only tokens
2026-05-04 00:06:27 -07:00
sudacode 2a06bfc989 Fix kana-only N+1 tokenizer regression test
- Use a pure-kana fixture for the subtitle token N+1 case
- Update task notes for the latest CodeRabbit follow-up
2026-05-04 00:06:27 -07:00
sudacode 55ec191db5 Suppress subtitle annotations for grammar fragments
- Hide annotation metadata for auxiliary inflection and ja-nai endings
- Preserve lexical `くれる` forms and add regression coverage
2026-05-04 00:06:27 -07:00
sudacode 0c051c988c fix: suppress N+1 for kana-only candidates and fix minSentenceWords coun
- Treat kana-only tokens with surrounding subtitle punctuation (…, ―, etc.) as kana-only so they are not promoted to N+1 targets
- Exclude unknown tokens filtered from N+1 targeting from the minSentenceWords count so filtered kana-only unknowns cannot satisfy sentence length threshold
- Add regression tests for kana-only candidate suppression and filtered-unknown padding cases
2026-05-04 00:06:27 -07:00
sudacode d9b3028ef1 Cancel pending Linux MPV fullscreen overlay refresh bursts
- return a cancel handle from the Linux refresh burst scheduler
- clear pending refresh bursts when overlays hide or windows close
- tighten the burst test polling to wait for the async refresh
2026-05-04 00:06:27 -07:00
sudacode 424ff991c4 fix: accept modified digits for multi-line sentence mining 2026-05-04 00:06:27 -07:00
sudacode c0a37622a0 fix: address CodeRabbit review comments 2026-05-04 00:06:27 -07:00
sudacode 5afec94f71 fix: address fullscreen and n-plus-one review notes 2026-05-04 00:06:27 -07:00
sudacode cca68af2b7 fix: refresh overlay on Hyprland fullscreen 2026-05-04 00:06:27 -07:00
sudacode 8dd63d69a2 fix: exclude kana-only n+1 targets 2026-05-04 00:06:27 -07:00
sudacode 9042edf68a fix: restore jlpt subtitle underlines 2026-05-04 00:06:27 -07:00
sudacode 8217d052e9 fix(tokenizer): preserve annotation and enrichment behavior 2026-05-04 00:06:27 -07:00
sudacode 544a770c09 feat(tokenizer): use Yomitan word classes for subtitle POS filtering
- Carry matched headword wordClasses from termsFind into YomitanScanToken
- Map recognized Yomitan wordClasses to SubMiner coarse POS before annotation
- MeCab enrichment now fills only missing POS fields, preserving existing coarse pos1
- Exclude standalone grammar particles, して helper fragments, and single-kana surfaces from annotations
- Respect source-text punctuation gaps when counting N+1 sentence words
- Preserve known-word highlight on excluded kanji-containing tokens
- Add backlog tasks 304 (N+1 boundary bug) and 305 (wordClasses POS, done)
2026-05-04 00:06:27 -07:00
13 changed files with 9 additions and 389 deletions
@@ -1,54 +0,0 @@
---
id: TASK-342
title: Improve docs homepage indexability
status: Done
assignee:
- codex
created_date: '2026-05-11 04:53'
updated_date: '2026-05-11 05:06'
labels:
- docs
- seo
dependencies: []
references:
- 'https://docs.subminer.moe/'
- >-
https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Google Search Console reports https://docs.subminer.moe/ as Crawled - currently not indexed. Investigate and improve the docs-site homepage and crawl signals so the root docs page has clear unique content, self-consistent canonical metadata, and no avoidable duplicate sitemap entry from VitePress README output. Keep the change scoped to docs-site SEO/content and verify with docs tests/build plus live-style generated HTML inspection.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Generated docs HTML for https://docs.subminer.moe/ declares a self-referential canonical URL and does not declare noindex.
- [x] #2 Generated sitemap does not advertise a duplicate /README docs page when the docs homepage is the intended canonical root.
- [x] #3 Docs tests cover the SEO/indexing signals so canonical and duplicate sitemap regressions are caught.
- [x] #4 Docs build/test commands pass or any blocker is documented with exact failure output.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add failing docs-site tests for canonical head generation and sitemap duplicate filtering. 2. Update VitePress config to emit canonical URLs and exclude /README from the sitemap. 3. Strengthen docs homepage content with unique overview/decision-path copy. 4. Run docs tests/build and inspect generated HTML/sitemap.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
2026-05-11: Added docs SEO tests, root canonical generation, sitemap README filtering, and stronger docs homepage orientation copy. Verified generated index.html contains self canonical/no noindex and generated sitemap omits /README.
2026-05-11: Added changes/342-docs-indexability.md and verified changelog lint. Final verification: bun run --cwd docs-site test, bun run docs:build, bun x prettier --check touched docs/changelog files, bun run changelog:lint.
2026-05-11: User requested removal of the added homepage orientation block. Removed it, removed the matching content test, and updated the changelog fragment. Generated index.html no longer contains the removed headings while preserving canonical metadata.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Improved docs homepage indexability signals for https://docs.subminer.moe/. Added self-referential canonical generation for VitePress pages and filtered the duplicate /README page out of the generated sitemap. Added docs SEO regression coverage, kept the existing Plausible hostname test aligned with the shared hostname constant, and added a docs changelog fragment. Per follow-up request, the extra homepage orientation copy was removed before handoff. Verification passed: docs tests, docs build, targeted generated HTML/sitemap inspection, Prettier check for touched files, and changelog lint.
<!-- SECTION:FINAL_SUMMARY:END -->
@@ -1,44 +0,0 @@
---
id: TASK-343
title: Fix macOS character dictionary selector session shortcut
status: Done
assignee: []
created_date: '2026-05-11 08:05'
updated_date: '2026-05-11 08:06'
labels:
- bug
- macos
- character-dictionary
- plugin
dependencies: []
modified_files:
- plugin/subminer/session_bindings.lua
- scripts/test-plugin-session-bindings.lua
priority: medium
ordinal: 181500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Opening the character dictionary AniList selector from mpv/session shortcuts should work on macOS. Current generated session bindings include the openCharacterDictionary session action, but the Lua plugin CLI dispatch table does not map that action to the app flag, so the shortcut cannot reach the runtime selector.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 The openCharacterDictionary session action invokes the app with --open-character-dictionary from the mpv plugin.
- [x] #2 Regression coverage proves the Lua session-binding CLI map forwards openCharacterDictionary correctly.
- [x] #3 Existing session-binding regression coverage still passes.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
TDD red: `lua scripts/test-plugin-session-bindings.lua` failed because openCharacterDictionary did not emit --open-character-dictionary. Green after adding the missing Lua CLI mapping.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Fixed the mpv plugin session action mapping so the character dictionary selector shortcut dispatches `--open-character-dictionary` to the app. Added Lua regression coverage for the macOS-style Alt+Meta+A binding and verified adjacent TypeScript session binding tests.
<!-- SECTION:FINAL_SUMMARY:END -->
@@ -1,74 +0,0 @@
---
id: TASK-344
title: Fix macOS overlay tracker hiding while mpv remains active
status: Done
assignee:
- codex
created_date: '2026-05-11 08:27'
updated_date: '2026-05-11 08:41'
labels:
- bug
- macos
- overlay
dependencies: []
references:
- src/main/runtime/overlay-visibility-runtime.ts
- src/window-trackers
modified_files:
- src/core/services/overlay-visibility.ts
- src/core/services/overlay-visibility.test.ts
- changes/344-macos-overlay-active-mpv.md
priority: high
ordinal: 182500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
macOS playback overlay should match Windows behavior: the tracker may only hide or alter overlay layering when mpv is no longer the active playback window. When mpv remains topmost or fullscreen, the visible overlay must stay present and interactive unless the user manually hides it or minimizes mpv.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 With mpv active on macOS, window-tracker updates do not hide the visible overlay or make it click-through.
- [x] #2 With mpv fullscreen on macOS, tracker geometry/layering refreshes preserve overlay interactivity.
- [x] #3 Overlay visibility still changes when mpv is no longer active, and manual hide/minimize behavior remains intact.
- [x] #4 A regression test covers the macOS active-mpv path that previously produced an overlay loading OSD and non-interactive overlay.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add a focused regression in `src/core/services/overlay-visibility.test.ts` for macOS with mpv tracked/focused and retained geometry: visibility refresh must not hide the overlay, must not emit loading OSD, and must leave the overlay interactive (`setIgnoreMouseEvents(false)`) unless forced passthrough is active.
2. Update `src/core/services/overlay-visibility.ts` so macOS tracker refreshes preserve the visible overlay while mpv is active/fullscreen, and only hide/re-layer for explicit manual hide, minimized/untracked target, or non-active mpv cases.
3. Run the focused overlay visibility tests, then a broader fast gate if the focused fix is green.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Implemented in the shared overlay visibility service. macOS now keeps tracked/focused mpv overlays interactive instead of defaulting to mouse passthrough, and preserves an already visible active-mpv overlay during temporary tracker-not-ready refreshes without showing the loading OSD. Forced passthrough, modal hide, manual hide, Windows minimized handling, and initial macOS tracker-not-ready startup behavior remain covered by tests.
Verification: `bun test src/core/services/overlay-visibility.test.ts` passed; affected overlay/mouse/runtime group passed; `bun run typecheck`, `bun run changelog:lint`, `bun run build`, `bun run test:env`, and `bun run test:smoke:dist` passed. `bun run test:fast` is blocked by existing cross-file test pollution: `src/core/services/subsync.test.ts` passes alone, but fails when run after `src/renderer/handlers/keyboard.test.ts` because `window.electronAPI` is undefined in a lingering keyboard handler; Bun then reports nested node:test errors for later files. `bun run format:check:src` is blocked by pre-existing formatting drift in `src/core/services/stats-window.ts`; touched files pass direct Prettier check.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Summary:
- Updated macOS overlay visibility logic so a tracked/focused mpv window keeps the visible overlay interactive instead of click-through.
- Preserved an already visible active-mpv overlay during temporary macOS tracker-not-ready refreshes, avoiding the loading OSD/hide path for that active playback case.
- Added regression coverage for active mpv tracker refreshes and transient tracker-not-ready refreshes, plus updated old macOS expectations to the new active-mpv contract.
- Added a changelog fragment for the user-visible overlay fix.
Verification:
- Passed: `bun test src/core/services/overlay-visibility.test.ts`
- Passed: `bun test src/core/services/overlay-visibility.test.ts src/core/services/overlay-runtime-init.test.ts src/core/services/overlay-shortcut-handler.test.ts src/renderer/handlers/mouse.test.ts`
- Passed: `bun run typecheck`
- Passed: `bun run changelog:lint`
- Passed: `bun run build`
- Passed: `bun run test:env`
- Passed: `bun run test:smoke:dist`
- Blocked: `bun run test:fast` by existing keyboard/subsync cross-file global pollution; isolated `bun test src/core/services/subsync.test.ts` passes.
- Blocked: `bun run format:check:src` by pre-existing formatting drift in `src/core/services/stats-window.ts`; touched files pass direct Prettier check.
<!-- SECTION:FINAL_SUMMARY:END -->
-4
View File
@@ -1,4 +0,0 @@
type: docs
area: docs
- Improved the docs homepage indexing signals with canonical URLs and a cleaner sitemap.
-4
View File
@@ -1,4 +0,0 @@
type: fixed
area: overlay
- Overlay: Kept the macOS overlay visible and interactive while mpv remains the active tracked window, including transient tracker refreshes.
+1 -24
View File
@@ -1,15 +1,3 @@
const DOCS_HOSTNAME = 'https://docs.subminer.moe';
function pageToCanonicalHref(page: string): string | null {
if (page === '404.md') return null;
const route = page
.replace(/(^|\/)index\.md$/, '')
.replace(/\.md$/, '')
.replace(/\/$/, '');
return route ? `${DOCS_HOSTNAME}/${route}` : `${DOCS_HOSTNAME}/`;
}
export default { export default {
title: 'SubMiner Docs', title: 'SubMiner Docs',
description: description:
@@ -46,18 +34,7 @@ export default {
appearance: 'dark', appearance: 'dark',
cleanUrls: true, cleanUrls: true,
metaChunk: true, metaChunk: true,
sitemap: { sitemap: { hostname: 'https://docs.subminer.moe' },
hostname: DOCS_HOSTNAME,
transformItems(items) {
return items.filter(
(item) => item.url !== 'README' && item.url !== `${DOCS_HOSTNAME}/README`,
);
},
},
transformHead({ page }) {
const href = pageToCanonicalHref(page);
return href ? [['link', { rel: 'canonical', href }]] : [];
},
lastUpdated: true, lastUpdated: true,
srcExclude: ['subagents/**'], srcExclude: ['subagents/**'],
markdown: { markdown: {
+1 -1
View File
@@ -8,7 +8,7 @@
"docs:dev": "VITE_EXTRA_EXTENSIONS=jsonc vitepress dev --host 0.0.0.0 --port 5173 --strictPort", "docs:dev": "VITE_EXTRA_EXTENSIONS=jsonc vitepress dev --host 0.0.0.0 --port 5173 --strictPort",
"docs:build": "VITE_EXTRA_EXTENSIONS=jsonc vitepress build", "docs:build": "VITE_EXTRA_EXTENSIONS=jsonc vitepress build",
"docs:preview": "VITE_EXTRA_EXTENSIONS=jsonc vitepress preview --host 0.0.0.0 --port 4173 --strictPort", "docs:preview": "VITE_EXTRA_EXTENSIONS=jsonc vitepress preview --host 0.0.0.0 --port 4173 --strictPort",
"test": "bun test plausible.test.ts index.assets.test.ts docs-sync.test.ts seo.test.ts" "test": "bun test plausible.test.ts index.assets.test.ts docs-sync.test.ts"
}, },
"dependencies": { "dependencies": {
"@catppuccin/vitepress": "^0.1.2", "@catppuccin/vitepress": "^0.1.2",
+1 -2
View File
@@ -7,8 +7,7 @@ const docsConfigContents = readFileSync(docsConfigPath, 'utf8');
const docsThemeContents = readFileSync(docsThemePath, 'utf8'); const docsThemeContents = readFileSync(docsThemePath, 'utf8');
test('docs site keeps docs hostname while sending plausible events to subminer.moe via worker.subminer.moe capture endpoint', () => { test('docs site keeps docs hostname while sending plausible events to subminer.moe via worker.subminer.moe capture endpoint', () => {
expect(docsConfigContents).toContain("const DOCS_HOSTNAME = 'https://docs.subminer.moe'"); expect(docsConfigContents).toContain("hostname: 'https://docs.subminer.moe'");
expect(docsConfigContents).toContain('hostname: DOCS_HOSTNAME');
expect(docsThemeContents).toContain("const PLAUSIBLE_DOMAIN = 'subminer.moe'"); expect(docsThemeContents).toContain("const PLAUSIBLE_DOMAIN = 'subminer.moe'");
expect(docsThemeContents).toContain('const PLAUSIBLE_ENABLED_HOSTNAMES = new Set(['); expect(docsThemeContents).toContain('const PLAUSIBLE_ENABLED_HOSTNAMES = new Set([');
expect(docsThemeContents).toContain("'docs.subminer.moe'"); expect(docsThemeContents).toContain("'docs.subminer.moe'");
-40
View File
@@ -1,40 +0,0 @@
import { expect, test } from 'bun:test';
import type { TransformContext } from 'vitepress';
import docsConfig from './.vitepress/config';
function makeTransformContext(page: string): TransformContext {
return {
page,
siteConfig: {} as TransformContext['siteConfig'],
siteData: {} as TransformContext['siteData'],
pageData: {} as TransformContext['pageData'],
title: 'SubMiner',
description: 'SubMiner docs',
head: [],
content: '',
assets: [],
};
}
test('docs pages emit stable self-referential canonical URLs', async () => {
const rootHead = await docsConfig.transformHead?.(makeTransformContext('index.md'));
const usageHead = await docsConfig.transformHead?.(makeTransformContext('usage.md'));
expect(rootHead).toContainEqual([
'link',
{ rel: 'canonical', href: 'https://docs.subminer.moe/' },
]);
expect(usageHead).toContainEqual([
'link',
{ rel: 'canonical', href: 'https://docs.subminer.moe/usage' },
]);
expect(JSON.stringify(rootHead).toLowerCase()).not.toContain('noindex');
});
test('docs sitemap excludes duplicate README page from indexable URLs', async () => {
const items = [{ url: '' }, { url: 'README' }, { url: 'usage' }];
const transformedItems = await docsConfig.sitemap?.transformItems?.(items);
expect(transformedItems?.map((item) => item.url)).toEqual(['', 'usage']);
});
-2
View File
@@ -159,8 +159,6 @@ function M.create(ctx)
return { "--open-youtube-picker" } return { "--open-youtube-picker" }
elseif action_id == "openSessionHelp" then elseif action_id == "openSessionHelp" then
return { "--open-session-help" } return { "--open-session-help" }
elseif action_id == "openCharacterDictionary" then
return { "--open-character-dictionary" }
elseif action_id == "openControllerSelect" then elseif action_id == "openControllerSelect" then
return { "--open-controller-select" } return { "--open-controller-select" }
elseif action_id == "openControllerDebug" then elseif action_id == "openControllerDebug" then
-25
View File
@@ -79,14 +79,6 @@ local ctx = {
actionType = "session-action", actionType = "session-action",
actionId = "playNextSubtitle", actionId = "playNextSubtitle",
}, },
{
key = {
code = "KeyA",
modifiers = { "alt", "meta" },
},
actionType = "session-action",
actionId = "openCharacterDictionary",
},
{ {
key = { key = {
code = "KeyL", code = "KeyL",
@@ -161,23 +153,6 @@ local play_next_call = recorded.async_calls[#recorded.async_calls]
assert_true(play_next_call ~= nil, "play-next binding should invoke CLI action") assert_true(play_next_call ~= nil, "play-next binding should invoke CLI action")
assert_true(play_next_call[2] == "--play-next-subtitle", "play-next binding should pass CLI flag") assert_true(play_next_call[2] == "--play-next-subtitle", "play-next binding should pass CLI flag")
local character_dictionary = nil
for _, binding in ipairs(recorded.bindings) do
if binding.keys == "Alt+Meta+a" then
character_dictionary = binding
break
end
end
assert_true(character_dictionary ~= nil, "character dictionary binding should be registered")
character_dictionary.fn()
local character_dictionary_call = recorded.async_calls[#recorded.async_calls]
assert_true(character_dictionary_call ~= nil, "character dictionary binding should invoke CLI action")
assert_true(
character_dictionary_call[2] == "--open-character-dictionary",
"character dictionary binding should pass CLI flag"
)
starter.fn() starter.fn()
local modified_digit = nil local modified_digit = nil
+3 -105
View File
@@ -883,7 +883,7 @@ test('visible overlay stays hidden while a modal window is active', () => {
assert.ok(!calls.includes('update-bounds')); assert.ok(!calls.includes('update-bounds'));
}); });
test('macOS tracked visible overlay stays interactive without passively stealing focus', () => { test('macOS tracked visible overlay stays click-through without passively stealing focus', () => {
const { window, calls } = createMainWindowRecorder(); const { window, calls } = createMainWindowRecorder();
const tracker: WindowTrackerStub = { const tracker: WindowTrackerStub = {
isTracking: () => true, isTracking: () => true,
@@ -915,113 +915,11 @@ test('macOS tracked visible overlay stays interactive without passively stealing
isWindowsPlatform: false, isWindowsPlatform: false,
} as never); } as never);
assert.ok(calls.includes('mouse-ignore:false:plain')); assert.ok(calls.includes('mouse-ignore:true:forward'));
assert.ok(calls.includes('show')); assert.ok(calls.includes('show'));
assert.ok(!calls.includes('focus')); assert.ok(!calls.includes('focus'));
}); });
test('macOS keeps active mpv overlay visible and interactive during tracker refresh', () => {
const { window, calls } = createMainWindowRecorder();
const osdMessages: string[] = [];
const tracker: WindowTrackerStub = {
isTracking: () => true,
getGeometry: () => ({ x: 0, y: 0, width: 1280, height: 720 }),
isTargetWindowFocused: () => true,
};
updateVisibleOverlayVisibility({
visibleOverlayVisible: true,
mainWindow: window as never,
windowTracker: tracker as never,
trackerNotReadyWarningShown: false,
setTrackerNotReadyWarningShown: () => {
calls.push('tracker-warning');
},
updateVisibleOverlayBounds: () => {
calls.push('update-bounds');
},
ensureOverlayWindowLevel: () => {
calls.push('ensure-level');
},
syncPrimaryOverlayWindowLayer: () => {
calls.push('sync-layer');
},
enforceOverlayLayerOrder: () => {
calls.push('enforce-order');
},
syncOverlayShortcuts: () => {
calls.push('sync-shortcuts');
},
isMacOSPlatform: true,
isWindowsPlatform: false,
showOverlayLoadingOsd: (message: string) => {
osdMessages.push(message);
},
} as never);
assert.ok(calls.includes('update-bounds'));
assert.ok(calls.includes('sync-layer'));
assert.ok(calls.includes('mouse-ignore:false:plain'));
assert.ok(calls.includes('ensure-level'));
assert.ok(calls.includes('enforce-order'));
assert.ok(calls.includes('sync-shortcuts'));
assert.ok(!calls.includes('hide'));
assert.deepEqual(osdMessages, []);
});
test('macOS preserves an already visible active mpv overlay while tracker is temporarily not ready', () => {
const { window, calls } = createMainWindowRecorder();
const osdMessages: string[] = [];
let trackerWarning = false;
const tracker: WindowTrackerStub = {
isTracking: () => false,
getGeometry: () => null,
isTargetWindowFocused: () => true,
};
window.show();
calls.length = 0;
updateVisibleOverlayVisibility({
visibleOverlayVisible: true,
mainWindow: window as never,
windowTracker: tracker as never,
trackerNotReadyWarningShown: trackerWarning,
setTrackerNotReadyWarningShown: (shown: boolean) => {
trackerWarning = shown;
calls.push(`tracker-warning:${shown}`);
},
updateVisibleOverlayBounds: () => {
calls.push('update-bounds');
},
ensureOverlayWindowLevel: () => {
calls.push('ensure-level');
},
syncPrimaryOverlayWindowLayer: () => {
calls.push('sync-layer');
},
enforceOverlayLayerOrder: () => {
calls.push('enforce-order');
},
syncOverlayShortcuts: () => {
calls.push('sync-shortcuts');
},
isMacOSPlatform: true,
isWindowsPlatform: false,
showOverlayLoadingOsd: (message: string) => {
osdMessages.push(message);
},
} as never);
assert.equal(trackerWarning, false);
assert.ok(calls.includes('sync-layer'));
assert.ok(calls.includes('mouse-ignore:false:plain'));
assert.ok(calls.includes('ensure-level'));
assert.ok(calls.includes('sync-shortcuts'));
assert.ok(!calls.includes('hide'));
assert.deepEqual(osdMessages, []);
});
test('forced mouse passthrough keeps macOS tracked overlay passive while visible', () => { test('forced mouse passthrough keeps macOS tracked overlay passive while visible', () => {
const { window, calls } = createMainWindowRecorder(); const { window, calls } = createMainWindowRecorder();
const tracker: WindowTrackerStub = { const tracker: WindowTrackerStub = {
@@ -1345,7 +1243,7 @@ test('macOS preserves visible overlay during transient tracker loss with retaine
assert.deepEqual(osdMessages, []); assert.deepEqual(osdMessages, []);
assert.ok(calls.includes('update-bounds')); assert.ok(calls.includes('update-bounds'));
assert.ok(calls.includes('sync-layer')); assert.ok(calls.includes('sync-layer'));
assert.ok(calls.includes('mouse-ignore:false:plain')); assert.ok(calls.includes('mouse-ignore:true:forward'));
assert.ok(calls.includes('ensure-level')); assert.ok(calls.includes('ensure-level'));
assert.ok(calls.includes('enforce-order')); assert.ok(calls.includes('enforce-order'));
assert.ok(calls.includes('sync-shortcuts')); assert.ok(calls.includes('sync-shortcuts'));
+3 -10
View File
@@ -92,14 +92,10 @@ export function updateVisibleOverlayVisibility(args: {
const showPassiveVisibleOverlay = (): void => { const showPassiveVisibleOverlay = (): void => {
const forceMousePassthrough = args.forceMousePassthrough === true; const forceMousePassthrough = args.forceMousePassthrough === true;
const wasVisible = mainWindow.isVisible(); const wasVisible = mainWindow.isVisible();
const shouldDefaultToPassthrough =
args.isMacOSPlatform || args.isWindowsPlatform || forceMousePassthrough;
const isVisibleOverlayFocused = const isVisibleOverlayFocused =
typeof mainWindow.isFocused === 'function' && mainWindow.isFocused(); typeof mainWindow.isFocused === 'function' && mainWindow.isFocused();
const shouldDefaultToPassthrough =
args.isWindowsPlatform ||
forceMousePassthrough ||
(args.isMacOSPlatform &&
!isVisibleOverlayFocused &&
!(args.windowTracker?.isTargetWindowFocused?.() ?? true));
const windowsForegroundProcessName = const windowsForegroundProcessName =
args.lastKnownWindowsForegroundProcessName?.trim().toLowerCase() ?? null; args.lastKnownWindowsForegroundProcessName?.trim().toLowerCase() ?? null;
const windowsOverlayProcessName = args.windowsOverlayProcessName?.trim().toLowerCase() ?? null; const windowsOverlayProcessName = args.windowsOverlayProcessName?.trim().toLowerCase() ?? null;
@@ -265,11 +261,8 @@ export function updateVisibleOverlayVisibility(args: {
} }
const hasRetainedTrackedGeometry = args.windowTracker.getGeometry() !== null; const hasRetainedTrackedGeometry = args.windowTracker.getGeometry() !== null;
const hasActiveMacOSTargetSignal =
args.isMacOSPlatform && (args.windowTracker.isTargetWindowFocused?.() ?? false);
const shouldPreserveTransientTrackedOverlay = const shouldPreserveTransientTrackedOverlay =
(args.isMacOSPlatform && (args.isMacOSPlatform && hasRetainedTrackedGeometry) ||
(hasRetainedTrackedGeometry || (mainWindow.isVisible() && hasActiveMacOSTargetSignal))) ||
(args.isWindowsPlatform && (args.isWindowsPlatform &&
typeof args.windowTracker.isTargetWindowMinimized === 'function' && typeof args.windowTracker.isTargetWindowMinimized === 'function' &&
!args.windowTracker.isTargetWindowMinimized()); !args.windowTracker.isTargetWindowMinimized());