2 Commits

12 changed files with 341 additions and 7 deletions

View File

@@ -1,5 +1,14 @@
# Changelog # Changelog
## v0.9.1 (2026-03-24)
### Changed
- Release: Reduced packaged release size by excluding duplicate `extraResources` payload and pruning docs, tests, sourcemaps, and other source-only files from Electron bundles.
### Fixed
- Overlay: Restored controller navigation and lookup/mining controls while the subtitle sidebar is open, while keeping true modal dialogs blocking controller actions.
- Tokenizer: Fixed subtitle annotation clearing so explanatory contrast endings like `んですけど` are excluded consistently across the shared tokenizer filter and annotation stage.
## v0.9.0 (2026-03-23) ## v0.9.0 (2026-03-23)
### Added ### Added

View File

@@ -0,0 +1,57 @@
---
id: TASK-231
title: Restore controller input while subtitle sidebar is open
status: Done
assignee:
- '@codex'
created_date: '2026-03-24 00:15'
updated_date: '2026-03-24 00:15'
labels:
- bug
- controller
- subtitle-sidebar
- overlay
dependencies: []
references:
- /home/sudacode/projects/japanese/SubMiner/src/renderer/renderer.ts
- /home/sudacode/projects/japanese/SubMiner/src/renderer/controller-interaction-blocking.ts
- /home/sudacode/projects/japanese/SubMiner/src/renderer/controller-interaction-blocking.test.ts
priority: high
ordinal: 54900
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
When keyboard-only mode is active, opening the subtitle sidebar should not disable controller navigation and lookup/mining controls. Restore controller input while the sidebar is open, while keeping true modal dialogs blocking controller actions.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Opening the subtitle sidebar does not block controller input for keyboard-only mode actions.
- [x] #2 Controller-select/debug and other true modal dialogs still block controller actions while open.
- [x] #3 Focused regression coverage exists for the sidebar-open controller gating rule.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Root cause: renderer gamepad polling used the broad `isAnyModalOpen()` check as its interaction gate, and that list includes `subtitleSidebarModalOpen`. The subtitle sidebar is non-modal for controller usage, so gamepad input was being suppressed whenever the sidebar was visible.
Fixed by extracting a dedicated controller-interaction blocking helper that excludes the subtitle sidebar but keeps the existing blocking behavior for true modal dialogs.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Restored controller input while the subtitle sidebar is open by switching gamepad polling to a dedicated modal-blocking rule that leaves the sidebar controller-passive. Added a regression test covering the sidebar-open exception and preserving hard blocks for actual modal dialogs.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -0,0 +1,66 @@
---
id: TASK-232
title: Trim release package size by pruning duplicate and source-only assets
status: Done
assignee:
- '@codex'
created_date: '2026-03-24 12:05'
updated_date: '2026-03-24 12:30'
labels:
- release
- packaging
priority: medium
ordinal: 54700
dependencies: []
references:
- /home/sudacode/projects/japanese/SubMiner/package.json
- /home/sudacode/projects/japanese/SubMiner/src/release-workflow.test.ts
- /home/sudacode/projects/japanese/SubMiner/src/core/services/texthooker.ts
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Reduce packaged release artifact size without changing user-visible functionality by pruning files that are duplicated between `app.asar` and `extraResources`, excluding source/test/doc-only trees from Electron packaging, and trimming obviously non-runtime vendored payload.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Electron packaging excludes repo content that is source-only, test-only, docs-only, or duplicated by `extraResources`.
- [x] #2 Release packaging tests cover the new exclusion rules.
- [x] #3 Verification includes at least targeted release-packaging tests and one packaging-oriented validation step.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Completed scope:
- Exclude `assets`, `plugin`, and `vendor/yomitan-jlpt-vocab` from `files` because they are already staged via `extraResources`.
- Exclude `dist` sourcemaps/tests, repo docs/tests/packaging metadata, and stats source leftovers from `files`.
- Exclude non-runtime `vendor/texthooker-ui` payload such as `public/`, `.vscode/`, and package metadata.
- Exclude Linux musl libsql binary from packaged app payload for AppImage-focused savings.
Verification:
- `bun test src/release-workflow.test.ts`
- `bun run build`
- `node_modules/.bin/electron-builder --linux dir --publish never`
- `node_modules/.bin/electron-builder --linux AppImage --publish never`
Observed result:
- `release/linux-unpacked/resources/app.asar` dropped from about `100 MB` to `29 MB`.
- `release/SubMiner-0.9.0.AppImage` dropped from about `256 MB` to `194 MB`.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Trimmed Electron packaging so release artifacts no longer bundle duplicated `extraResources`, source/test/doc-only repo content, non-runtime `texthooker-ui` files, or the Linux musl libsql binary. Added release-packaging regression coverage and verified the Linux package shrink with fresh local builds.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -0,0 +1,69 @@
---
id: TASK-233
title: Cut patch release v0.9.1 for package size pruning
status: Done
assignee:
- '@codex'
created_date: '2026-03-24 12:40'
updated_date: '2026-03-24 12:55'
labels:
- release
- patch
dependencies:
- TASK-232
references:
- /home/sudacode/projects/japanese/SubMiner/package.json
- /home/sudacode/projects/japanese/SubMiner/CHANGELOG.md
- /home/sudacode/projects/japanese/SubMiner/release/release-notes.md
- /home/sudacode/projects/japanese/SubMiner/docs-site/changelog.md
priority: high
ordinal: 54800
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Publish a patch release for the packaging-size cleanup by bumping the app version to `0.9.1`, generating committed release metadata, and keeping release-facing docs/changelog surfaces aligned.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Repository version metadata is updated to `0.9.1`.
- [x] #2 `CHANGELOG.md`, `release/release-notes.md`, and `docs-site/changelog.md` contain the committed `v0.9.1` release line and the consumed fragment is removed.
- [x] #3 Release-readiness verification passes for changelog, docs, tests, and build lanes.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Completed:
- Bumped `package.json` to `0.9.1`.
- Ran `bun run changelog:build --version 0.9.1 --date 2026-03-24`, which generated `CHANGELOG.md` + `release/release-notes.md` and consumed both pending release fragments.
- Synced `docs-site/changelog.md` with the generated `v0.9.1` release line.
- Confirmed no additional README/docs wording changes were needed beyond changelog surfaces.
Verification:
- `bun run changelog:lint`
- `bun run changelog:check --version 0.9.1`
- `bun run verify:config-example`
- `bun run typecheck`
- `bun run test:fast`
- `bun run test:env`
- `bun run build`
- `bun run docs:test`
- `bun run docs:build`
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Prepared patch release `v0.9.1` locally. Version metadata, committed changelog artifacts, release notes, and docs-site changelog are aligned, and the release gate is green. Pending manual release actions are the release-prep commit, `git tag v0.9.1`, and push/tag publication.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,4 +0,0 @@
type: fixed
area: tokenizer
- Fixed subtitle annotation clearing so explanatory contrast endings like `んですけど` are excluded consistently across the shared tokenizer filter and annotation stage.

View File

@@ -1,5 +1,10 @@
# Changelog # Changelog
## v0.9.1 (2026-03-24)
- Reduced packaged release size by excluding duplicate `extraResources` payload and pruning docs, tests, sourcemaps, and other source-only files from Electron bundles.
- Restored controller navigation and lookup/mining controls while the subtitle sidebar is open, while keeping true modal dialogs blocking controller actions.
- Fixed subtitle annotation clearing so explanatory contrast endings like `んですけど` are excluded consistently across the shared tokenizer filter and annotation stage.
## v0.9.0 (2026-03-23) ## v0.9.0 (2026-03-23)
- Added an app-owned YouTube subtitle flow with absPlayer-style timedtext parsing that auto-loads the default primary subtitle plus a best-effort secondary at startup and resumes once the primary is ready. - Added an app-owned YouTube subtitle flow with absPlayer-style timedtext parsing that auto-loads the default primary subtitle plus a best-effort secondary at startup and resumes once the primary is ready.
- Added a manual YouTube subtitle picker on `Ctrl+Alt+C` so subtitle selection can be retried on demand during active YouTube playback. - Added a manual YouTube subtitle picker on `Ctrl+Alt+C` so subtitle selection can be retried on demand during active YouTube playback.

View File

@@ -1,6 +1,6 @@
{ {
"name": "subminer", "name": "subminer",
"version": "0.9.0", "version": "0.9.1",
"description": "All-in-one sentence mining overlay with AnkiConnect and dictionary integration", "description": "All-in-one sentence mining overlay with AnkiConnect and dictionary integration",
"packageManager": "bun@1.3.5", "packageManager": "bun@1.3.5",
"main": "dist/main-entry.js", "main": "dist/main-entry.js",
@@ -166,20 +166,44 @@
}, },
"files": [ "files": [
"**/*", "**/*",
"!assets{,/**/*}",
"!src{,/**/*}", "!src{,/**/*}",
"!launcher{,/**/*}", "!launcher{,/**/*}",
"!docs{,/**/*}",
"!tests{,/**/*}",
"!packaging{,/**/*}",
"!README.md",
"!CHANGELOG.md",
"!AGENTS.md",
"!CLAUDE.md",
"!stats/src{,/**/*}", "!stats/src{,/**/*}",
"!stats/index.html", "!stats/index.html",
"!stats/public{,/**/*}",
"!stats/package.json",
"!stats/tsconfig.json",
"!stats/vite.config.ts",
"!docs-site{,/**/*}", "!docs-site{,/**/*}",
"!changes{,/**/*}", "!changes{,/**/*}",
"!backlog{,/**/*}", "!backlog{,/**/*}",
"!.tmp{,/**/*}", "!.tmp{,/**/*}",
"!release-*{,/**/*}", "!release-*{,/**/*}",
"!dist/**/*.map",
"!dist/**/*.test.*",
"!dist/**/__tests__{,/**/*}",
"!scripts/**/*.test.*",
"!plugin{,/**/*}",
"!vendor/subminer-yomitan{,/**/*}", "!vendor/subminer-yomitan{,/**/*}",
"!vendor/yomitan-jlpt-vocab{,/**/*}",
"!vendor/texthooker-ui/src{,/**/*}", "!vendor/texthooker-ui/src{,/**/*}",
"!vendor/texthooker-ui/node_modules{,/**/*}", "!vendor/texthooker-ui/node_modules{,/**/*}",
"!vendor/texthooker-ui/.svelte-kit{,/**/*}", "!vendor/texthooker-ui/.svelte-kit{,/**/*}",
"!vendor/texthooker-ui/package-lock.json" "!vendor/texthooker-ui/.vscode{,/**/*}",
"!vendor/texthooker-ui/public{,/**/*}",
"!vendor/texthooker-ui/README.md",
"!vendor/texthooker-ui/package.json",
"!vendor/texthooker-ui/package-lock.json",
"!vendor/texthooker-ui/tsconfig*.json",
"!node_modules/@libsql/linux-x64-musl{,/**/*}"
], ],
"extraResources": [ "extraResources": [
{ {

19
release/release-notes.md Normal file
View File

@@ -0,0 +1,19 @@
## Highlights
### Changed
- Release: Reduced packaged release size by excluding duplicate `extraResources` payload and pruning docs, tests, sourcemaps, and other source-only files from Electron bundles.
### Fixed
- Overlay: Restored controller navigation and lookup/mining controls while the subtitle sidebar is open, while keeping true modal dialogs blocking controller actions.
- Tokenizer: Fixed subtitle annotation clearing so explanatory contrast endings like `んですけど` are excluded consistently across the shared tokenizer filter and annotation stage.
## Installation
See the README and docs/installation guide for full setup steps.
## Assets
- Linux: `SubMiner.AppImage`
- macOS: `SubMiner-*.dmg` and `SubMiner-*.zip`
- Optional extras: `subminer-assets.tar.gz` and the `subminer` launcher
Note: the `subminer` wrapper script uses Bun (`#!/usr/bin/env bun`), so `bun` must be installed and on `PATH`.

View File

@@ -78,6 +78,30 @@ test('release packaging keeps default file inclusion and excludes large source-o
assert.ok(files.includes('!release-*{,/**/*}')); assert.ok(files.includes('!release-*{,/**/*}'));
assert.ok(files.includes('!vendor/subminer-yomitan{,/**/*}')); assert.ok(files.includes('!vendor/subminer-yomitan{,/**/*}'));
assert.ok(files.includes('!vendor/texthooker-ui/src{,/**/*}')); assert.ok(files.includes('!vendor/texthooker-ui/src{,/**/*}'));
assert.ok(files.includes('!assets{,/**/*}'));
assert.ok(files.includes('!plugin{,/**/*}'));
assert.ok(files.includes('!vendor/yomitan-jlpt-vocab{,/**/*}'));
assert.ok(files.includes('!docs{,/**/*}'));
assert.ok(files.includes('!tests{,/**/*}'));
assert.ok(files.includes('!packaging{,/**/*}'));
assert.ok(files.includes('!README.md'));
assert.ok(files.includes('!CHANGELOG.md'));
assert.ok(files.includes('!AGENTS.md'));
assert.ok(files.includes('!CLAUDE.md'));
assert.ok(files.includes('!stats/public{,/**/*}'));
assert.ok(files.includes('!stats/package.json'));
assert.ok(files.includes('!stats/tsconfig.json'));
assert.ok(files.includes('!stats/vite.config.ts'));
assert.ok(files.includes('!dist/**/*.map'));
assert.ok(files.includes('!dist/**/*.test.*'));
assert.ok(files.includes('!dist/**/__tests__{,/**/*}'));
assert.ok(files.includes('!scripts/**/*.test.*'));
assert.ok(files.includes('!vendor/texthooker-ui/public{,/**/*}'));
assert.ok(files.includes('!vendor/texthooker-ui/.vscode{,/**/*}'));
assert.ok(files.includes('!vendor/texthooker-ui/README.md'));
assert.ok(files.includes('!vendor/texthooker-ui/package.json'));
assert.ok(files.includes('!vendor/texthooker-ui/tsconfig*.json'));
assert.ok(files.includes('!node_modules/@libsql/linux-x64-musl{,/**/*}'));
}); });
test('config example generation runs directly from source without unrelated bundle prerequisites', () => { test('config example generation runs directly from source without unrelated bundle prerequisites', () => {

View File

@@ -0,0 +1,36 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { isControllerInteractionBlocked } from './controller-interaction-blocking.js';
test('subtitle sidebar stays controller-passive while other modals block controller input', () => {
assert.equal(
isControllerInteractionBlocked({
controllerSelectModalOpen: false,
controllerDebugModalOpen: false,
jimakuModalOpen: false,
kikuModalOpen: false,
runtimeOptionsModalOpen: false,
subsyncModalOpen: false,
youtubePickerModalOpen: false,
sessionHelpModalOpen: false,
subtitleSidebarModalOpen: true,
}),
false,
);
assert.equal(
isControllerInteractionBlocked({
controllerSelectModalOpen: false,
controllerDebugModalOpen: false,
jimakuModalOpen: false,
kikuModalOpen: false,
runtimeOptionsModalOpen: true,
subsyncModalOpen: false,
youtubePickerModalOpen: false,
sessionHelpModalOpen: false,
subtitleSidebarModalOpen: false,
}),
true,
);
});

View File

@@ -0,0 +1,24 @@
type ControllerInteractionModalState = {
controllerSelectModalOpen: boolean;
controllerDebugModalOpen: boolean;
jimakuModalOpen: boolean;
kikuModalOpen: boolean;
runtimeOptionsModalOpen: boolean;
subsyncModalOpen: boolean;
youtubePickerModalOpen: boolean;
sessionHelpModalOpen: boolean;
subtitleSidebarModalOpen: boolean;
};
export function isControllerInteractionBlocked(state: ControllerInteractionModalState): boolean {
return (
state.controllerSelectModalOpen ||
state.controllerDebugModalOpen ||
state.jimakuModalOpen ||
state.kikuModalOpen ||
state.runtimeOptionsModalOpen ||
state.subsyncModalOpen ||
state.youtubePickerModalOpen ||
state.sessionHelpModalOpen
);
}

View File

@@ -35,6 +35,7 @@ import { createJimakuModal } from './modals/jimaku.js';
import { createKikuModal } from './modals/kiku.js'; import { createKikuModal } from './modals/kiku.js';
import { createSessionHelpModal } from './modals/session-help.js'; import { createSessionHelpModal } from './modals/session-help.js';
import { createSubtitleSidebarModal } from './modals/subtitle-sidebar.js'; import { createSubtitleSidebarModal } from './modals/subtitle-sidebar.js';
import { isControllerInteractionBlocked } from './controller-interaction-blocking.js';
import { createRuntimeOptionsModal } from './modals/runtime-options.js'; import { createRuntimeOptionsModal } from './modals/runtime-options.js';
import { createSubsyncModal } from './modals/subsync.js'; import { createSubsyncModal } from './modals/subsync.js';
import { createYoutubeTrackPickerModal } from './modals/youtube-track-picker.js'; import { createYoutubeTrackPickerModal } from './modals/youtube-track-picker.js';
@@ -88,6 +89,10 @@ function isAnyModalOpen(): boolean {
); );
} }
function isControllerInputBlocked(): boolean {
return isControllerInteractionBlocked(ctx.state);
}
function syncSettingsModalSubtitleSuppression(): void { function syncSettingsModalSubtitleSuppression(): void {
const suppressSubtitles = isAnySettingsModalOpen(); const suppressSubtitles = isAnySettingsModalOpen();
document.body.classList.toggle('settings-modal-open', suppressSubtitles); document.body.classList.toggle('settings-modal-open', suppressSubtitles);
@@ -323,7 +328,7 @@ function startControllerPolling(): void {
}, },
getKeyboardModeEnabled: () => ctx.state.keyboardDrivenModeEnabled, getKeyboardModeEnabled: () => ctx.state.keyboardDrivenModeEnabled,
getLookupWindowOpen: () => ctx.state.yomitanPopupVisible || isYomitanPopupVisible(document), getLookupWindowOpen: () => ctx.state.yomitanPopupVisible || isYomitanPopupVisible(document),
getInteractionBlocked: () => isAnyModalOpen(), getInteractionBlocked: () => isControllerInputBlocked(),
toggleKeyboardMode: () => keyboardHandlers.handleKeyboardModeToggleRequested(), toggleKeyboardMode: () => keyboardHandlers.handleKeyboardModeToggleRequested(),
toggleLookup: () => keyboardHandlers.handleLookupWindowToggleRequested(), toggleLookup: () => keyboardHandlers.handleLookupWindowToggleRequested(),
closeLookup: () => { closeLookup: () => {