Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f635f3042
|
||
|
|
7c637149dd | ||
|
|
33f5f890d5 | ||
|
|
d8c2315405 | ||
|
|
8976d78390
|
||
|
|
1c50d249e0 | ||
|
|
8e29cb3c55 | ||
|
|
fa3051f9cb | ||
|
|
848a6c4021 | ||
|
|
b99409bbcd | ||
|
|
6d0f37ac21 | ||
|
|
696462a82e | ||
|
|
655510cf2e | ||
|
|
af44735bb8 | ||
|
|
804fb8933e
|
||
|
|
2924f04316 | ||
|
|
a9a4c4f083 | ||
|
|
90fc7cf940 | ||
|
|
2a0e931325 | ||
|
|
0129e03c0f
|
||
|
|
586d7950cf
|
||
|
|
33dceabc3e
|
||
|
|
ad1abc3268
|
||
|
|
e59b78dca0 | ||
|
|
203a61a20f
|
||
|
|
a681bbaf25
|
||
|
|
6219a81144 | ||
|
|
cc9af79d6e | ||
|
|
d3a94536a8
|
||
|
|
3068d02f89
|
||
|
|
9150a60630 | ||
|
|
3549e0ed93 | ||
|
|
b0a8b6b3fc
|
||
|
|
153bc5d4bf
|
||
|
|
cbaa4626e1 | ||
|
|
e690ffba07
|
||
|
|
d047e3e0a4 | ||
|
|
01613f1be5 | ||
|
|
bf4e5bb609 | ||
|
|
7909fcd2cf | ||
|
|
8fc20b2499 | ||
|
|
ea2b3a9507 | ||
|
|
a6d96e9966 | ||
|
|
7108e086ba | ||
|
|
78a9b6346f | ||
|
|
78eca52b7e | ||
|
|
b48083ddd8 | ||
|
|
4b6842d034 | ||
|
|
9ffa2bce94
|
||
|
|
88b73656a7
|
||
|
|
e77aa5c3b3 | ||
|
|
121b688afb | ||
|
|
727be5683a | ||
|
|
138bfe9759 | ||
|
|
0aba17d95b
|
||
|
|
a5d0b63792 | ||
|
|
15cb9fe918
|
||
|
|
db45a70ec2
|
||
|
|
9e79fa93c2 | ||
|
|
9fadbc8cdd
|
||
|
|
ffbbe698fc | ||
|
|
7b72dd2cc4
|
||
|
|
416cd7e9ac
|
||
|
|
eb749f8c19
|
||
|
|
65ac5a7728 | ||
|
|
df675cfbfe
|
||
|
|
e49b5f6589
|
||
|
|
0bbd8617f4 | ||
|
|
4e43c5c6e1 | ||
|
|
953abdf42a
|
||
|
|
fdb0a31fff
|
||
|
|
04b878dd93
|
||
|
|
07b32626a6 | ||
|
|
c68b73c8c1 | ||
|
|
b24008237b | ||
|
|
63eb228e4f | ||
|
|
ba0155e429 | ||
|
|
23af9c6464
|
||
|
|
8825cca642
|
||
|
|
bc73a02623 | ||
|
|
5096ca217b
|
||
|
|
285b19ccc3
|
||
|
|
7fbc36dc41
|
||
|
|
e93b696e71
|
||
|
|
7c161723cd | ||
|
|
b0fb39a8f5 | ||
|
|
a7a302bdd0 | ||
|
|
6a1d1211ae
|
||
|
|
7585d430ae
|
||
|
|
122ff9a3c5
|
||
|
|
786bd5af81
|
||
|
|
42d1384c68 | ||
|
|
1508863dbb | ||
|
|
4dd30f44d1
|
||
|
|
ab48a5678e | ||
|
|
2f21582666 | ||
|
|
383aed8bad | ||
|
|
f9c4e892dc | ||
|
|
7959e1a4e5 | ||
|
|
917d52dd94 | ||
|
|
05425cc5db | ||
|
|
8097ed27e5 | ||
|
|
76e43d7e6d | ||
|
|
4f0762e840 | ||
|
|
026d495fac | ||
|
|
dd76782d30
|
||
|
|
a8c16147ad | ||
|
|
186d4a0640 | ||
|
|
2831e05124 | ||
|
|
6841a37a22 | ||
|
|
7c1eac03dc | ||
|
|
e6dc9dfec5
|
||
|
|
6d69a56574 | ||
|
|
0c37c665a2 | ||
|
|
8ae5bde6a4
|
||
|
|
016a43b804 | ||
|
|
fb2d1a512b | ||
|
|
614a8ca912 | ||
|
|
9eecb7358b
|
||
|
|
1e5d7747b4 | ||
|
|
c14c690875
|
||
|
|
84f718043a | ||
|
|
99266294b8 | ||
|
|
c055359be1
|
||
|
|
ec5a147095 | ||
|
|
a0635f4360 | ||
|
|
c0a78ef008 | ||
|
|
87b01155df | ||
|
|
fc5c49e365 | ||
|
|
a20269e9f5 | ||
|
|
2ad491e95c
|
||
|
|
6fffcc731f | ||
|
|
5cc21113fd | ||
|
|
051140f910 | ||
|
|
6e945f0872 | ||
|
|
c2c25c0da6 | ||
|
|
556de61756
|
||
|
|
a98fb0fddf
|
||
|
|
e816b3b371 | ||
|
|
ed7d3f4c3d | ||
|
|
509dc5bf7f | ||
|
|
0a0aa3ec98
|
||
|
|
b87cc3cfdd
|
||
|
|
8cb3c8c90a
|
||
|
|
03ea903927
|
||
|
|
3aea42e6f8
|
||
|
|
88bb3edfa4
|
||
|
|
9445aef004
|
||
|
|
c01bcd9d0f
|
||
|
|
695613b1e7
|
||
|
|
20797772f8 | ||
|
|
72c312810a
|
||
|
|
b61f050e78
|
||
|
|
2a77ba9dd4 | ||
|
|
4ed878270f
|
||
|
|
1d1b0c7bb6
|
||
|
|
db61ce358d
|
||
|
|
e9778a945a | ||
|
|
7de73e16a1 | ||
|
|
273652f781 | ||
|
|
14c0086520
|
||
|
|
ff0bc0af6d | ||
|
|
00b1b79bf4 | ||
|
|
e11a5fea0d
|
||
|
|
f73fe179d0
|
||
|
|
2938e7a32a | ||
|
|
82f6b4705a | ||
|
|
a02c33dac4 | ||
|
|
2174e689a2 | ||
|
|
7d729cb60c | ||
|
|
d963def1a9 | ||
|
|
6fb3d2eb13 | ||
|
|
c5a77ac067 | ||
|
|
8ddc151435
|
||
|
|
b98d4d65c7 | ||
|
|
046e74ea91
|
||
|
|
8bf847503d | ||
|
|
47b5903392
|
||
|
|
bf85554d1e | ||
|
|
d74c7e1235
|
||
|
|
57ddd19953 | ||
|
|
ee25536d90 | ||
|
|
7b0fbdf254 | ||
|
|
2fefc83e3f | ||
|
|
dbdf578c68 | ||
|
|
441ecf3c04 | ||
|
|
a0dde4ee3e
|
||
|
|
fe4dacc1e7 | ||
|
|
b08cd0db35 | ||
|
|
bffb1c5982 | ||
|
|
5b8848518a | ||
|
|
176edd67f1
|
||
|
|
4d65dec340 | ||
|
|
6607c333bc
|
||
|
|
b2bbf1ae12
|
||
|
|
b204d4dd6e | ||
|
|
89ed675935 | ||
|
|
1995200e76
|
||
|
|
e876e483d6
|
||
|
|
6d2a72e13b | ||
|
|
0d7084c8aa | ||
|
|
95e0abc7b7
|
||
|
|
d9155ceacb
|
||
|
|
9d0585423c
|
||
|
|
455cfff90a
|
||
|
|
987b325edb | ||
|
|
08c6807cb1
|
||
|
|
18c6410f24
|
||
|
|
e223cf9b71
|
||
|
|
c718bb8b34
|
||
|
|
177f7e5fd6
|
||
|
|
20b50ec4e1 | ||
|
|
deae61b211 | ||
|
|
44959ed282
|
||
|
|
2398f5c030 | ||
|
|
8711cf1a48
|
||
|
|
9f4888293b
|
||
|
|
29332b103e
|
||
|
|
8b2cee2c58 | ||
|
|
2e2ee3f028 | ||
|
|
49b926e08c | ||
|
|
66f8ca4f80 | ||
|
|
6fe1e0fee4 | ||
|
|
0ab840b362 | ||
|
|
21a06c12fb | ||
|
|
14070acceb
|
||
|
|
7d81342f0f
|
||
|
|
4b7f750919 | ||
|
|
6ab3d823a4 | ||
|
|
8797719a09
|
||
|
|
8acc78cc1c | ||
|
|
321461c50f | ||
|
|
beeab564b4
|
||
|
|
cab7975a2b
|
||
|
|
846292809c
|
||
|
|
8712780d08 | ||
|
|
7b1a005a65 | ||
|
|
84c75f50aa
|
||
|
|
8b838f2c7d
|
||
|
|
db4139ba0b
|
||
|
|
6c251502b3 | ||
|
|
ae40934d3a | ||
|
|
cdb1475a54
|
||
|
|
a2e49b369b
|
||
|
|
7f13aed50a | ||
|
|
8b21a2bca8
|
||
|
|
925413adfe
|
||
|
|
d0644ab2eb | ||
|
|
d253710c2e | ||
|
|
c3df510e4f
|
||
|
|
187f68e5b6 | ||
|
|
0e254cbbef
|
||
|
|
7b94adafbd
|
||
|
|
61f39d1e09 | ||
|
|
e7739de51c | ||
|
|
ad1d240f20 | ||
|
|
8b0ef662bc
|
||
|
|
0a58c20ad7 | ||
|
|
38ddb29aa0 | ||
|
|
8b9a70c5a6 | ||
|
|
48a084914a | ||
|
|
a042b04357 | ||
|
|
35ca2afc6f | ||
|
|
b14f977e33 | ||
|
|
eef4500599 | ||
|
|
73af1451b7
|
||
|
|
36a3704815 | ||
|
|
359cb0a301 | ||
|
|
4b10e85053
|
||
|
|
c942a2cf2d | ||
|
|
f65afa6046 | ||
|
|
389d8e06e0
|
||
|
|
0008b55b70
|
||
|
|
d16ae9c745
|
||
|
|
36f94151b8 | ||
|
|
5326ad32f5 | ||
|
|
d199376364
|
||
|
|
3aee88c150
|
||
|
|
70da3ee8bd | ||
|
|
aa8eb753f6
|
||
|
|
8d73de8731
|
||
|
|
5a98397efe
|
||
|
|
a117c5759c | ||
|
|
ae7e6f82a8 | ||
|
|
1158be5b39 | ||
|
|
33e767458f | ||
|
|
94a65416ae | ||
|
|
0a384a22c9 | ||
|
|
b3b45521b6
|
||
|
|
131b23efa9
|
||
|
|
e2afceb492 | ||
|
|
7be1843c41 | ||
|
|
c09d009a3e
|
||
|
|
2007e28be8 | ||
|
|
d5bfdcae7b | ||
|
|
311f1e8ee5 | ||
|
|
e6a16a069b | ||
|
|
af67c53dd6 | ||
|
|
ea79e331fa
|
||
|
|
ee89b0c8a9 | ||
|
|
f2fd58cd2b | ||
|
|
1280a30216
|
||
|
|
a80ed72b2d
|
||
|
|
4cc6c12dc7 | ||
|
|
425004879a | ||
|
|
76f99e6518 | ||
|
|
f1e260e996 | ||
|
|
54e90754ef
|
||
|
|
487143802a | ||
|
|
e6a004ab8b | ||
|
|
b510c54875 | ||
|
|
e1ea464bc9 | ||
|
|
b46b8dfa41 | ||
|
|
ca067a6ccf | ||
|
|
d719b346e0 | ||
|
|
a1da3dcdc8
|
||
|
|
9927ef1581
|
||
|
|
791c993870
|
||
|
|
38dbce517c
|
||
|
|
889dc9c009
|
||
|
|
097021072d
|
||
|
|
91c8eb8faf
|
||
|
|
eed0a6a243 | ||
|
|
d33009d4a3 | ||
|
|
8d0535f3ca | ||
|
|
75f9b8a803
|
||
|
|
c30cce0a14
|
||
|
|
3e6591e390 | ||
|
|
f033f87329 | ||
|
|
29cb8c7fb4
|
||
|
|
1dcfed86ab | ||
|
|
efe50ed1e4 | ||
|
|
5b44981688 | ||
|
|
2add95d541
|
||
|
|
f62fff2585
|
||
|
|
11c196821d | ||
|
|
43ebc7d371
|
||
|
|
639e331f24 | ||
|
|
78be72e32f
|
||
|
|
3932e53ced | ||
|
|
097b619d71 | ||
|
|
f7abcedd75
|
||
|
|
807c0ff3db | ||
|
|
7e6f9672cf | ||
|
|
9fe13601fb
|
||
|
|
920cbab1bc | ||
|
|
17d97f0b7e | ||
|
|
10463e7348
|
||
|
|
e9abbd5f05
|
||
|
|
d6ff50455a
|
||
|
|
b1bdeabca8 | ||
|
|
da3c971ee6 | ||
|
|
c02edc90cc
|
||
|
|
4d1a20d69b
|
||
|
|
7e86c4ea3d | ||
|
|
c4f99fec2f | ||
|
|
c6328eef09
|
||
|
|
dc52bc2fba | ||
|
|
a54f03f0cd | ||
|
|
799cce6991 | ||
|
|
6b2cb002ac | ||
|
|
e84674e3b5 | ||
|
|
6ca5cede3e
|
||
|
|
4d010e6a18
|
||
|
|
5250ca8214 | ||
|
|
49f89e6452 | ||
|
|
89723e2ccb
|
||
|
|
d05e2bd8ec
|
||
|
|
7484d3c102
|
||
|
|
f78a875ba3
|
||
|
|
a025652542
|
||
|
|
91a01b86a9 | ||
|
|
105713361e | ||
|
|
4cb0dbfaad | ||
|
|
801cdcafca
|
||
|
|
094bcce0dc
|
||
|
|
d1ec678d7a
|
||
|
|
f0324cd93a
|
||
|
|
1b2ee03678
|
||
|
|
18e61d2048
|
||
|
|
f0a11c2c99
|
||
|
|
bc8d1edf6f
|
||
|
|
d48d880ba3 | ||
|
|
7c9b65db8b | ||
|
|
e5c1135501 | ||
|
|
430373f010 | ||
|
|
b68d17614d
|
||
|
|
30712738dc | ||
|
|
0915b23dc8 | ||
|
|
db30c61327 | ||
|
|
27f5b2bb58
|
||
|
|
baabdb6d30
|
||
|
|
3a67e23bc3
|
||
|
|
13e2b5f8c8
|
||
|
|
53aa58d044 | ||
|
|
d8934647a9 | ||
|
|
7ac51cd5e9
|
||
|
|
52bab1d611 | ||
|
|
49e46e6b9b
|
||
|
|
c1c40c8d40
|
||
|
|
c71482cb44
|
||
|
|
05cf4a6fe5 | ||
|
|
9b4de93283
|
||
|
|
16ffbbc4b3
|
||
|
|
de4f3efa30
|
||
|
|
bc7dde3b02 | ||
|
|
7a64488ed5
|
||
|
|
5f3c3871d3 | ||
|
|
4d24e22bb5 | ||
|
|
c47cfb52af | ||
|
|
da0087bba6
|
||
|
|
8338f27794
|
||
|
|
b029d65c90
|
||
|
|
c24f99899b
|
||
|
|
3aca581764
|
||
|
|
ba540d09b2
|
||
|
|
6530d2ccbc
|
||
|
|
a784091ecb
|
||
|
|
61c3e1e3c6 | ||
|
|
ce76a75630
|
||
|
|
52249db5b4
|
||
|
|
09d8b52fbf
|
||
|
|
0edd566904
|
||
|
|
6eb1b0f197
|
||
|
|
e4137d9760
|
||
|
|
864f4124ae
|
||
|
|
7514985feb | ||
|
|
d6c72806bb | ||
|
|
3502cdc607
|
||
|
|
d51e7fe401 | ||
|
|
f9a4039ad2
|
||
|
|
8e5c21b443
|
||
|
|
55b350c3a2
|
||
|
|
54324df3be
|
||
|
|
35adf8299c | ||
|
|
2d4f2d1139
|
||
|
|
77e632276b
|
||
|
|
4c95b57885
|
||
|
|
242402b253
|
||
|
|
61d15f9431
|
||
|
|
508864bcbb
|
||
|
|
23c54bb01e
|
||
|
|
ec667c64e8
|
||
|
|
39b2ccad8e
|
||
|
|
23815945bf
|
||
|
|
9dca83acd9
|
||
|
|
55300e2d8c
|
||
|
|
28afd15134
|
||
|
|
58304757aa
|
||
|
|
c95518e94a
|
||
|
|
5ee4617607 | ||
|
|
842008b089
|
||
|
|
6f56a0bcf6
|
||
|
|
5feed360ca | ||
|
|
c17f0a4080 | ||
|
|
0317c7f011 | ||
|
|
13797b5005
|
||
|
|
b24d9d7487
|
||
|
|
3a01cffc6b | ||
|
|
eddf6f0456
|
||
|
|
f6c024d61e
|
||
|
|
6749ff843c | ||
|
|
42abdd1268
|
||
|
|
5d914b1547
|
||
|
|
50b45cac0b | ||
|
|
e35aac6ee0
|
||
|
|
fe2da22d29
|
||
|
|
2045ffbff8
|
||
|
|
478869ff28 | ||
|
|
9eed37420e | ||
|
|
99f4d2baaf | ||
|
|
f4e8c3feec
|
||
|
|
d0b308f340
|
||
|
|
1b56360a24 | ||
|
|
68833c76c4
|
||
|
|
4d7c80f2e4 | ||
|
|
2f17859b7b
|
||
|
|
9c7e02cbf0
|
||
|
|
5f320edab5
|
||
|
|
f7ce3371a1
|
||
|
|
71805dccd7
|
||
|
|
a13b7352d7
|
||
|
|
ff5ecdaded
|
||
|
|
b32c3cf58c
|
||
|
|
b791262860
|
||
|
|
29b8c6e02a
|
||
|
|
618727d8e8
|
||
|
|
fed60c265d
|
||
|
|
a34a7489db
|
||
|
|
e59192bbe1
|
||
|
|
e0f82d28f0
|
||
|
|
a0521aeeaf
|
||
|
|
2127f759ca
|
||
|
|
5e787183d0
|
||
|
|
81ca31b899
|
||
|
|
e2a7597b4f
|
||
|
|
2e59c21078
|
||
|
|
7b5ab3294d
|
||
|
|
2bbf38f987
|
||
|
|
f09c91494d
|
||
|
|
58ec9b76e0
|
||
|
|
7a196f69d6
|
||
|
|
c799a8de3c | ||
|
|
34d2dce8dc
|
||
|
|
3a22a97761
|
||
|
|
962243e959
|
||
|
|
021010a338
|
||
|
|
4c0575afe0
|
||
|
|
9e46176519
|
||
|
|
f10e905dbd
|
||
|
|
e4aa8ff907 | ||
|
|
a6ece5388a
|
||
|
|
6a44b54b51
|
||
|
|
93cd688625
|
||
|
|
8e319a417d
|
||
|
|
38034db1e4
|
||
|
|
f775f90360
|
||
|
|
55dff6ced7
|
||
|
|
d0c11d347b
|
||
|
|
f0418c6e56
|
||
|
|
e18985fb14 |
No files matched your search
@@ -0,0 +1,46 @@
|
||||
---
|
||||
name: subminer-change-verification
|
||||
description: Verify SubMiner changes with repo-native cheap-first test lanes. Use after code, config, launcher, plugin, runtime, stats, documentation, or workflow changes; do not use for read-only questions.
|
||||
---
|
||||
|
||||
# SubMiner Change Verification
|
||||
|
||||
Verify the behavior claimed by a change without running unrelated expensive checks by default.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Inspect the requested scope and changed paths with `git status --short` and `git diff`.
|
||||
2. Read `docs/workflow/verification.md` as the source of truth for maintained lanes.
|
||||
3. Run the cheapest lane or lanes that cover the changed behavior.
|
||||
4. Escalate to the full handoff gate only for substantial or cross-boundary changes.
|
||||
5. Report exact commands, results, skipped checks, blockers, and remaining risk.
|
||||
|
||||
Do not use hidden wrapper commands. Verification commands are owned by `package.json` and the workflow documentation.
|
||||
|
||||
## Lane Selection
|
||||
|
||||
- Internal docs, `AGENTS.md`, or `.agents/skills/**`: `bun run test:docs:kb`
|
||||
- User-facing `docs-site/**`: `bun run docs:test`, then `bun run docs:build`
|
||||
- Config/schema/defaults: `bun run test:config`
|
||||
- If defaults or templates changed, also run `bun run generate:config-example` and `bun run verify:config-example`.
|
||||
- General TypeScript source: `bun run typecheck`, then `bun run test:fast`
|
||||
- Launcher or mpv plugin: `bun run test:launcher` or `bun run test:env`, based on the behavior changed
|
||||
- Runtime compatibility or dist-sensitive wiring: `bun run test:runtime:compat`
|
||||
- Stats dashboard: `bun run test:stats`
|
||||
- Overlay, renderer, mpv, or mining behavior in the running app: `bun run build`, then `bun run test:e2e`
|
||||
- Build/release scripts: `bun run test:scripts`
|
||||
|
||||
For substantial changes, use the full gate documented in `AGENTS.md` and `docs/workflow/verification.md`.
|
||||
|
||||
## Runtime Escalation
|
||||
|
||||
Real runtime checks are required when the claim depends on actual Electron, mpv, overlay, focus, window tracking, launch, or socket behavior. Run the relevant application flow when the environment supports it. Otherwise, report the missing runtime dependency and do not present cheaper checks as authoritative runtime validation.
|
||||
|
||||
## Pre-Handoff Checks
|
||||
|
||||
Before handoff, reconcile both questions:
|
||||
|
||||
1. Do behavior, defaults, flags, shortcuts, ports, APIs, architecture, or workflow changes require documentation updates?
|
||||
2. Does the change require a current-outcome fragment under `changes/` according to `changes/README.md`?
|
||||
|
||||
Complete required updates before handoff or report the blocker.
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: subminer-release
|
||||
description: Prepare, cut, publish, or repair SubMiner stable and prerelease releases. Use for hands-on release work; do not use for general release questions.
|
||||
---
|
||||
|
||||
# SubMiner release
|
||||
|
||||
Carry out the requested release phase using the repository's current release process.
|
||||
|
||||
## Source of truth
|
||||
|
||||
Read `docs/RELEASING.md` completely before changing files or release state. Treat it as canonical. Read `changes/README.md` when the work touches change fragments or generated release notes.
|
||||
|
||||
Do not copy release commands or policy into this skill. If this skill disagrees with the release guide, follow the guide and reconcile the skill before handoff.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Identify whether the request is for a stable release, prerelease, release preparation, publication, or repair.
|
||||
2. Inspect the current branch, worktree status, package version, pending change fragments, relevant tags, and latest CI state before making changes.
|
||||
3. Follow the matching procedure in `docs/RELEASING.md` in order. Review generated changelog and release-note Markdown before it can be committed or published.
|
||||
4. Run every required gate for the requested release phase. Do not treat a cheaper test lane as a substitute for the documented release gate.
|
||||
5. Before a stable tag, confirm the package and tag versions match and no pending `changes/*.md` fragments remain. Preserve fragments for prereleases as documented.
|
||||
6. Report the resulting version, completed checks, local commit and tag state, remote publication state, skipped platform checks, and any remaining manual work.
|
||||
|
||||
## Authorization boundaries
|
||||
|
||||
- A request to prepare a release stops before commit, tag, push, or remote publication unless the user also authorizes those actions.
|
||||
- A clear request to cut or publish a release includes the documented commit, tag, and push steps. Ask before the first remote mutation when the wording is ambiguous.
|
||||
- Do not edit an existing GitHub release, publish to the AUR, change secrets, or alter signing configuration unless the user explicitly requests that operation.
|
||||
- Do not switch branches without consent.
|
||||
|
||||
## Stop conditions
|
||||
|
||||
Stop and report the blocker when required CI or a release gate fails, authentication is missing, versions disagree, required artifacts are absent, or the worktree contains unexpected changes that overlap the release. Do not tag or publish a partially verified release.
|
||||
@@ -0,0 +1,15 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [ksyasuda]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: sudacode
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
polar: # Replace with a single Polar username
|
||||
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
||||
thanks_dev: # Replace with a single thanks.dev username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
@@ -0,0 +1,105 @@
|
||||
name: Bug Report
|
||||
description: Report something that is broken or behaving incorrectly
|
||||
title: '[Bug]: '
|
||||
labels: ['bug']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to file a bug report! Please search [existing issues](https://github.com/ksyasuda/SubMiner/issues?q=is%3Aissue) first to avoid duplicates.
|
||||
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: A clear description of the bug, including what you expected to happen instead.
|
||||
placeholder: When I open the Yomitan popup, the overlay freezes...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Minimal, ordered steps that reliably trigger the bug.
|
||||
placeholder: |
|
||||
1. Launch `subminer`
|
||||
2. Play a video in MPV
|
||||
3. Hover a word and press ...
|
||||
4. See error
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: area
|
||||
attributes:
|
||||
label: Affected area
|
||||
description: Which part of SubMiner is affected?
|
||||
options:
|
||||
- Overlay / Yomitan popup
|
||||
- Anki mining
|
||||
- Subtitle annotations
|
||||
- Subtitle sidebar
|
||||
- Immersion tracking / stats
|
||||
- Launcher / CLI
|
||||
- MPV plugin
|
||||
- Jellyfin integration
|
||||
- Jimaku integration
|
||||
- AniList integration
|
||||
- YouTube integration
|
||||
- Character dictionary
|
||||
- WebSocket / texthooker API
|
||||
- Configuration
|
||||
- Documentation
|
||||
- Other / not sure
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating system
|
||||
options:
|
||||
- Linux
|
||||
- macOS
|
||||
- Windows
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: SubMiner version
|
||||
description: Output of `subminer --version`, or the release tag / commit you are running.
|
||||
placeholder: v0.15.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: compositor
|
||||
attributes:
|
||||
label: Compositor (Linux only)
|
||||
description: SubMiner's overlay supports Hyprland and sway. Name yours (and version if known). Leave blank on macOS / Windows.
|
||||
placeholder: Hyprland 0.55
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: mpv-version
|
||||
attributes:
|
||||
label: MPV version
|
||||
description: Output of `mpv --version` (first line).
|
||||
placeholder: mpv 0.38.0
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Logs / console output
|
||||
description: |
|
||||
Relevant logs. For verbose output, run `electron . --dev --log-level debug`.
|
||||
This will be rendered as code automatically — no backticks needed.
|
||||
render: shell
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Documentation
|
||||
url: https://docs.subminer.moe
|
||||
about: Setup, configuration, and feature docs — check here before filing an issue.
|
||||
- name: Troubleshooting guide
|
||||
url: https://docs.subminer.moe/troubleshooting
|
||||
about: Common problems and fixes (Hyprland rules, MPV detection, Anki connection, etc.).
|
||||
@@ -0,0 +1,59 @@
|
||||
name: Feature Request
|
||||
description: Suggest a new feature or an improvement to an existing one
|
||||
title: '[Feature]: '
|
||||
labels: ['enhancement']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for the idea! Please search [existing issues](https://github.com/ksyasuda/SubMiner/issues?q=is%3Aissue) first to avoid duplicates.
|
||||
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: Problem / motivation
|
||||
description: What problem are you trying to solve? What is missing or frustrating today?
|
||||
placeholder: When mining a card I have to manually switch to Anki because...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: proposal
|
||||
attributes:
|
||||
label: Proposed solution
|
||||
description: Describe the feature or change you'd like to see.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: area
|
||||
attributes:
|
||||
label: Related area
|
||||
description: Which part of SubMiner does this relate to?
|
||||
options:
|
||||
- Overlay / Yomitan popup
|
||||
- Anki mining
|
||||
- Subtitle annotations
|
||||
- Subtitle sidebar
|
||||
- Immersion tracking / stats
|
||||
- Launcher / CLI
|
||||
- MPV plugin
|
||||
- Jellyfin integration
|
||||
- Jimaku integration
|
||||
- AniList integration
|
||||
- YouTube integration
|
||||
- Character dictionary
|
||||
- WebSocket / texthooker API
|
||||
- Configuration
|
||||
- Documentation
|
||||
- Other / new area
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Alternatives considered
|
||||
description: Any workarounds you currently use or other approaches you've thought about.
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1,36 @@
|
||||
<!--
|
||||
Thanks for contributing to SubMiner! Fill out the sections below.
|
||||
Keep it short — a couple of sentences per section is fine.
|
||||
-->
|
||||
|
||||
## Summary
|
||||
|
||||
<!-- What does this PR do and why? -->
|
||||
|
||||
## Type of change
|
||||
|
||||
<!-- Check all that apply. -->
|
||||
|
||||
- [ ] Bug fix
|
||||
- [ ] New feature
|
||||
- [ ] Refactor / internal
|
||||
- [ ] Documentation
|
||||
- [ ] Other
|
||||
|
||||
## Related issues
|
||||
|
||||
<!-- e.g. "Closes #123". Delete if none. -->
|
||||
|
||||
## How was this tested?
|
||||
|
||||
<!--
|
||||
Describe verification. The default handoff gate is:
|
||||
bun run typecheck && bun run test:fast && bun run test:env && bun run build && bun run test:smoke:dist
|
||||
If docs-site/ changed, also: bun run docs:test && bun run docs:build
|
||||
-->
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Reconciled current-outcome changelog fragment(s), or this PR is labeled `skip-changelog` (see [`changes/README.md`](../changes/README.md))
|
||||
- [ ] Docs updated in the same PR if behavior, defaults, flags, shortcuts, ports, or APIs changed
|
||||
- [ ] Relevant checks pass locally (typecheck, tests, build)
|
||||
@@ -8,67 +8,89 @@ on:
|
||||
|
||||
jobs:
|
||||
build-test-audit:
|
||||
runs-on: ubuntu-latest
|
||||
uses: ./.github/workflows/quality-gate.yml
|
||||
|
||||
# Boots the built app with a real mpv on each platform. Kept outside the
|
||||
# reusable quality gate so releases do not depend on it while the lane is new.
|
||||
e2e:
|
||||
name: E2E (${{ matrix.os }})
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
env:
|
||||
SUBMINER_E2E_KEEP: '1'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.5
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
- name: Point temp files at an uploadable directory
|
||||
run: |
|
||||
# os.tmpdir() follows these, so the kept session logs land under runner.temp.
|
||||
dir="$RUNNER_TEMP/e2e"
|
||||
mkdir -p "$dir"
|
||||
{ echo "TMPDIR=$dir"; echo "TEMP=$dir"; echo "TMP=$dir"; } >> "$GITHUB_ENV"
|
||||
|
||||
- name: Install mpv and ffmpeg (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
# Only Ubuntu sources are needed; unrelated runner repositories can be unavailable.
|
||||
apt_sources=(-o Dir::Etc::sourcelist=sources.list.d/ubuntu.sources -o Dir::Etc::sourceparts=-)
|
||||
sudo apt-get "${apt_sources[@]}" update
|
||||
sudo apt-get "${apt_sources[@]}" install -y mpv ffmpeg xvfb
|
||||
|
||||
- name: Install mpv and ffmpeg (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
# Runner images bake in different Homebrew metadata, so without an update the
|
||||
# installed mpv build depends on which image the job lands on.
|
||||
brew update
|
||||
brew install mpv ffmpeg
|
||||
mpv --version
|
||||
|
||||
- name: Install mpv and ffmpeg (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
choco install mpv ffmpeg -y --no-progress
|
||||
# The mpv package adds no shim, so put its directory on PATH for later steps.
|
||||
mpv_exe="$(find /c/ProgramData/chocolatey/lib /c/ProgramData/chocolatey/bin "/c/Program Files" /c/tools -maxdepth 6 -iname mpv.exe 2>/dev/null | head -n 1)"
|
||||
test -n "$mpv_exe"
|
||||
cygpath -w "$(dirname "$mpv_exe")" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
run: |
|
||||
bun install --frozen-lockfile
|
||||
cd stats && bun install --frozen-lockfile
|
||||
|
||||
- name: Build (TypeScript check)
|
||||
# Keep explicit typecheck for fast fail before full build/bundle.
|
||||
run: bun run tsc --noEmit
|
||||
- name: Build
|
||||
run: bun run build
|
||||
|
||||
- name: Test suite (source)
|
||||
run: bun run test:fast
|
||||
- name: End-to-end suite
|
||||
run: bun run test:e2e
|
||||
|
||||
- name: Launcher smoke suite (source)
|
||||
run: bun run test:launcher:smoke:src
|
||||
- name: Dictionary backend windows (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
run: xvfb-run -a bun run test:dictionary:electron
|
||||
|
||||
- name: Upload launcher smoke artifacts (on failure)
|
||||
- name: Upload session logs (on failure)
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: launcher-smoke
|
||||
path: .tmp/launcher-smoke/**
|
||||
name: e2e-logs-${{ matrix.os }}
|
||||
path: ${{ runner.temp }}/e2e/subminer-e2e-*/*.log
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Build (bundle)
|
||||
run: bun run build
|
||||
|
||||
- name: Dist smoke suite
|
||||
run: bun run test:smoke:dist
|
||||
|
||||
- name: Build docs
|
||||
run: bun run docs:build
|
||||
|
||||
- name: Security audit
|
||||
run: bun audit --audit-level high
|
||||
continue-on-error: true
|
||||
|
||||
- name: Build Bun subminer wrapper
|
||||
run: make build-launcher
|
||||
|
||||
- name: Verify Bun subminer wrapper
|
||||
run: dist/launcher/subminer --help >/dev/null
|
||||
|
||||
- name: Enforce generated launcher workflow
|
||||
run: bash scripts/verify-generated-launcher.sh
|
||||
@@ -1,49 +0,0 @@
|
||||
name: Claude Code
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_review_comment:
|
||||
types: [created]
|
||||
issues:
|
||||
types: [opened, assigned]
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
jobs:
|
||||
claude:
|
||||
if: |
|
||||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
|
||||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
|
||||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
issues: read
|
||||
id-token: write
|
||||
actions: read # Required for Claude to read CI results on PRs
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Run Claude Code
|
||||
id: claude
|
||||
uses: anthropics/claude-code-action@v1
|
||||
with:
|
||||
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
||||
|
||||
# This is an optional setting that allows Claude to read CI results on PRs
|
||||
additional_permissions: |
|
||||
actions: read
|
||||
|
||||
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
|
||||
# prompt: 'Update the pull request description to include a summary of changes.'
|
||||
|
||||
# Optional: Add claude_args to customize behavior and configuration
|
||||
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
||||
# or https://code.claude.com/docs/en/cli-reference for available options
|
||||
# claude_args: '--allowed-tools Bash(gh pr:*)'
|
||||
@@ -0,0 +1,88 @@
|
||||
name: Docs Pages
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
rebuild_archives:
|
||||
description: 'Stable tags to rebuild in R2 (comma-separated, or "all"); archives are otherwise built once'
|
||||
required: false
|
||||
default: ''
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- 'v*'
|
||||
paths:
|
||||
- 'docs-site/**'
|
||||
- 'scripts/docs-versioning.ts'
|
||||
- 'scripts/docs-versioned-assets.ts'
|
||||
- 'scripts/docs-archive-store.ts'
|
||||
- 'scripts/build-versioned-docs.ts'
|
||||
- '.github/workflows/docs-pages.yml'
|
||||
- 'package.json'
|
||||
- 'bun.lock'
|
||||
|
||||
concurrency:
|
||||
group: docs-pages-production
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
if: ${{ github.ref_type != 'tag' || !contains(github.ref_name, '-') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Guard stable docs tag shape
|
||||
id: tag_guard
|
||||
if: github.ref_type == 'tag'
|
||||
env:
|
||||
TAG_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
if [[ ! "$TAG_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "::notice::Skipping non-stable docs tag $TAG_NAME"
|
||||
echo "stable_tag=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
echo "stable_tag=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Setup Bun
|
||||
if: steps.tag_guard.outputs.stable_tag != 'false'
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.5
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.tag_guard.outputs.stable_tag != 'false'
|
||||
run: |
|
||||
bun install --frozen-lockfile
|
||||
cd docs-site && bun install --frozen-lockfile
|
||||
|
||||
- name: Test docs
|
||||
if: steps.tag_guard.outputs.stable_tag != 'false'
|
||||
run: bun run docs:test
|
||||
|
||||
# Builds only archives missing from R2 (plus any requested rebuilds), then the
|
||||
# root and /main/ builds that make up the Pages deployment.
|
||||
- name: Build versioned docs
|
||||
if: steps.tag_guard.outputs.stable_tag != 'false'
|
||||
env:
|
||||
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
DOCS_ARCHIVE_R2_ACCESS_KEY_ID: ${{ secrets.DOCS_ARCHIVE_R2_ACCESS_KEY_ID }}
|
||||
DOCS_ARCHIVE_R2_SECRET_ACCESS_KEY: ${{ secrets.DOCS_ARCHIVE_R2_SECRET_ACCESS_KEY }}
|
||||
DOCS_ARCHIVE_R2_BUCKET: ${{ vars.DOCS_ARCHIVE_R2_BUCKET }}
|
||||
REBUILD_ARCHIVES: ${{ inputs.rebuild_archives }}
|
||||
run: bun run scripts/build-versioned-docs.ts --require-archives "--rebuild-archives=${REBUILD_ARCHIVES}"
|
||||
|
||||
- name: Deploy docs to Cloudflare Pages
|
||||
if: steps.tag_guard.outputs.stable_tag != 'false'
|
||||
uses: cloudflare/wrangler-action@v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
# Run from docs-site so Wrangler bundles docs-site/functions (the /v/* archive server).
|
||||
workingDirectory: docs-site
|
||||
command: pages deploy ../.tmp/docs-versioned-site --project-name "${{ vars.CLOUDFLARE_PAGES_PROJECT_NAME }}" --branch main
|
||||
@@ -0,0 +1,238 @@
|
||||
name: Package release
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
CSC_LINK:
|
||||
required: true
|
||||
CSC_KEY_PASSWORD:
|
||||
required: true
|
||||
APPLE_ID:
|
||||
required: true
|
||||
APPLE_APP_SPECIFIC_PASSWORD:
|
||||
required: true
|
||||
APPLE_TEAM_ID:
|
||||
required: true
|
||||
# Project TMDB key baked into release artifacts; builds stay valid without it.
|
||||
SUBMINER_TMDB_API_KEY:
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.5
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
stats/node_modules
|
||||
vendor/texthooker-ui/node_modules
|
||||
vendor/subminer-yomitan/node_modules
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/texthooker-ui/bun.lock', 'vendor/subminer-yomitan/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ runner.arch }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
bun install --frozen-lockfile
|
||||
cd stats && bun install --frozen-lockfile
|
||||
|
||||
- name: Build texthooker-ui
|
||||
run: |
|
||||
cd vendor/texthooker-ui
|
||||
bun install --frozen-lockfile
|
||||
bun run build
|
||||
|
||||
- name: Build AppImage
|
||||
run: bun run build:appimage
|
||||
env:
|
||||
SUBMINER_TMDB_API_KEY: ${{ secrets.SUBMINER_TMDB_API_KEY }}
|
||||
|
||||
- name: Build unversioned AppImage
|
||||
run: |
|
||||
shopt -s nullglob
|
||||
appimages=(dist/release/SubMiner-*.AppImage)
|
||||
if [ "${#appimages[@]}" -eq 0 ]; then
|
||||
echo "No versioned AppImage found to create unversioned artifact."
|
||||
ls -la dist/release
|
||||
exit 1
|
||||
fi
|
||||
cp "${appimages[0]}" dist/release/SubMiner.AppImage
|
||||
|
||||
- name: Smoke packaged runtime assets
|
||||
shell: bash
|
||||
run: xvfb-run -a bun run test:package "dist/release/linux-unpacked/resources"
|
||||
|
||||
- name: Upload AppImage artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: appimage
|
||||
path: |
|
||||
dist/release/*.AppImage
|
||||
dist/release/latest*.yml
|
||||
dist/release/*.blockmap
|
||||
if-no-files-found: error
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.5
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
stats/node_modules
|
||||
vendor/texthooker-ui/node_modules
|
||||
vendor/subminer-yomitan/node_modules
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/texthooker-ui/bun.lock', 'vendor/subminer-yomitan/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ runner.arch }}-bun-
|
||||
|
||||
- name: Validate macOS signing/notarization secrets
|
||||
run: |
|
||||
missing=0
|
||||
for name in CSC_LINK CSC_KEY_PASSWORD APPLE_ID APPLE_APP_SPECIFIC_PASSWORD APPLE_TEAM_ID; do
|
||||
if [ -z "${!name}" ]; then
|
||||
echo "Missing required secret: $name"
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
if [ "$missing" -ne 0 ]; then
|
||||
echo "Set all required macOS signing/notarization secrets and rerun."
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
bun install --frozen-lockfile
|
||||
cd stats && bun install --frozen-lockfile
|
||||
|
||||
- name: Build texthooker-ui
|
||||
run: |
|
||||
cd vendor/texthooker-ui
|
||||
bun install --frozen-lockfile
|
||||
bun run build
|
||||
|
||||
- name: Build signed + notarized macOS artifacts
|
||||
run: bun run build:mac
|
||||
env:
|
||||
SUBMINER_TMDB_API_KEY: ${{ secrets.SUBMINER_TMDB_API_KEY }}
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
|
||||
- name: Smoke packaged runtime assets
|
||||
shell: bash
|
||||
run: bun run test:package "dist/release/mac-arm64/SubMiner.app/Contents/Resources"
|
||||
|
||||
- name: Upload macOS artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos
|
||||
path: |
|
||||
dist/release/*.dmg
|
||||
dist/release/*.zip
|
||||
dist/release/latest*.yml
|
||||
dist/release/*.blockmap
|
||||
if-no-files-found: error
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.5
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
stats/node_modules
|
||||
vendor/texthooker-ui/node_modules
|
||||
vendor/subminer-yomitan/node_modules
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/texthooker-ui/bun.lock', 'vendor/subminer-yomitan/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ runner.arch }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
bun install --frozen-lockfile
|
||||
cd stats && bun install --frozen-lockfile
|
||||
|
||||
- name: Build texthooker-ui
|
||||
shell: powershell
|
||||
run: |
|
||||
Set-Location vendor/texthooker-ui
|
||||
bun install --frozen-lockfile
|
||||
bun run build
|
||||
|
||||
- name: Verify managed Windows launcher
|
||||
run: bun test src/main/runtime/managed-launcher.test.ts
|
||||
|
||||
- name: Verify Windows launcher bootstrap
|
||||
run: bun test src/main/runtime/windows-launcher-bootstrap.test.ts
|
||||
|
||||
- name: Build unsigned Windows artifacts
|
||||
run: bun run build:win:unsigned
|
||||
env:
|
||||
SUBMINER_TMDB_API_KEY: ${{ secrets.SUBMINER_TMDB_API_KEY }}
|
||||
|
||||
- name: Smoke packaged runtime assets
|
||||
shell: bash
|
||||
run: bun run test:package "dist/release/win-unpacked/resources"
|
||||
|
||||
- name: Upload Windows artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows
|
||||
path: |
|
||||
dist/release/*.exe
|
||||
dist/release/*.zip
|
||||
dist/release/latest*.yml
|
||||
dist/release/*.blockmap
|
||||
if-no-files-found: error
|
||||
@@ -0,0 +1,188 @@
|
||||
name: Prerelease
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*-beta.*'
|
||||
- 'v*-rc.*'
|
||||
|
||||
concurrency:
|
||||
group: prerelease-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
quality-gate:
|
||||
permissions:
|
||||
contents: read
|
||||
uses: ./.github/workflows/quality-gate.yml
|
||||
|
||||
package:
|
||||
needs: [quality-gate]
|
||||
permissions:
|
||||
contents: read
|
||||
uses: ./.github/workflows/package-release.yml
|
||||
secrets:
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
SUBMINER_TMDB_API_KEY: ${{ secrets.SUBMINER_TMDB_API_KEY }}
|
||||
|
||||
release:
|
||||
needs: [package]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download AppImage
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: appimage
|
||||
path: dist/release
|
||||
|
||||
- name: Download macOS artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: macos
|
||||
path: dist/release
|
||||
|
||||
- name: Download Windows artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: windows
|
||||
path: dist/release
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.5
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ runner.arch }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Build launcher runtime artifacts
|
||||
run: make build-launcher
|
||||
|
||||
- name: Smoke launcher bundle
|
||||
run: bun dist/launcher/subminer.js --help >/dev/null
|
||||
|
||||
- name: Enforce generated launcher workflow
|
||||
run: bash scripts/verify-generated-launcher.sh
|
||||
|
||||
- name: Verify generated config examples
|
||||
run: bun run verify:config-example
|
||||
|
||||
- name: Package optional assets bundle
|
||||
run: |
|
||||
# Keep the archive layout stable for the AUR PKGBUILD.
|
||||
tar -czf "dist/release/subminer-assets.tar.gz" \
|
||||
--transform 's,^packaging/bun/licenses,resources/bun/licenses,' \
|
||||
config.example.jsonc \
|
||||
plugin/subminer \
|
||||
plugin/subminer.conf \
|
||||
assets/themes/subminer.rasi \
|
||||
assets/thumbnailers/subminer-ffmpegthumbnailer.thumbnailer \
|
||||
packaging/bun/licenses
|
||||
|
||||
- name: Package Bun corresponding source
|
||||
run: bun scripts/package-bun-source.mjs
|
||||
|
||||
- name: Generate checksums
|
||||
run: |
|
||||
shopt -s nullglob
|
||||
files=(dist/release/*.AppImage dist/release/*.dmg dist/release/*.exe dist/release/*.zip dist/release/*.tar.gz dist/release/latest*.yml dist/release/*.blockmap dist/launcher/subminer dist/launcher/subminer.cmd)
|
||||
if [ "${#files[@]}" -eq 0 ]; then
|
||||
echo "No release artifacts found for checksum generation."
|
||||
exit 1
|
||||
fi
|
||||
: > dist/release/SHA256SUMS.txt
|
||||
for file in "${files[@]}"; do
|
||||
printf '%s %s\n' \
|
||||
"$(sha256sum "$file" | awk '{print $1}')" \
|
||||
"${file##*/}" >> dist/release/SHA256SUMS.txt
|
||||
done
|
||||
|
||||
- name: Get version from tag
|
||||
id: version
|
||||
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Verify committed prerelease notes
|
||||
env:
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.VERSION }}
|
||||
run: |
|
||||
if [ ! -s packaging/release-notes/prerelease-notes.md ]; then
|
||||
echo "::error::packaging/release-notes/prerelease-notes.md is missing or empty. Run 'bun run changelog:prerelease-notes --version <version>' locally and commit the file before tagging."
|
||||
exit 1
|
||||
fi
|
||||
if ! bun run changelog:check-prerelease-notes --version "$RELEASE_VERSION"; then
|
||||
echo "::error::packaging/release-notes/prerelease-notes.md was not generated for $RELEASE_VERSION. Rerun 'bun run changelog:prerelease-notes --version $RELEASE_VERSION' locally, commit, and retag."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Publish Prerelease
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.VERSION }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
shopt -s nullglob
|
||||
artifacts=(
|
||||
dist/release/*.AppImage
|
||||
dist/release/*.dmg
|
||||
dist/release/*.exe
|
||||
dist/release/*.zip
|
||||
dist/release/*.tar.gz
|
||||
dist/release/*.tar.gz.sha256
|
||||
dist/release/latest*.yml
|
||||
dist/release/*.blockmap
|
||||
dist/release/SHA256SUMS.txt
|
||||
dist/launcher/subminer
|
||||
dist/launcher/subminer.cmd
|
||||
)
|
||||
|
||||
if [ "${#artifacts[@]}" -eq 0 ]; then
|
||||
echo "No release artifacts found for upload."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if gh release view "$RELEASE_VERSION" >/dev/null 2>&1; then
|
||||
gh release edit "$RELEASE_VERSION" \
|
||||
--draft \
|
||||
--prerelease \
|
||||
--title "$RELEASE_VERSION" \
|
||||
--notes-file packaging/release-notes/prerelease-notes.md
|
||||
else
|
||||
gh release create "$RELEASE_VERSION" \
|
||||
--draft \
|
||||
--latest=false \
|
||||
--prerelease \
|
||||
--title "$RELEASE_VERSION" \
|
||||
--notes-file packaging/release-notes/prerelease-notes.md
|
||||
fi
|
||||
|
||||
for asset in "${artifacts[@]}"; do
|
||||
gh release upload "$RELEASE_VERSION" "$asset" --clobber
|
||||
done
|
||||
|
||||
gh release edit "$RELEASE_VERSION" \
|
||||
--draft=false \
|
||||
--prerelease \
|
||||
--title "$RELEASE_VERSION" \
|
||||
--notes-file packaging/release-notes/prerelease-notes.md
|
||||
@@ -0,0 +1,145 @@
|
||||
name: Quality Gate
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
launcher-runtime:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.5
|
||||
|
||||
- name: Verify native runtime staging
|
||||
run: bun test src/main/runtime/managed-launcher.test.ts
|
||||
|
||||
- name: Verify Windows launcher bootstrap
|
||||
run: bun test src/main/runtime/windows-launcher-bootstrap.test.ts
|
||||
|
||||
- name: Verify native mpv process launch
|
||||
run: bun test src/main/runtime/mpv-process.test.ts
|
||||
|
||||
- name: Verify POSIX launcher bootstrap
|
||||
run: bun test src/main/runtime/posix-launcher-bootstrap.test.ts
|
||||
|
||||
quality-gate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.5
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
stats/node_modules
|
||||
vendor/subminer-yomitan/node_modules
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/subminer-yomitan/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ runner.arch }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
bun install --frozen-lockfile
|
||||
cd stats && bun install --frozen-lockfile
|
||||
|
||||
- name: Lint changelog fragments
|
||||
run: bun run changelog:lint
|
||||
|
||||
- name: Lint stats (formatting)
|
||||
run: bun run lint:stats
|
||||
|
||||
- name: Enforce pull request changelog fragments (`skip-changelog` label bypass)
|
||||
if: github.event_name == 'pull_request'
|
||||
env:
|
||||
BASE_REF: ${{ github.base_ref }}
|
||||
PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }}
|
||||
run: bun run changelog:pr-check --base-ref "origin/$BASE_REF" --head-ref "HEAD" --labels "$PR_LABELS"
|
||||
|
||||
- name: Build (TypeScript check)
|
||||
run: bun run typecheck
|
||||
|
||||
- name: Verify generated config examples
|
||||
run: bun run verify:config-example
|
||||
|
||||
- name: Install Lua
|
||||
run: |
|
||||
# Lua needs only Ubuntu sources; unrelated runner repositories can be unavailable.
|
||||
test -f /etc/apt/sources.list.d/ubuntu.sources
|
||||
apt_sources=(-o Dir::Etc::sourcelist=sources.list.d/ubuntu.sources -o Dir::Etc::sourceparts=-)
|
||||
sudo apt-get "${apt_sources[@]}" update
|
||||
sudo apt-get "${apt_sources[@]}" install -y lua5.4
|
||||
sudo ln -sf /usr/bin/lua5.4 /usr/local/bin/lua
|
||||
lua -v
|
||||
|
||||
- name: Launcher unit and script suites
|
||||
run: bun run test:launcher:unit:src && bun run test:scripts
|
||||
|
||||
- name: Environment suite
|
||||
run: bun run test:env
|
||||
|
||||
- name: Upload launcher smoke artifacts (on failure)
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: launcher-smoke
|
||||
path: .tmp/launcher-smoke/**
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Coverage suite (maintained source lane)
|
||||
run: bun run test:coverage:src
|
||||
|
||||
- name: Upload coverage artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-test-src
|
||||
path: coverage/test-src/lcov.info
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Stats UI tests
|
||||
run: bun run test:stats
|
||||
|
||||
- name: Build (bundle)
|
||||
run: bun run build
|
||||
|
||||
- name: Immersion SQLite verification
|
||||
run: bun run test:immersion:sqlite:dist
|
||||
|
||||
- name: Dist smoke suite
|
||||
run: bun run test:smoke:dist
|
||||
|
||||
- name: Security audit
|
||||
run: bun audit --audit-level high
|
||||
|
||||
- name: Build launcher runtime artifacts
|
||||
run: make build-launcher
|
||||
|
||||
- name: Smoke launcher bundle
|
||||
run: bun dist/launcher/subminer.js --help >/dev/null
|
||||
|
||||
- name: Enforce generated launcher workflow
|
||||
run: bash scripts/verify-generated-launcher.sh
|
||||
@@ -4,195 +4,37 @@ on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
- '!v*-beta.*'
|
||||
- '!v*-rc.*'
|
||||
|
||||
concurrency:
|
||||
group: release-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
quality-gate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
permissions:
|
||||
contents: read
|
||||
uses: ./.github/workflows/quality-gate.yml
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.5
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Test suite (source)
|
||||
run: bun run test:fast
|
||||
|
||||
- name: Launcher smoke suite (source)
|
||||
run: bun run test:launcher:smoke:src
|
||||
|
||||
- name: Upload launcher smoke artifacts (on failure)
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: launcher-smoke
|
||||
path: .tmp/launcher-smoke/**
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Build (bundle)
|
||||
run: bun run build
|
||||
|
||||
- name: Dist smoke suite
|
||||
run: bun run test:smoke:dist
|
||||
|
||||
build-linux:
|
||||
package:
|
||||
needs: [quality-gate]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.5
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
vendor/texthooker-ui/node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'vendor/texthooker-ui/package.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Build texthooker-ui
|
||||
run: |
|
||||
cd vendor/texthooker-ui
|
||||
bun install
|
||||
bun run build
|
||||
|
||||
- name: Build AppImage
|
||||
run: bun run build:appimage
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build unversioned AppImage
|
||||
run: |
|
||||
shopt -s nullglob
|
||||
appimages=(release/SubMiner-*.AppImage)
|
||||
if [ "${#appimages[@]}" -eq 0 ]; then
|
||||
echo "No versioned AppImage found to create unversioned artifact."
|
||||
ls -la release
|
||||
exit 1
|
||||
fi
|
||||
cp "${appimages[0]}" release/SubMiner.AppImage
|
||||
|
||||
- name: Upload AppImage artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: appimage
|
||||
path: release/*.AppImage
|
||||
|
||||
build-macos:
|
||||
needs: [quality-gate]
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.5
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
vendor/texthooker-ui/node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'vendor/texthooker-ui/package.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Validate macOS signing/notarization secrets
|
||||
run: |
|
||||
missing=0
|
||||
for name in CSC_LINK CSC_KEY_PASSWORD APPLE_ID APPLE_APP_SPECIFIC_PASSWORD APPLE_TEAM_ID; do
|
||||
if [ -z "${!name}" ]; then
|
||||
echo "Missing required secret: $name"
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
if [ "$missing" -ne 0 ]; then
|
||||
echo "Set all required macOS signing/notarization secrets and rerun."
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Build texthooker-ui
|
||||
run: |
|
||||
cd vendor/texthooker-ui
|
||||
bun install
|
||||
bun run build
|
||||
|
||||
- name: Build signed + notarized macOS artifacts
|
||||
run: bun run build:mac
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
|
||||
- name: Upload macOS artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos
|
||||
path: |
|
||||
release/*.dmg
|
||||
release/*.zip
|
||||
permissions:
|
||||
contents: read
|
||||
uses: ./.github/workflows/package-release.yml
|
||||
secrets:
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
SUBMINER_TMDB_API_KEY: ${{ secrets.SUBMINER_TMDB_API_KEY }}
|
||||
|
||||
release:
|
||||
needs: [build-linux, build-macos]
|
||||
needs: [package]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -203,13 +45,19 @@ jobs:
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: appimage
|
||||
path: release
|
||||
path: dist/release
|
||||
|
||||
- name: Download macOS artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: macos
|
||||
path: release
|
||||
path: dist/release
|
||||
|
||||
- name: Download Windows artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: windows
|
||||
path: dist/release
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
@@ -229,112 +77,103 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Build Bun subminer wrapper
|
||||
- name: Build launcher runtime artifacts
|
||||
run: make build-launcher
|
||||
|
||||
- name: Verify Bun subminer wrapper
|
||||
run: dist/launcher/subminer --help >/dev/null
|
||||
- name: Smoke launcher bundle
|
||||
run: bun dist/launcher/subminer.js --help >/dev/null
|
||||
|
||||
- name: Enforce generated launcher workflow
|
||||
run: bash scripts/verify-generated-launcher.sh
|
||||
|
||||
- name: Verify generated config examples
|
||||
run: bun run verify:config-example
|
||||
|
||||
- name: Package optional assets bundle
|
||||
run: |
|
||||
tar -czf "release/subminer-assets.tar.gz" \
|
||||
# Keep the archive layout stable for the AUR PKGBUILD.
|
||||
tar -czf "dist/release/subminer-assets.tar.gz" \
|
||||
--transform 's,^packaging/bun/licenses,resources/bun/licenses,' \
|
||||
config.example.jsonc \
|
||||
plugin/subminer \
|
||||
plugin/subminer.conf \
|
||||
assets/themes/subminer.rasi
|
||||
assets/themes/subminer.rasi \
|
||||
assets/thumbnailers/subminer-ffmpegthumbnailer.thumbnailer \
|
||||
packaging/bun/licenses
|
||||
|
||||
- name: Package Bun corresponding source
|
||||
run: bun scripts/package-bun-source.mjs
|
||||
|
||||
- name: Generate checksums
|
||||
run: |
|
||||
shopt -s nullglob
|
||||
files=(release/*.AppImage release/*.dmg release/*.zip release/*.tar.gz dist/launcher/subminer)
|
||||
files=(dist/release/*.AppImage dist/release/*.dmg dist/release/*.exe dist/release/*.zip dist/release/*.tar.gz dist/release/latest*.yml dist/release/*.blockmap dist/launcher/subminer dist/launcher/subminer.cmd)
|
||||
if [ "${#files[@]}" -eq 0 ]; then
|
||||
echo "No release artifacts found for checksum generation."
|
||||
exit 1
|
||||
fi
|
||||
sha256sum "${files[@]}" > release/SHA256SUMS.txt
|
||||
: > dist/release/SHA256SUMS.txt
|
||||
for file in "${files[@]}"; do
|
||||
printf '%s %s\n' \
|
||||
"$(sha256sum "$file" | awk '{print $1}')" \
|
||||
"${file##*/}" >> dist/release/SHA256SUMS.txt
|
||||
done
|
||||
|
||||
- name: Get version from tag
|
||||
id: version
|
||||
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Generate changelog
|
||||
id: changelog
|
||||
- name: Guard against pending changelog fragments
|
||||
env:
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.VERSION }}
|
||||
run: |
|
||||
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
|
||||
if [ -n "$PREV_TAG" ]; then
|
||||
CHANGES=$(git log --pretty=format:"- %s" ${PREV_TAG}..HEAD)
|
||||
else
|
||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||
if [ "$COMMIT_COUNT" -gt 10 ]; then
|
||||
CHANGES=$(git log --pretty=format:"- %s" HEAD~10..HEAD)
|
||||
else
|
||||
CHANGES=$(git log --pretty=format:"- %s")
|
||||
fi
|
||||
if find changes -maxdepth 1 -name '*.md' -not -name README.md -print -quit | grep -q .; then
|
||||
echo "::error::Pending changelog fragments detected. Run 'bun run changelog:build --version $RELEASE_VERSION' locally and commit the polished CHANGELOG.md before tagging. CI no longer auto-builds the changelog because the polish step requires the local 'claude' CLI."
|
||||
exit 1
|
||||
fi
|
||||
echo "CHANGES<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$CHANGES" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Verify changelog is ready for tagged release
|
||||
env:
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.VERSION }}
|
||||
run: bun run changelog:check --version "$RELEASE_VERSION"
|
||||
|
||||
- name: Generate release notes from changelog
|
||||
env:
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.VERSION }}
|
||||
run: bun run changelog:release-notes --version "$RELEASE_VERSION"
|
||||
|
||||
- name: Publish Release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.VERSION }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
cat > release-body.md <<'EOF'
|
||||
## Changes
|
||||
${{ steps.changelog.outputs.CHANGES }}
|
||||
|
||||
## Installation
|
||||
|
||||
### AppImage (Recommended)
|
||||
1. Download the AppImage below
|
||||
2. Make it executable: `chmod +x SubMiner.AppImage`
|
||||
3. Run: `./SubMiner.AppImage`
|
||||
|
||||
### macOS
|
||||
1. Download `subminer-*.dmg`
|
||||
2. Open the DMG and drag `SubMiner.app` into `/Applications`
|
||||
3. If needed, use the ZIP artifact as an alternative
|
||||
|
||||
### Manual Installation
|
||||
See the [README](https://github.com/${{ github.repository }}#installation) for manual installation instructions.
|
||||
|
||||
### Optional Assets (config example + mpv plugin + rofi theme)
|
||||
1. Download `subminer-assets.tar.gz`
|
||||
2. Extract and copy `config.example.jsonc` to `~/.config/SubMiner/config.jsonc`
|
||||
3. Copy `plugin/subminer/` directory contents to `~/.config/mpv/scripts/`
|
||||
4. Copy `plugin/subminer.conf` to `~/.config/mpv/script-opts/`
|
||||
5. Copy `assets/themes/subminer.rasi` to:
|
||||
- Linux: `~/.local/share/SubMiner/themes/subminer.rasi`
|
||||
- macOS: `~/Library/Application Support/SubMiner/themes/subminer.rasi`
|
||||
|
||||
Note: the `subminer` wrapper script uses Bun (`#!/usr/bin/env bun`), so `bun` must be installed and on `PATH`.
|
||||
EOF
|
||||
|
||||
if gh release view "${{ steps.version.outputs.VERSION }}" >/dev/null 2>&1; then
|
||||
gh release edit "${{ steps.version.outputs.VERSION }}" \
|
||||
--title "${{ steps.version.outputs.VERSION }}" \
|
||||
--notes-file release-body.md \
|
||||
--prerelease false
|
||||
if gh release view "$RELEASE_VERSION" >/dev/null 2>&1; then
|
||||
# Do not pass the prerelease flag here; gh defaults to a normal release.
|
||||
gh release edit "$RELEASE_VERSION" \
|
||||
--draft=false \
|
||||
--title "$RELEASE_VERSION" \
|
||||
--notes-file packaging/release-notes/release-notes.md
|
||||
else
|
||||
gh release create "${{ steps.version.outputs.VERSION }}" \
|
||||
--title "${{ steps.version.outputs.VERSION }}" \
|
||||
--notes-file release-body.md \
|
||||
--prerelease false
|
||||
gh release create "$RELEASE_VERSION" \
|
||||
--title "$RELEASE_VERSION" \
|
||||
--notes-file packaging/release-notes/release-notes.md
|
||||
fi
|
||||
|
||||
shopt -s nullglob
|
||||
artifacts=(
|
||||
release/*.AppImage
|
||||
release/*.dmg
|
||||
release/*.zip
|
||||
release/*.tar.gz
|
||||
release/SHA256SUMS.txt
|
||||
dist/release/*.AppImage
|
||||
dist/release/*.dmg
|
||||
dist/release/*.exe
|
||||
dist/release/*.zip
|
||||
dist/release/*.tar.gz
|
||||
dist/release/*.tar.gz.sha256
|
||||
dist/release/latest*.yml
|
||||
dist/release/*.blockmap
|
||||
dist/release/SHA256SUMS.txt
|
||||
dist/launcher/subminer
|
||||
dist/launcher/subminer.cmd
|
||||
)
|
||||
|
||||
if [ "${#artifacts[@]}" -eq 0 ]; then
|
||||
@@ -343,5 +182,145 @@ jobs:
|
||||
fi
|
||||
|
||||
for asset in "${artifacts[@]}"; do
|
||||
gh release upload "${{ steps.version.outputs.VERSION }}" "$asset" --clobber
|
||||
gh release upload "$RELEASE_VERSION" "$asset" --clobber
|
||||
done
|
||||
|
||||
aur-publish:
|
||||
needs: [release]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get version from tag
|
||||
id: version
|
||||
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Check AUR publish prerequisites
|
||||
id: aur_prereqs
|
||||
env:
|
||||
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "${AUR_SSH_PRIVATE_KEY}" ]; then
|
||||
echo "::warning::Missing AUR_SSH_PRIVATE_KEY; skipping automated AUR publish."
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Configure SSH for AUR
|
||||
id: aur_ssh
|
||||
if: steps.aur_prereqs.outputs.skip != 'true'
|
||||
env:
|
||||
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if install -dm700 ~/.ssh \
|
||||
&& printf '%s\n' "${AUR_SSH_PRIVATE_KEY}" > ~/.ssh/aur \
|
||||
&& chmod 600 ~/.ssh/aur \
|
||||
&& ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts \
|
||||
&& chmod 644 ~/.ssh/known_hosts; then
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "::warning::Unable to configure SSH for AUR; skipping automated AUR publish."
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Clone AUR repo
|
||||
id: aur_clone
|
||||
if: steps.aur_prereqs.outputs.skip != 'true' && steps.aur_ssh.outputs.skip != 'true'
|
||||
env:
|
||||
GIT_SSH_COMMAND: ssh -i ~/.ssh/aur -o IdentitiesOnly=yes
|
||||
run: |
|
||||
set -euo pipefail
|
||||
attempts=3
|
||||
for attempt in $(seq 1 "$attempts"); do
|
||||
if git clone ssh://aur@aur.archlinux.org/subminer-bin.git aur-subminer-bin; then
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
rm -rf aur-subminer-bin
|
||||
|
||||
if [ "$attempt" -lt "$attempts" ]; then
|
||||
sleep $((attempt * 15))
|
||||
fi
|
||||
done
|
||||
|
||||
echo "::warning::Unable to clone subminer-bin from AUR after ${attempts} attempts; skipping automated AUR publish."
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Download release assets for AUR
|
||||
id: aur_assets
|
||||
if: steps.aur_prereqs.outputs.skip != 'true' && steps.aur_ssh.outputs.skip != 'true' && steps.aur_clone.outputs.skip != 'true'
|
||||
env:
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.VERSION }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version="$RELEASE_VERSION"
|
||||
install -dm755 .tmp/aur-release-assets
|
||||
for asset in "SubMiner-${version#v}.AppImage" subminer subminer-assets.tar.gz; do
|
||||
destination=".tmp/aur-release-assets/$asset"
|
||||
if ! curl --fail --silent --show-error --location \
|
||||
--retry 3 --retry-delay 1 --retry-all-errors \
|
||||
--connect-timeout 30 --max-time 600 \
|
||||
--output "$destination.partial" \
|
||||
"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/download/$version/$asset"; then
|
||||
echo "::warning::Unable to download $asset after retries; skipping automated AUR publish."
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
mv "$destination.partial" "$destination"
|
||||
done
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Update AUR packaging metadata
|
||||
if: steps.aur_prereqs.outputs.skip != 'true' && steps.aur_ssh.outputs.skip != 'true' && steps.aur_clone.outputs.skip != 'true' && steps.aur_assets.outputs.skip != 'true'
|
||||
env:
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.VERSION }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version_no_v="$RELEASE_VERSION"
|
||||
version_no_v="${version_no_v#v}"
|
||||
cp packaging/aur/subminer-bin/PKGBUILD aur-subminer-bin/PKGBUILD
|
||||
cp packaging/aur/subminer-bin/.SRCINFO aur-subminer-bin/.SRCINFO
|
||||
bash scripts/update-aur-package.sh \
|
||||
--pkg-dir aur-subminer-bin \
|
||||
--version "$RELEASE_VERSION" \
|
||||
--appimage ".tmp/aur-release-assets/SubMiner-${version_no_v}.AppImage" \
|
||||
--wrapper ".tmp/aur-release-assets/subminer" \
|
||||
--assets ".tmp/aur-release-assets/subminer-assets.tar.gz"
|
||||
|
||||
- name: Commit and push AUR update
|
||||
if: steps.aur_prereqs.outputs.skip != 'true' && steps.aur_ssh.outputs.skip != 'true' && steps.aur_clone.outputs.skip != 'true' && steps.aur_assets.outputs.skip != 'true'
|
||||
working-directory: aur-subminer-bin
|
||||
env:
|
||||
GIT_SSH_COMMAND: ssh -i ~/.ssh/aur -o IdentitiesOnly=yes
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.VERSION }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if git diff --quiet -- PKGBUILD .SRCINFO; then
|
||||
echo "AUR packaging already up to date."
|
||||
exit 0
|
||||
fi
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git add PKGBUILD .SRCINFO
|
||||
git commit -m "Update to $RELEASE_VERSION"
|
||||
|
||||
attempts=3
|
||||
for attempt in $(seq 1 "$attempts"); do
|
||||
if git push origin HEAD:master; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$attempt" -lt "$attempts" ]; then
|
||||
sleep $((attempt * 15))
|
||||
fi
|
||||
done
|
||||
|
||||
echo "::warning::Unable to push the AUR update after ${attempts} attempts; GitHub release is published, but subminer-bin needs manual follow-up."
|
||||
@@ -1,13 +1,19 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
/package-lock.json
|
||||
|
||||
# Superpowers brainstorming
|
||||
.superpowers/
|
||||
|
||||
# Electron build output
|
||||
out/
|
||||
dist/
|
||||
release/
|
||||
coverage/
|
||||
|
||||
# Launcher build artifact (produced by make build-launcher)
|
||||
/subminer
|
||||
/main-entry.js
|
||||
/main-entry.js.map
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
@@ -21,9 +27,7 @@ Thumbs.db
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
**/CLAUDE.md
|
||||
environment.toml
|
||||
**/CLAUDE.md
|
||||
.env
|
||||
.vscode/*
|
||||
|
||||
@@ -32,7 +36,30 @@ environment.toml
|
||||
.vitepress/dist/
|
||||
docs/.vitepress/cache/
|
||||
docs/.vitepress/dist/
|
||||
# Local test media and scripts
|
||||
tests/*
|
||||
.worktrees/
|
||||
.tmp/
|
||||
.codex/*
|
||||
.agents/*
|
||||
!.agents/skills/
|
||||
.agents/skills/*
|
||||
!.agents/skills/subminer-change-verification/
|
||||
!.agents/skills/subminer-release/
|
||||
!.agents/skills/subminer-scrum-master/
|
||||
.agents/skills/subminer-change-verification/*
|
||||
!.agents/skills/subminer-change-verification/SKILL.md
|
||||
!.agents/skills/subminer-change-verification/scripts/
|
||||
.agents/skills/subminer-change-verification/scripts/*
|
||||
!.agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh
|
||||
!.agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh
|
||||
.agents/skills/subminer-release/*
|
||||
!.agents/skills/subminer-release/SKILL.md
|
||||
.agents/skills/subminer-scrum-master/*
|
||||
!.agents/skills/subminer-scrum-master/SKILL.md
|
||||
favicon.png
|
||||
.claude/*
|
||||
!stats/public/favicon.png
|
||||
|
||||
# Browser-automation session artifacts (page snapshots, console logs, downloads)
|
||||
.playwright-mcp/
|
||||
@@ -5,6 +5,10 @@
|
||||
[submodule "vendor/yomitan-jlpt-vocab"]
|
||||
path = vendor/yomitan-jlpt-vocab
|
||||
url = https://github.com/stephenmk/yomitan-jlpt-vocab
|
||||
[submodule "yomitan-jlpt-vocab"]
|
||||
path = vendor/yomitan-jlpt-vocab
|
||||
url = https://github.com/stephenmk/yomitan-jlpt-vocab
|
||||
[submodule "vendor/subminer-yomitan"]
|
||||
path = vendor/subminer-yomitan
|
||||
url = https://github.com/ksyasuda/subminer-yomitan
|
||||
[submodule "vendor/hachidori"]
|
||||
path = vendor/hachidori
|
||||
url = https://github.com/ksyasuda/hachidori.git
|
||||
branch = subminer
|
||||
@@ -1,6 +1,9 @@
|
||||
node_modules
|
||||
dist
|
||||
release
|
||||
coverage
|
||||
vendor
|
||||
*.log
|
||||
src/core/services/tokenizer/__fixtures__/golden/*.json
|
||||
# Reviewed release text and vendored license texts; never reformat
|
||||
packaging/release-notes
|
||||
packaging/bun/licenses
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
@@ -1,29 +1,85 @@
|
||||
<!-- BACKLOG.MD MCP GUIDELINES START -->
|
||||
# AGENTS.md
|
||||
|
||||
<CRITICAL_INSTRUCTION>
|
||||
## Internal Docs
|
||||
|
||||
## BACKLOG WORKFLOW INSTRUCTIONS
|
||||
Start here, then leave this file.
|
||||
|
||||
This project uses Backlog.md MCP for all task and project management activities.
|
||||
- Internal system of record: [`docs/README.md`](./docs/README.md)
|
||||
- Architecture map: [`docs/architecture/README.md`](./docs/architecture/README.md)
|
||||
- Workflow map: [`docs/workflow/README.md`](./docs/workflow/README.md)
|
||||
- Verification lanes: [`docs/workflow/verification.md`](./docs/workflow/verification.md)
|
||||
- Testing guidelines: [`docs/workflow/testing.md`](./docs/workflow/testing.md)
|
||||
- Knowledge-base rules: [`docs/knowledge-base/README.md`](./docs/knowledge-base/README.md)
|
||||
- Release guide: [`docs/RELEASING.md`](./docs/RELEASING.md)
|
||||
|
||||
**CRITICAL GUIDANCE**
|
||||
`docs-site/` is user-facing. Do not treat it as the canonical internal source of truth.
|
||||
|
||||
- If your client supports MCP resources, read `backlog://workflow/overview` to understand when and how to use Backlog for this project.
|
||||
- If your client only supports tools or the above request fails, call `backlog.get_workflow_overview()` tool to load the tool-oriented overview (it lists the matching guide tools).
|
||||
`CLAUDE.md` is a symlink to this file; there is one project instruction file, not two.
|
||||
|
||||
- **First time working here?** Read the overview resource IMMEDIATELY to learn the workflow
|
||||
- **Already familiar?** You should have the overview cached ("## Backlog.md Overview (MCP)")
|
||||
- **When to read it**: BEFORE creating tasks, or when you're unsure whether to track work
|
||||
## Quick Start
|
||||
|
||||
These guides cover:
|
||||
- Init workspace: `git submodule update --init --recursive`
|
||||
- Install deps: `make deps` or `bun install` plus `(cd vendor/texthooker-ui && bun install --frozen-lockfile)`
|
||||
- Fast dev loop: `make dev-watch`
|
||||
- Full local run: `bun run dev`
|
||||
- Verbose Electron debug: `electron . --start --dev --log-level debug`
|
||||
|
||||
- Decision framework for when to create tasks
|
||||
- Search-first workflow to avoid duplicates
|
||||
- Links to detailed guides for task creation, execution, and finalization
|
||||
- MCP tools reference
|
||||
## Build / Test
|
||||
|
||||
You MUST read the overview resource to understand the complete workflow. The information is NOT summarized here.
|
||||
- Runtime/package manager: Bun; use the version pinned by `package.json`.
|
||||
- Follow [`docs/workflow/verification.md`](./docs/workflow/verification.md) and start with the cheapest sufficient lane.
|
||||
- Full handoff gate for substantial changes:
|
||||
`bun run typecheck`
|
||||
`bun run test:fast`
|
||||
`bun run test:env`
|
||||
`bun run build`
|
||||
`bun run test:smoke:dist`
|
||||
- If `docs-site/` changed, also run:
|
||||
`bun run docs:test`
|
||||
`bun run docs:build`
|
||||
- Prefer `make pretty` and `bun run format:check:src`
|
||||
|
||||
</CRITICAL_INSTRUCTION>
|
||||
## Change-Specific Checks
|
||||
|
||||
<!-- BACKLOG.MD MCP GUIDELINES END -->
|
||||
- Config/schema/defaults: `bun run test:config`; if template/defaults changed, `bun run generate:config-example`
|
||||
- Launcher/plugin: `bun run test:launcher` or `bun run test:env`
|
||||
- Runtime-compat / dist-sensitive: `bun run test:runtime:compat`
|
||||
- Stats dashboard UI (`stats/`): `bun run test:stats`
|
||||
- Overlay / renderer / mpv / mining behavior in the running app: `bun run build`, then `bun run test:e2e` (headless, sandboxed; `bun run e2e start` for exploratory checks)
|
||||
- Build/release scripts (`scripts/**`): `bun run test:scripts`
|
||||
- Internal docs, `AGENTS.md`, or repo skills: `bun run test:docs:kb`
|
||||
- User-facing `docs-site/`: `bun run docs:test`, then `bun run docs:build`
|
||||
- macOS Swift helpers (`scripts/get-mpv-window-macos.swift`, `scripts/macos-controller-watch.swift`): compile them on macOS with `bun run build`
|
||||
- Test lanes are directory-discovered via `scripts/test-lanes.ts`; never hand-list test files in `package.json`
|
||||
|
||||
## Docs Upkeep
|
||||
|
||||
- Docs ship with the change, not after. If a change alters behavior, defaults, flags, shortcuts, ports, or APIs, update the matching docs in the same PR. Touching code without reconciling its docs is an incomplete change.
|
||||
- Source of truth for config defaults is the generated `config.example.jsonc`. Never write a default value into prose you didn't read from it, and don't restate the same default across multiple docs; cite/link to one place so there's a single thing to update.
|
||||
- Trigger map (touch left → update right):
|
||||
- `src/config/definitions/**` (schema/defaults/template) → `bun run generate:config-example`, then reconcile `docs-site/configuration.md` + any feature doc that cites that default
|
||||
- shortcuts/keybindings (`shortcuts.*`, `keybindings`, `stats.*Key`, `subtitleSidebar.toggleKey`, controller bindings) → `docs-site/shortcuts.md`
|
||||
- CLI flags/subcommands (`src/cli/args.ts`, `launcher/**`) → `docs-site/usage.md` + relevant integration doc
|
||||
- feature behavior (anki / jellyfin / jimaku / anilist / youtube / immersion / stats / websocket / sidebar / character-dictionary / annotations) → matching `docs-site/<feature>.md`
|
||||
- architecture / IPC / workflow / internal process → internal `docs/` (system of record)
|
||||
- feature set / requirements / install flow → `README.md`
|
||||
- Removing or renaming a config key: grep `docs-site/` and `docs/` for the old key and any value it documented; legacy/hidden keys (`LEGACY_HIDDEN_CONFIG_PATHS`) should not appear in user docs as current settings.
|
||||
- Verify after doc edits: `bun run verify:config-example` (if config touched), `bun run docs:test`, `bun run docs:build`.
|
||||
|
||||
## Sensitive Files
|
||||
|
||||
- Launcher source of truth: `launcher/*.ts`
|
||||
- Generated launcher artifact: `dist/launcher/subminer`; never hand-edit it
|
||||
- Repo-root `./subminer` is stale; do not revive it
|
||||
- `bun run build` rebuilds bundled Yomitan from `vendor/subminer-yomitan`
|
||||
- Do not change signing/packaging identifiers unless the task explicitly requires it
|
||||
|
||||
## Release / PR Notes
|
||||
|
||||
- User-visible PRs need reconciled current-outcome fragment(s) in `changes/*.md`. Format and rules live in [`changes/README.md`](./changes/README.md) (`type` + `area` keys required; inspect existing same-PR fragments, then update/remove stale bullets or add only genuinely separate outcomes; apply the `skip-changelog` label to opt out).
|
||||
- User-visible docs changes get a `type: docs` fragment
|
||||
- CI enforces `bun run changelog:lint` and `bun run changelog:pr-check`
|
||||
- PR review helpers:
|
||||
- `gh pr view --json number,title --jq '"PR #\\(.number): \\(.title)"'`
|
||||
- `gh api repos/:owner/:repo/pulls/<num>/comments --paginate`
|
||||
- For CI debugging, inspect runs with `gh run list/view`; rerun or fix only within the requested scope.
|
||||
@@ -1,10 +1,11 @@
|
||||
.PHONY: help deps build build-launcher install build-linux build-macos build-macos-unsigned clean install-linux install-macos install-plugin uninstall uninstall-linux uninstall-macos print-dirs pretty ensure-bun generate-config generate-example-config docs-dev docs docs-preview docs-watch dev-start dev-start-macos dev-watch dev-watch-macos dev-toggle dev-stop
|
||||
.PHONY: help submodules deps build build-launcher install build-linux build-macos build-macos-unsigned clean install-linux install-macos install-windows uninstall uninstall-linux uninstall-macos uninstall-windows print-dirs pretty lint ensure-bun generate-config generate-example-config dev-start dev-start-macos dev-watch dev-watch-macos dev-toggle dev-stop docs-test docs-build docs-build-versioned docs-dev
|
||||
|
||||
APP_NAME := subminer
|
||||
THEME_SOURCE := assets/themes/subminer.rasi
|
||||
THUMBNAILER_SOURCE := assets/thumbnailers/subminer-ffmpegthumbnailer.thumbnailer
|
||||
LAUNCHER_OUT := dist/launcher/$(APP_NAME)
|
||||
THEME_FILE := subminer.rasi
|
||||
PLUGIN_CONF := plugin/subminer.conf
|
||||
THUMBNAILER_FILE := subminer-ffmpegthumbnailer.thumbnailer
|
||||
|
||||
# Default install prefix for the wrapper script.
|
||||
PREFIX ?= $(HOME)/.local
|
||||
@@ -20,15 +21,10 @@ MACOS_DATA_DIR ?= $(HOME)/Library/Application Support/SubMiner
|
||||
MACOS_APP_DIR ?= $(HOME)/Applications
|
||||
MACOS_APP_DEST ?= $(MACOS_APP_DIR)/SubMiner.app
|
||||
|
||||
# mpv plugin install directories.
|
||||
MPV_CONFIG_DIR ?= $(HOME)/.config/mpv
|
||||
MPV_SCRIPTS_DIR ?= $(MPV_CONFIG_DIR)/scripts
|
||||
MPV_SCRIPT_OPTS_DIR ?= $(MPV_CONFIG_DIR)/script-opts
|
||||
|
||||
# If building from source, the AppImage will typically land in release/.
|
||||
APPIMAGE_SRC := $(firstword $(wildcard release/SubMiner-*.AppImage))
|
||||
MACOS_APP_SRC := $(firstword $(wildcard release/*.app release/*/*.app))
|
||||
MACOS_ZIP_SRC := $(firstword $(wildcard release/SubMiner-*.zip))
|
||||
# If building from source, the AppImage will typically land in dist/release/.
|
||||
APPIMAGE_SRC = $(firstword $(wildcard dist/release/SubMiner-*.AppImage))
|
||||
MACOS_APP_SRC = $(firstword $(wildcard dist/release/*.app dist/release/*/*.app))
|
||||
MACOS_ZIP_SRC = $(firstword $(wildcard dist/release/SubMiner-*.zip))
|
||||
|
||||
UNAME_S := $(shell uname -s 2>/dev/null || echo Unknown)
|
||||
ifeq ($(OS),Windows_NT)
|
||||
@@ -41,6 +37,17 @@ else
|
||||
PLATFORM := unknown
|
||||
endif
|
||||
|
||||
WINDOWS_APPDATA ?= $(if $(APPDATA),$(subst \,/,$(APPDATA)),$(HOME)/AppData/Roaming)
|
||||
|
||||
# mpv plugin install directories.
|
||||
ifeq ($(PLATFORM),windows)
|
||||
MPV_CONFIG_DIR ?= $(WINDOWS_APPDATA)/mpv
|
||||
else
|
||||
MPV_CONFIG_DIR ?= $(HOME)/.config/mpv
|
||||
endif
|
||||
MPV_SCRIPTS_DIR ?= $(MPV_CONFIG_DIR)/scripts
|
||||
MPV_SCRIPT_OPTS_DIR ?= $(MPV_CONFIG_DIR)/script-opts
|
||||
|
||||
help:
|
||||
@printf '%s\n' \
|
||||
"Targets:" \
|
||||
@@ -49,27 +56,30 @@ help:
|
||||
" build-linux Build Linux AppImage" \
|
||||
" build-macos Build macOS DMG/ZIP (signed if configured)" \
|
||||
" build-macos-unsigned Build macOS DMG/ZIP without signing/notarization" \
|
||||
" clean Remove build artifacts (dist/, release/, AppImage, binary)" \
|
||||
" clean Remove build artifacts (dist/, AppImage, binary)" \
|
||||
" dev-start Build and launch local Electron app" \
|
||||
" dev-start-macos Build and launch local Electron app with macOS tracker backend" \
|
||||
" dev-watch Start fast watch loop (tsc + renderer + Electron dev app)" \
|
||||
" dev-watch-macos Start watch loop with forced macOS tracker backend" \
|
||||
" dev-toggle Toggle overlay in a running local Electron app" \
|
||||
" dev-stop Stop a running local Electron app" \
|
||||
" docs-dev Run VitePress docs dev server" \
|
||||
" docs-watch Run VitePress docs dev + Backlog browser together" \
|
||||
" docs Build VitePress static docs" \
|
||||
" docs-preview Preview built VitePress docs" \
|
||||
" docs-test Run docs tests" \
|
||||
" docs-build Build the docs site" \
|
||||
" docs-build-versioned Build production versioned docs site" \
|
||||
" docs-dev Start the docs dev server" \
|
||||
" install-linux Install Linux wrapper/theme/app artifacts" \
|
||||
" install-macos Install macOS wrapper/theme/app artifacts" \
|
||||
" install-plugin Install mpv Lua plugin and plugin config" \
|
||||
" install-windows Print Windows packaging/install guidance" \
|
||||
" generate-config Generate ~/.config/SubMiner/config.jsonc from centralized defaults" \
|
||||
"" \
|
||||
"Other targets:" \
|
||||
" deps Install JS dependencies (root + texthooker-ui)" \
|
||||
" submodules Initialize/update git submodules" \
|
||||
" deps Initialize submodules and install JS dependencies (root + stats + texthooker-ui)" \
|
||||
" uninstall-linux Remove Linux install artifacts" \
|
||||
" uninstall-macos Remove macOS install artifacts" \
|
||||
" uninstall-windows Remove Windows mpv plugin artifacts" \
|
||||
" print-dirs Show resolved install locations" \
|
||||
" lint Lint stats (format check)" \
|
||||
"" \
|
||||
"Variables:" \
|
||||
" PREFIX=... Override wrapper install prefix (default: $$HOME/.local)" \
|
||||
@@ -78,7 +88,7 @@ help:
|
||||
" LINUX_DATA_DIR=... Override Linux app data dir" \
|
||||
" MACOS_DATA_DIR=... Override macOS app data dir" \
|
||||
" MACOS_APP_DIR=... Override macOS app install dir (default: $$HOME/Applications)" \
|
||||
" MPV_CONFIG_DIR=... Override mpv config dir (default: $$HOME/.config/mpv)"
|
||||
" MPV_CONFIG_DIR=... Override mpv config dir (default: $$HOME/.config/mpv or %APPDATA%/mpv on Windows)"
|
||||
|
||||
print-dirs:
|
||||
@printf '%s\n' \
|
||||
@@ -89,26 +99,38 @@ print-dirs:
|
||||
"MACOS_DATA_DIR=$(MACOS_DATA_DIR)" \
|
||||
"MACOS_APP_DIR=$(MACOS_APP_DIR)" \
|
||||
"MACOS_APP_DEST=$(MACOS_APP_DEST)" \
|
||||
"WINDOWS_APPDATA=$(WINDOWS_APPDATA)" \
|
||||
"MPV_CONFIG_DIR=$(MPV_CONFIG_DIR)" \
|
||||
"MPV_SCRIPTS_DIR=$(MPV_SCRIPTS_DIR)" \
|
||||
"MPV_SCRIPT_OPTS_DIR=$(MPV_SCRIPT_OPTS_DIR)" \
|
||||
"APPIMAGE_SRC=$(APPIMAGE_SRC)" \
|
||||
"MACOS_APP_SRC=$(MACOS_APP_SRC)" \
|
||||
"MACOS_ZIP_SRC=$(MACOS_ZIP_SRC)"
|
||||
|
||||
deps:
|
||||
@$(MAKE) --no-print-directory ensure-bun
|
||||
submodules:
|
||||
@git submodule update --init --recursive
|
||||
|
||||
deps: submodules ensure-bun
|
||||
@bun install
|
||||
@cd stats && bun install --frozen-lockfile
|
||||
@cd vendor/texthooker-ui && bun install --frozen-lockfile
|
||||
|
||||
ensure-bun:
|
||||
@command -v bun >/dev/null 2>&1 || { printf '%s\n' "[ERROR] bun not found"; exit 1; }
|
||||
|
||||
pretty: ensure-bun
|
||||
@bun run format
|
||||
@bun run format:src
|
||||
@bun run format:stats
|
||||
|
||||
lint: ensure-bun
|
||||
@bun run lint:stats
|
||||
|
||||
build:
|
||||
@printf '%s\n' "[INFO] Detected platform: $(PLATFORM)"
|
||||
@case "$(PLATFORM)" in \
|
||||
linux) $(MAKE) --no-print-directory build-linux ;; \
|
||||
macos) $(MAKE) --no-print-directory build-macos ;; \
|
||||
windows) printf '%s\n' "[INFO] Windows builds run via: bun run build:win" ;; \
|
||||
*) printf '%s\n' "[ERROR] Unsupported OS for this Makefile target: $(PLATFORM)"; exit 1 ;; \
|
||||
esac
|
||||
|
||||
@@ -117,6 +139,7 @@ install:
|
||||
@case "$(PLATFORM)" in \
|
||||
linux) $(MAKE) --no-print-directory install-linux ;; \
|
||||
macos) $(MAKE) --no-print-directory install-macos ;; \
|
||||
windows) $(MAKE) --no-print-directory install-windows ;; \
|
||||
*) printf '%s\n' "[ERROR] Unsupported OS for this Makefile target: $(PLATFORM)"; exit 1 ;; \
|
||||
esac
|
||||
|
||||
@@ -136,18 +159,12 @@ build-macos-unsigned: deps
|
||||
@bun run build:mac:unsigned
|
||||
|
||||
build-launcher:
|
||||
@printf '%s\n' "[INFO] Bundling launcher script"
|
||||
@install -d "$(dir $(LAUNCHER_OUT))"
|
||||
@bun build ./launcher/main.ts --target=bun --packages=bundle --outfile="$(LAUNCHER_OUT)"
|
||||
@if ! head -1 "$(LAUNCHER_OUT)" | grep -q '^#!/usr/bin/env bun'; then \
|
||||
{ printf '#!/usr/bin/env bun\n'; cat "$(LAUNCHER_OUT)"; } > "$(LAUNCHER_OUT).tmp" && mv "$(LAUNCHER_OUT).tmp" "$(LAUNCHER_OUT)"; \
|
||||
fi
|
||||
@chmod +x "$(LAUNCHER_OUT)"
|
||||
@printf '%s\n' "[INFO] Launcher artifact: $(LAUNCHER_OUT)"
|
||||
@printf '%s\n' "[INFO] Building launcher runtime artifacts"
|
||||
@bun run build:launcher
|
||||
|
||||
clean:
|
||||
@printf '%s\n' "[INFO] Removing build artifacts"
|
||||
@rm -rf dist release
|
||||
@rm -rf dist
|
||||
@rm -f "$(BINDIR)/subminer" "$(BINDIR)/SubMiner.AppImage"
|
||||
|
||||
generate-config: ensure-bun
|
||||
@@ -155,21 +172,8 @@ generate-config: ensure-bun
|
||||
@bun run electron . --generate-config
|
||||
|
||||
generate-example-config: ensure-bun
|
||||
@bun run build
|
||||
@bun run generate:config-example
|
||||
|
||||
docs-dev: ensure-bun
|
||||
@bun run docs:dev
|
||||
|
||||
docs-watch: ensure-bun
|
||||
@bun run docs:watch
|
||||
|
||||
docs: ensure-bun
|
||||
@bun run docs:build
|
||||
|
||||
docs-preview: ensure-bun
|
||||
@bun run docs:preview
|
||||
|
||||
dev-start: ensure-bun
|
||||
@bun run build
|
||||
@bun run electron . --start
|
||||
@@ -190,20 +194,36 @@ dev-toggle: ensure-bun
|
||||
dev-stop: ensure-bun
|
||||
@bun run electron . --stop
|
||||
|
||||
docs-test: ensure-bun
|
||||
@bun run docs:test
|
||||
|
||||
docs-build: ensure-bun
|
||||
@bun run docs:build
|
||||
|
||||
docs-build-versioned: ensure-bun
|
||||
@bun run docs:build:versioned
|
||||
|
||||
docs-dev: ensure-bun
|
||||
@bun run docs:dev
|
||||
|
||||
|
||||
install-linux: build-launcher
|
||||
@printf '%s\n' "[INFO] Installing Linux wrapper/theme artifacts"
|
||||
@printf '%s\n' "[INFO] Installing Linux wrapper/support artifacts"
|
||||
@install -d "$(BINDIR)"
|
||||
@install -m 0755 "$(LAUNCHER_OUT)" "$(BINDIR)/$(APP_NAME)"
|
||||
@install -d "$(LINUX_DATA_DIR)/themes"
|
||||
@install -m 0644 "./$(THEME_SOURCE)" "$(LINUX_DATA_DIR)/themes/$(THEME_FILE)"
|
||||
@install -d "$(LINUX_DATA_DIR)/thumbnailers"
|
||||
@install -m 0644 "./$(THUMBNAILER_SOURCE)" "$(LINUX_DATA_DIR)/thumbnailers/$(THUMBNAILER_FILE)"
|
||||
@install -d "$(LINUX_DATA_DIR)/plugin/subminer"
|
||||
@cp -R ./plugin/subminer/. "$(LINUX_DATA_DIR)/plugin/subminer/"
|
||||
@if [ -n "$(APPIMAGE_SRC)" ]; then \
|
||||
install -m 0755 "$(APPIMAGE_SRC)" "$(BINDIR)/SubMiner.AppImage"; \
|
||||
else \
|
||||
printf '%s\n' "[WARN] No release/SubMiner-*.AppImage found; skipping AppImage install"; \
|
||||
printf '%s\n' "[WARN] No dist/release/SubMiner-*.AppImage found; skipping AppImage install"; \
|
||||
printf '%s\n' " Build one with: make build"; \
|
||||
fi
|
||||
@printf '%s\n' "Installed to:" " $(BINDIR)/subminer" " $(LINUX_DATA_DIR)/themes/$(THEME_FILE)"
|
||||
@printf '%s\n' "Installed to:" " $(BINDIR)/subminer" " $(LINUX_DATA_DIR)/themes/$(THEME_FILE)" " $(LINUX_DATA_DIR)/thumbnailers/$(THUMBNAILER_FILE)"
|
||||
|
||||
install-macos: build-launcher
|
||||
@printf '%s\n' "[INFO] Installing macOS wrapper/theme/app artifacts"
|
||||
@@ -211,6 +231,8 @@ install-macos: build-launcher
|
||||
@install -m 0755 "$(LAUNCHER_OUT)" "$(BINDIR)/$(APP_NAME)"
|
||||
@install -d "$(MACOS_DATA_DIR)/themes"
|
||||
@install -m 0644 "./$(THEME_SOURCE)" "$(MACOS_DATA_DIR)/themes/$(THEME_FILE)"
|
||||
@install -d "$(MACOS_DATA_DIR)/plugin/subminer"
|
||||
@cp -R ./plugin/subminer/. "$(MACOS_DATA_DIR)/plugin/subminer/"
|
||||
@install -d "$(MACOS_APP_DIR)"
|
||||
@if [ -n "$(MACOS_APP_SRC)" ]; then \
|
||||
rm -rf "$(MACOS_APP_DEST)"; \
|
||||
@@ -221,31 +243,39 @@ install-macos: build-launcher
|
||||
ditto -x -k "$(MACOS_ZIP_SRC)" "$(MACOS_APP_DIR)"; \
|
||||
printf '%s\n' "[INFO] Installed app bundle from $(MACOS_ZIP_SRC)"; \
|
||||
else \
|
||||
printf '%s\n' "[WARN] No macOS app bundle or zip found in release/; skipping app install"; \
|
||||
printf '%s\n' "[WARN] No macOS app bundle or zip found in dist/release/; skipping app install"; \
|
||||
printf '%s\n' " Build one with: make build"; \
|
||||
fi
|
||||
@printf '%s\n' "Installed to:" " $(BINDIR)/subminer" " $(MACOS_DATA_DIR)/themes/$(THEME_FILE)" " $(MACOS_APP_DEST)"
|
||||
|
||||
install-plugin:
|
||||
@printf '%s\n' "[INFO] Installing mpv plugin artifacts"
|
||||
@install -d "$(MPV_SCRIPTS_DIR)"
|
||||
@rm -f "$(MPV_SCRIPTS_DIR)/subminer.lua"
|
||||
@install -d "$(MPV_SCRIPTS_DIR)/subminer"
|
||||
@install -d "$(MPV_SCRIPT_OPTS_DIR)"
|
||||
@cp -R ./plugin/subminer/. "$(MPV_SCRIPTS_DIR)/subminer/"
|
||||
@install -m 0644 "./$(PLUGIN_CONF)" "$(MPV_SCRIPT_OPTS_DIR)/subminer.conf"
|
||||
@printf '%s\n' "Installed to:" " $(MPV_SCRIPTS_DIR)/subminer/main.lua" " $(MPV_SCRIPTS_DIR)/subminer/" " $(MPV_SCRIPT_OPTS_DIR)/subminer.conf"
|
||||
install-windows:
|
||||
@printf '%s\n' "[INFO] Windows builds run via: bun run build:win"
|
||||
@printf '%s\n' "[INFO] SubMiner-managed mpv launches inject the bundled runtime plugin; no global mpv plugin install is needed."
|
||||
|
||||
# Uninstall behavior kept unchanged by default.
|
||||
uninstall: uninstall-linux
|
||||
uninstall:
|
||||
@printf '%s\n' "[INFO] Detected platform: $(PLATFORM)"
|
||||
@case "$(PLATFORM)" in \
|
||||
linux) $(MAKE) --no-print-directory uninstall-linux ;; \
|
||||
macos) $(MAKE) --no-print-directory uninstall-macos ;; \
|
||||
windows) $(MAKE) --no-print-directory uninstall-windows ;; \
|
||||
*) printf '%s\n' "[ERROR] Unsupported OS for this Makefile target: $(PLATFORM)"; exit 1 ;; \
|
||||
esac
|
||||
|
||||
uninstall-linux:
|
||||
@rm -f "$(BINDIR)/subminer" "$(BINDIR)/SubMiner.AppImage"
|
||||
@rm -f "$(LINUX_DATA_DIR)/themes/$(THEME_FILE)"
|
||||
@printf '%s\n' "Removed:" " $(BINDIR)/subminer" " $(BINDIR)/SubMiner.AppImage" " $(LINUX_DATA_DIR)/themes/$(THEME_FILE)"
|
||||
@rm -f "$(LINUX_DATA_DIR)/thumbnailers/$(THUMBNAILER_FILE)"
|
||||
@rm -rf "$(LINUX_DATA_DIR)/plugin/subminer"
|
||||
@printf '%s\n' "Removed:" " $(BINDIR)/subminer" " $(BINDIR)/SubMiner.AppImage" " $(LINUX_DATA_DIR)/themes/$(THEME_FILE)" " $(LINUX_DATA_DIR)/thumbnailers/$(THUMBNAILER_FILE)" " $(LINUX_DATA_DIR)/plugin/subminer"
|
||||
|
||||
uninstall-macos:
|
||||
@rm -f "$(BINDIR)/subminer"
|
||||
@rm -f "$(MACOS_DATA_DIR)/themes/$(THEME_FILE)"
|
||||
@rm -rf "$(MACOS_DATA_DIR)/plugin/subminer"
|
||||
@rm -rf "$(MACOS_APP_DEST)"
|
||||
@printf '%s\n' "Removed:" " $(BINDIR)/subminer" " $(MACOS_DATA_DIR)/themes/$(THEME_FILE)" " $(MACOS_APP_DEST)"
|
||||
@printf '%s\n' "Removed:" " $(BINDIR)/subminer" " $(MACOS_DATA_DIR)/themes/$(THEME_FILE)" " $(MACOS_DATA_DIR)/plugin/subminer" " $(MACOS_APP_DEST)"
|
||||
|
||||
uninstall-windows:
|
||||
@rm -rf "$(MPV_SCRIPTS_DIR)/subminer"
|
||||
@rm -f "$(MPV_SCRIPTS_DIR)/subminer.lua" "$(MPV_SCRIPTS_DIR)/subminer-loader.lua" "$(MPV_SCRIPT_OPTS_DIR)/subminer.conf"
|
||||
@printf '%s\n' "Removed:" " $(MPV_SCRIPTS_DIR)/subminer" " $(MPV_SCRIPT_OPTS_DIR)/subminer.conf"
|
||||
@@ -1,105 +1,282 @@
|
||||
<div align="center">
|
||||
<img src="assets/SubMiner.png" width="169" alt="SubMiner logo">
|
||||
<h1>SubMiner</h1>
|
||||
<strong>Look up words, mine to Anki, and enrich cards with context — without leaving mpv.</strong>
|
||||
<br /><br />
|
||||
|
||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
||||
[]()
|
||||
[](https://docs.subminer.moe)
|
||||
<img src="assets/SubMiner.png" width="160" alt="SubMiner logo">
|
||||
|
||||
# SubMiner
|
||||
|
||||
Look up words with Yomitan or Hachidori, mine them to Anki, and track your immersion without leaving mpv
|
||||
|
||||
[Installation](#quick-start) · [Requirements](#requirements) · [Usage](https://docs.subminer.moe/usage) · [Documentation](https://docs.subminer.moe)
|
||||
|
||||
[](https://github.com/ksyasuda/SubMiner/releases)
|
||||
[](https://github.com/ksyasuda/SubMiner/releases/latest)
|
||||
[](https://aur.archlinux.org/packages/subminer-bin)
|
||||
[](https://github.com/ksyasuda/SubMiner)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
||||
[](https://www.typescriptlang.org)
|
||||
|
||||
[](https://github.com/user-attachments/assets/7abab8a9-4e4e-4f06-9f3c-9783e15a3807)
|
||||
|
||||
</div>
|
||||
|
||||
<br />
|
||||
## Features
|
||||
|
||||
### Dictionary Lookups
|
||||
|
||||
Hover over any word in the subtitles to open the full Yomitan popup with definitions, pitch accent, and frequency data. SubMiner bundles its own Yomitan, separate from any browser install.
|
||||
|
||||
Yomitan remains the default. Select the bundled [Hachidori](https://github.com/bee-san/hachidori) backend with `dictionaryBackend: "hachidori"` and restart SubMiner. The tray opens the selected backend's settings. See [dictionary setup](https://docs.subminer.moe/usage#hachidori-setup) for importing dictionaries, linking an external Hachidori host, and configuring Anki.
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](./assets/minecard.mp4)
|
||||
|
||||
<img src="docs-site/public/screenshots/yomitan-lookup.png" width="800" alt="Yomitan dictionary popup over annotated subtitles in mpv">
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<br>
|
||||
|
||||
## What it does
|
||||
### Instant Anki Mining
|
||||
|
||||
SubMiner is an Electron overlay that sits on top of mpv. It turns your video player into a full sentence-mining workstation:
|
||||
Create an Anki card from the exact playback moment with one key press, click, or controller input. SubMiner fills in the sentence, an audio clip, and a screenshot or animated image.
|
||||
|
||||
- **Hover to look up** — Yomitan dictionary popups directly on subtitles
|
||||
- **Keyboard-driven lookup mode** — Navigate token-by-token, keep lookup open across tokens, and control popup scrolling/audio/mining without leaving the overlay
|
||||
- **One-key mining** — Creates Anki cards with sentence, audio, screenshot, and translation
|
||||
- **Instant auto-enrichment** — Optional local AnkiConnect proxy enriches new Yomitan cards immediately
|
||||
- **Reading annotations** — Combines N+1 targeting, frequency-dictionary highlighting, and JLPT underlining while you read
|
||||
- **Hover-aware playback** — By default, hovering subtitle text pauses mpv and resumes on mouse leave (`subtitleStyle.autoPauseVideoOnHover`)
|
||||
- **Subtitle tools** — Download from Jimaku, sync with alass/ffsubsync
|
||||
- **Immersion tracking** — SQLite-powered stats on your watch time and mining activity
|
||||
- **Custom texthooker page** — Built-in custom texthooker page and websocket, no extra setup
|
||||
- **Jellyfin integration** — Remote playback setup, cast device mode, and direct playback launch
|
||||
- **AniList progress** — Track episode completion and push watching progress automatically
|
||||
<div align="center">
|
||||
<img src="docs-site/public/screenshots/one-key-mining.png" width="800" alt="Yomitan lookup over an anime scene with Anki mining controls">
|
||||
</div>
|
||||
|
||||
## Quick start
|
||||
<br>
|
||||
|
||||
### 1. Install
|
||||
### Reading Annotations
|
||||
|
||||
**Linux (AppImage):**
|
||||
Subtitles are annotated as they play with frequency highlighting, JLPT tags, N+1 targeting, and character names from a generated dictionary. Particles and grammar-only tokens stay plain so the words worth learning stand out.
|
||||
|
||||
```bash
|
||||
wget https://github.com/ksyasuda/SubMiner/releases/latest/download/SubMiner.AppImage -O ~/.local/bin/SubMiner.AppImage
|
||||
chmod +x ~/.local/bin/SubMiner.AppImage
|
||||
wget https://github.com/ksyasuda/SubMiner/releases/latest/download/subminer -O ~/.local/bin/subminer
|
||||
chmod +x ~/.local/bin/subminer
|
||||
<div align="center">
|
||||
<img src="docs-site/public/screenshots/annotations.png" width="800" alt="Annotated subtitles with frequency colors, character portraits, and JLPT underlines">
|
||||
</div>
|
||||
|
||||
```
|
||||
<br>
|
||||
|
||||
> [!NOTE]
|
||||
> The `subminer` wrapper uses a [Bun](https://bun.sh) shebang. Make sure `bun` is on your `PATH`.
|
||||
### Immersion Dashboard
|
||||
|
||||
**From source** or **macOS** — see the [installation guide](https://docs.subminer.moe/installation#from-source).
|
||||
A stats dashboard tracks watch time, vocabulary growth, mining throughput, session history, and trends. Everything stays on your machine, with no third-party tracking.
|
||||
|
||||
### 2. Install the mpv plugin and configuration file
|
||||
<div align="center">
|
||||
<img src="docs-site/public/screenshots/stats-overview.png" width="800" alt="Stats dashboard showing watch time, cards mined, streaks, and tracking data">
|
||||
</div>
|
||||
|
||||
```bash
|
||||
wget https://github.com/ksyasuda/SubMiner/releases/latest/download/subminer-assets.tar.gz -O /tmp/subminer-assets.tar.gz
|
||||
tar -xzf /tmp/subminer-assets.tar.gz -C /tmp
|
||||
mkdir -p ~/.config/mpv/scripts/subminer
|
||||
mkdir -p ~/.config/mpv/script-opts
|
||||
cp -R /tmp/plugin/subminer/. ~/.config/mpv/scripts/subminer/
|
||||
cp /tmp/plugin/subminer.conf ~/.config/mpv/script-opts/
|
||||
mkdir -p ~/.config/SubMiner && cp /tmp/config.example.jsonc ~/.config/SubMiner/config.jsonc
|
||||
```
|
||||
<br>
|
||||
|
||||
### 3. Set up Yomitan Dictionaries
|
||||
### Integrations
|
||||
|
||||
```bash
|
||||
subminer app --yomitan
|
||||
```
|
||||
<table>
|
||||
<tr>
|
||||
<td><b>YouTube</b></td>
|
||||
<td>Play YouTube URLs with subtitle tracks picked by your language priorities, or choose tracks yourself in the overlay picker (<code>Ctrl+Alt+C</code>). Requires <code>yt-dlp</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>AniList</b></td>
|
||||
<td>Automatic episode tracking and progress sync</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Jellyfin</b></td>
|
||||
<td>Browse your Jellyfin library, or cast to SubMiner from any Jellyfin client. Setup and discovery live in the tray menu</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Jimaku</b></td>
|
||||
<td>Search and download Japanese subtitles. Requires a free Jimaku API key</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>TsukiHime</b></td>
|
||||
<td>Search and download subtitles extracted from anime releases, with Japanese and secondary-language tabs (<code>Ctrl+Shift+T</code>). Requires <code>xz</code> on your <code>PATH</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Subtitle generation</b></td>
|
||||
<td>Transcribe a video's audio into Japanese subtitles locally from the generation modal (<code>Ctrl+Shift+G</code>), the subtitle sidebar, or the launcher. SubMiner can download models for you; optional Silero speech detection helps focus on dialogue. Requires whisper.cpp and FFmpeg. <a href="https://docs.subminer.moe/subtitle-generation">Setup guide</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>AniSkip</b></td>
|
||||
<td>Automatic intro detection with chapter markers and a one-key skip (<code>TAB</code> by default)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>alass / ffsubsync</b></td>
|
||||
<td>Retime a subtitle against the audio or another subtitle track (<code>Ctrl+Alt+S</code>). Requires <code>alass</code> or <code>ffsubsync</code>; set <code>subsync.alass_path</code> or <code>subsync.ffsubsync_path</code> if they are not in <code>/usr/bin</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>WebSocket</b></td>
|
||||
<td>Plain subtitle feed plus a dedicated annotated feed for texthooker pages and custom tools</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Plugins</b></td>
|
||||
<td>Load your own JavaScript at startup to react to playback, run timers, add key-bound actions, and show results in an overlay panel. The bundled example explains the current line with Codex. <a href="https://docs.subminer.moe/plugins">Plugin guide</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
### 4. Mine
|
||||
<br>
|
||||
|
||||
```bash
|
||||
subminer app --start --background
|
||||
subminer video.mkv # default plugin config auto-starts visible overlay + resumes playback when ready
|
||||
subminer --start video.mkv # optional explicit overlay start when plugin auto_start=no
|
||||
```
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
| Required | Optional |
|
||||
| ------------------------------------------ | -------------------------------------------------- |
|
||||
| `bun` | |
|
||||
| `mpv` with IPC socket | `yt-dlp` |
|
||||
| `ffmpeg` | `guessit` (better AniSkip title/episode detection) |
|
||||
| `mecab` + `mecab-ipadic` | `fzf` / `rofi` |
|
||||
| Linux: `hyprctl` or `xdotool` + `xwininfo` | `chafa`, `ffmpegthumbnailer` |
|
||||
| macOS: Accessibility permission | |
|
||||
SubMiner runs on Linux, macOS 11+, and Windows 10+. Only **mpv** is required to run it (plus `fuse2` for the Linux AppImage). Mining cards also needs Anki with the [AnkiConnect](https://ankiweb.net/shared/info/2055492159) add-on. Everything else is optional.
|
||||
|
||||
| Dependency | Status | What it does |
|
||||
| ------------------------ | ---------------- | -------------------------------------------------------------------- |
|
||||
| mpv | Required | The video player SubMiner draws over |
|
||||
| fuse2 | Required (Linux) | Running the AppImage |
|
||||
| Anki + AnkiConnect | Required to mine | Card creation from the Yomitan popup |
|
||||
| ffmpeg | Recommended | Audio clips and screenshots on cards |
|
||||
| MeCab + mecab-ipadic | Recommended | More accurate N+1, JLPT, and frequency highlighting |
|
||||
| xdotool + xwininfo | Required (X11) | Window tracking on desktops other than Hyprland or Sway |
|
||||
| yt-dlp | Optional | YouTube playback |
|
||||
| xz | Optional | TsukiHime subtitle downloads (most Linux distros already have it) |
|
||||
| alass / ffsubsync | Optional | Subtitle sync |
|
||||
| whisper.cpp | Optional | [Subtitle generation](https://docs.subminer.moe/subtitle-generation) |
|
||||
| guessit | Optional | Better title, season, and episode detection for AniSkip and AniList |
|
||||
| fzf / rofi | Optional | Video picker in the `subminer` launcher (rofi is Linux only) |
|
||||
| chafa, ffmpegthumbnailer | Optional | Thumbnail previews in the launcher pickers |
|
||||
|
||||
<details>
|
||||
<summary><b>Platform-specific install commands</b></summary>
|
||||
|
||||
**Arch Linux:**
|
||||
|
||||
```bash
|
||||
sudo pacman -S --needed mpv ffmpeg
|
||||
paru -S --needed mecab-git mecab-ipadic # MeCab is only in the AUR
|
||||
```
|
||||
|
||||
On desktops other than Hyprland or Sway, also install `xdotool` and `xorg-xwininfo`.
|
||||
|
||||
**macOS:**
|
||||
|
||||
```bash
|
||||
brew install mpv ffmpeg mecab mecab-ipadic
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
|
||||
```powershell
|
||||
winget install shinchiro.mpv
|
||||
winget install Gyan.FFmpeg
|
||||
```
|
||||
|
||||
Then reopen your terminal and check `mpv --version` and `ffmpeg -version`. ffmpeg must be on `PATH`; mpv does not have to be. If `mpv` is not found, either add its folder (usually `%LOCALAPPDATA%\Programs\mpv`) to `PATH` or enter the full path to `mpv.exe` during first-run setup.
|
||||
|
||||
[Scoop](https://scoop.sh) is the alternative if you want one package manager for everything. It is the only one that also carries `xz`:
|
||||
|
||||
```powershell
|
||||
scoop bucket add extras
|
||||
scoop install extras/mpv main/ffmpeg main/yt-dlp main/xz
|
||||
```
|
||||
|
||||
See the [installation guide](https://docs.subminer.moe/installation#_1-install-requirements) for Ubuntu, Debian, and Fedora commands and the full optional package lists.
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Install SubMiner
|
||||
|
||||
<details>
|
||||
<summary><b>Arch Linux (AUR)</b></summary>
|
||||
|
||||
```bash
|
||||
paru -S subminer-bin
|
||||
```
|
||||
|
||||
Includes the AppImage and the `subminer` command.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Linux (AppImage)</b></summary>
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.local/bin
|
||||
wget https://github.com/ksyasuda/SubMiner/releases/latest/download/SubMiner.AppImage -O ~/.local/bin/SubMiner.AppImage \
|
||||
&& chmod +x ~/.local/bin/SubMiner.AppImage
|
||||
```
|
||||
|
||||
The AppImage is all you need. First-run setup can install the optional `subminer` command, which runs on a copy of Bun bundled with the app, so you do not need Bun installed.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>macOS (DMG)</b></summary>
|
||||
|
||||
Download the latest DMG from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest) and drag `SubMiner.app` into `/Applications`.
|
||||
|
||||
Then enable SubMiner under **System Settings > Privacy & Security > Accessibility** (named **Device Control and Data Access** on macOS 27) so the overlay can track the mpv window. Without it, SubMiner falls back to a basic window lookup that cannot tell when mpv is minimized. If macOS blocks the first launch, right-click the app and choose **Open**.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Windows</b></summary>
|
||||
|
||||
Download and run the latest installer (`SubMiner-<version>.exe`) from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest). A portable `.zip` is also available.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>From source</b></summary>
|
||||
|
||||
See the [build-from-source guide](https://docs.subminer.moe/installation#from-source).
|
||||
|
||||
</details>
|
||||
|
||||
### 2. Launch & Set Up
|
||||
|
||||
Start SubMiner and the setup window opens on first launch. It creates your config file, imports Yomitan dictionaries (you need at least one for lookups), and can install the `subminer` command. On Windows it also creates a **SubMiner mpv** shortcut.
|
||||
|
||||
```bash
|
||||
subminer app --setup # AUR
|
||||
~/.local/bin/SubMiner.AppImage --setup # AppImage
|
||||
```
|
||||
|
||||
On **macOS**, open `SubMiner.app` from `/Applications`. On **Windows**, run SubMiner from the Start menu. To reopen setup later, run `subminer app --setup`.
|
||||
|
||||
For card creation, keep Anki open with AnkiConnect installed. SubMiner connects to it at its default address with no extra setup.
|
||||
|
||||
### 3. Mine
|
||||
|
||||
```bash
|
||||
subminer video.mkv # play a video with SubMiner
|
||||
subminer /path/to/dir # pick a file with fzf
|
||||
subminer -R /path/to/dir # pick a file with rofi (Linux only)
|
||||
subminer -H # watch history: replay, next, or previous episode
|
||||
subminer doctor # check your setup
|
||||
```
|
||||
|
||||
On **Windows**, double-click the **SubMiner mpv** shortcut or drag a video file onto it.
|
||||
|
||||
Starting mpv some other way? See [Launching mpv yourself](https://docs.subminer.moe/installation#launching-mpv-yourself) for the IPC socket option the overlay needs.
|
||||
|
||||
## Documentation
|
||||
|
||||
For full guides on configuration, Anki, Jellyfin, and more, see [docs.subminer.moe](https://docs.subminer.moe).
|
||||
Full guides on configuration, Anki setup, Jellyfin, immersion tracking, and more: **[docs.subminer.moe](https://docs.subminer.moe)**
|
||||
|
||||
---
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
Built on the shoulders of [GameSentenceMiner](https://github.com/bpwhelan/GameSentenceMiner), [texthooker-ui](https://github.com/Renji-XD/texthooker-ui), [mpvacious](https://github.com/Ajatt-Tools/mpvacious), [Anacreon-Script](https://github.com/friedrich-de/Anacreon-Script), and [autosubsync-mpv](https://github.com/joaquintorres/autosubsync-mpv). Subtitles powered by [Jimaku.cc](https://jimaku.cc). Dictionary lookups via [Yomitan](https://github.com/yomidevs/yomitan).
|
||||
SubMiner builds on the work of these open-source projects:
|
||||
|
||||
| Project | Role |
|
||||
| ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
|
||||
| [ani-skip](https://github.com/synacktraa/ani-skip) | AniSkip API client for anime intro/outro skip timestamps |
|
||||
| [Anacreon-Script](https://github.com/friedrich-de/Anacreon-Script) | Inspiration for the mining workflow |
|
||||
| [asbplayer](https://github.com/killergerbah/asbplayer) | Inspiration for subtitle sidebar and logic for YouTube subtitle parsing |
|
||||
| [Bee's Character Dictionary](https://github.com/bee-san/Japanese_Character_Name_Dictionary) | Character name recognition in subtitles |
|
||||
| [Bun](https://github.com/oven-sh/bun) | Bundled runtime for the `subminer` command-line launcher |
|
||||
| [GameSentenceMiner](https://github.com/bpwhelan/GameSentenceMiner) | Inspiration for Electron overlay with Yomitan integration |
|
||||
| [jellyfin-mpv-shim](https://github.com/jellyfin/jellyfin-mpv-shim) | Jellyfin integration |
|
||||
| [Jimaku.cc](https://jimaku.cc) | Japanese subtitle search and downloads |
|
||||
| [Renji's Texthooker Page](https://github.com/Renji-XD/texthooker-ui) | Base for the WebSocket texthooker integration |
|
||||
| [Yomitan](https://github.com/yomidevs/yomitan) | Default dictionary engine and morphological parser |
|
||||
| [Hachidori](https://github.com/bee-san/hachidori) | Alternative dictionary backend, powered by HoshiDicts |
|
||||
| [yomitan-jlpt-vocab](https://github.com/stephenmk/yomitan-jlpt-vocab) | JLPT level tags for vocabulary |
|
||||
|
||||
## License
|
||||
|
||||
[GNU General Public License v3.0](LICENSE)
|
||||
SubMiner is released under the [GNU General Public License v3.0](LICENSE).
|
||||
|
||||
Release packages also bundle an unmodified copy of [Bun](https://github.com/oven-sh/bun), which is MIT licensed and statically links JavaScriptCore (LGPL 2.0) and TinyCC (LGPL 2.1). Its license texts and third-party notices ship inside the app under `resources/bun/licenses`, and each release publishes `bun-v1.3.5-source.tar.gz` with the corresponding source. See [Bundled Bun runtime](https://docs.subminer.moe/installation#bundled-bun-runtime).
|
||||
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 364 KiB |
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 433 B After Width: | Height: | Size: 253 B |
|
Before Width: | Height: | Size: 580 B After Width: | Height: | Size: 366 B |
|
Before Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 23 MiB |
|
Before Width: | Height: | Size: 303 KiB |
|
Before Width: | Height: | Size: 21 MiB After Width: | Height: | Size: 3.8 MiB |
@@ -0,0 +1,134 @@
|
||||
// Sends the current subtitle line, with a few lines of context, to `codex exec` and shows the
|
||||
// answer in the SubMiner plugin panel. Registers two actions: `explain` and `translate`.
|
||||
const { mkdtemp, readFile, rm } = require('node:fs/promises');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
|
||||
const DEFAULT_SETTINGS = {
|
||||
codexPath: 'codex',
|
||||
model: '',
|
||||
// Passed as -c model_reasoning_effort; low keeps answers to a few seconds. Empty uses your Codex config.
|
||||
reasoningEffort: 'low',
|
||||
before: 2,
|
||||
after: 1,
|
||||
timeoutMs: 120_000,
|
||||
pause: true,
|
||||
prompts: {},
|
||||
};
|
||||
|
||||
const ACTIONS = {
|
||||
explain: {
|
||||
title: 'Grammar',
|
||||
prompt:
|
||||
'You are a Japanese tutor. Explain the line marked >>> for an intermediate learner. ' +
|
||||
'Use the other lines only as context. Reply in English markdown: a natural translation, ' +
|
||||
'then a bullet list of the grammar points and notable vocabulary. Be concise.',
|
||||
},
|
||||
translate: {
|
||||
title: 'Translation',
|
||||
prompt:
|
||||
'Translate the line marked >>> into natural English. Use the other lines only as context. ' +
|
||||
'Reply with the translation, then one short note only if the line is ambiguous.',
|
||||
},
|
||||
};
|
||||
|
||||
function activate(ctx) {
|
||||
const settings = { ...DEFAULT_SETTINGS, ...ctx.settings };
|
||||
for (const [name, action] of Object.entries(ACTIONS)) {
|
||||
const prompt = settings.prompts?.[name] ?? action.prompt;
|
||||
ctx.actions.register(name, () => ask(ctx, settings, action.title, prompt));
|
||||
}
|
||||
}
|
||||
|
||||
function quote(text) {
|
||||
return text
|
||||
.split('\n')
|
||||
.map((line) => `> ${line}`)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
// Subtitles wrap across lines; flatten each cue so the >>> marker covers all of it.
|
||||
function oneLine(text) {
|
||||
return text
|
||||
.split('\n')
|
||||
.map((part) => part.trim())
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
function tail(text, lineCount) {
|
||||
return text.trim().split('\n').slice(-lineCount).join('\n');
|
||||
}
|
||||
|
||||
async function ask(ctx, settings, title, instructions) {
|
||||
const { before, current, after } = ctx.subtitles.context({
|
||||
before: settings.before,
|
||||
after: settings.after,
|
||||
});
|
||||
if (!current) {
|
||||
ctx.notify('No subtitle line to send.', { variant: 'warning' });
|
||||
return;
|
||||
}
|
||||
if (settings.pause) ctx.mpv.command(['set', 'pause', 'yes']);
|
||||
|
||||
const header = quote(current.text);
|
||||
const panel = ctx.ui.panel({ title, markdown: `${header}\n\n_Asking Codex..._` });
|
||||
const prompt = [
|
||||
instructions,
|
||||
'',
|
||||
'Subtitles:',
|
||||
...before.map((line) => oneLine(line.text)),
|
||||
`>>> ${oneLine(current.text)}`,
|
||||
...after.map((line) => oneLine(line.text)),
|
||||
].join('\n');
|
||||
|
||||
// Codex runs in an empty read-only directory so any command it tries cannot touch real files.
|
||||
const workDir = await mkdtemp(path.join(os.tmpdir(), 'subminer-ai-explain-'));
|
||||
const answerPath = path.join(workDir, 'answer.md');
|
||||
const args = [
|
||||
'exec',
|
||||
'--ephemeral',
|
||||
'--skip-git-repo-check',
|
||||
'--sandbox',
|
||||
'read-only',
|
||||
'--color',
|
||||
'never',
|
||||
'-C',
|
||||
workDir,
|
||||
'-o',
|
||||
answerPath,
|
||||
];
|
||||
if (settings.model) args.push('-m', settings.model);
|
||||
if (settings.reasoningEffort) {
|
||||
args.push('-c', `model_reasoning_effort=${JSON.stringify(settings.reasoningEffort)}`);
|
||||
}
|
||||
args.push('-');
|
||||
|
||||
try {
|
||||
const result = await ctx.exec(settings.codexPath, args, {
|
||||
stdin: prompt,
|
||||
timeoutMs: settings.timeoutMs,
|
||||
signal: panel.signal,
|
||||
});
|
||||
if (result.exitCode !== 0) {
|
||||
throw new Error(tail(result.stderr, 6) || `codex exited with code ${result.exitCode}`);
|
||||
}
|
||||
const answer = (await readFile(answerPath, 'utf8')).trim();
|
||||
panel.update({ markdown: `${header}\n\n${answer || '_Codex returned an empty answer._'}` });
|
||||
} catch (error) {
|
||||
// Closing the panel aborts the request; there is nobody left to show the error to.
|
||||
if (panel.signal.aborted) return;
|
||||
const message =
|
||||
error?.code === 'ENOENT'
|
||||
? `Cannot find \`${settings.codexPath}\`. Set \`plugins.ai-explain.settings.codexPath\` to its full path.`
|
||||
: String(error?.message ?? error);
|
||||
ctx.log.warn(`Codex request failed: ${message}`);
|
||||
panel.update({
|
||||
markdown: `${header}\n\n**Codex request failed.**\n\n\`\`\`\n${message}\n\`\`\``,
|
||||
});
|
||||
} finally {
|
||||
await rm(workDir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { activate };
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "AI Explain",
|
||||
"version": "1.0.0",
|
||||
"apiVersion": 1,
|
||||
"main": "main.js"
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Runs AnkiConnect's `sync` (AnkiWeb) after you mine cards. A burst of mining becomes one sync:
|
||||
// it waits until no card has been mined for `delayMs`, and syncs right away when playback ends.
|
||||
// Also registers a `sync` action for a manual sync.
|
||||
|
||||
const DEFAULT_SETTINGS = {
|
||||
delayMs: 30_000,
|
||||
syncOnEnd: true,
|
||||
timeoutMs: 60_000,
|
||||
notify: true,
|
||||
};
|
||||
|
||||
const END_REASONS = new Set(['eof', 'stop', 'quit']);
|
||||
|
||||
function activate(ctx) {
|
||||
const settings = { ...DEFAULT_SETTINGS, ...ctx.settings };
|
||||
// Cards mined since the last sync started.
|
||||
let dirty = false;
|
||||
let syncing = false;
|
||||
let cancelTimer = null;
|
||||
|
||||
function schedule() {
|
||||
cancelTimer?.();
|
||||
cancelTimer = ctx.after(settings.delayMs, flush);
|
||||
}
|
||||
|
||||
async function flush() {
|
||||
cancelTimer?.();
|
||||
cancelTimer = null;
|
||||
// A sync already running picks up new cards when it finishes.
|
||||
if (!dirty || syncing) return;
|
||||
dirty = false;
|
||||
syncing = true;
|
||||
try {
|
||||
await ctx.anki.invoke('sync', {}, { timeoutMs: settings.timeoutMs });
|
||||
ctx.log.info('Synced Anki.');
|
||||
if (settings.notify) ctx.notify('Anki synced');
|
||||
} catch (error) {
|
||||
ctx.log.warn(`Anki sync failed: ${error.message}`);
|
||||
ctx.notify(`Anki sync failed: ${error.message}`, { variant: 'error' });
|
||||
} finally {
|
||||
syncing = false;
|
||||
if (dirty) schedule();
|
||||
}
|
||||
}
|
||||
|
||||
ctx.anki.on('card-mined', () => {
|
||||
dirty = true;
|
||||
schedule();
|
||||
});
|
||||
|
||||
ctx.mpv.on('end-file', ({ reason }) => {
|
||||
if (settings.syncOnEnd && END_REASONS.has(reason)) return flush();
|
||||
});
|
||||
|
||||
ctx.actions.register('sync', () => {
|
||||
dirty = true;
|
||||
return flush();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { activate };
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "Anki Sync",
|
||||
"version": "1.0.0",
|
||||
"apiVersion": 1,
|
||||
"main": "main.js"
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Opens the Jimaku subtitle search when a file starts without a Japanese subtitle track.
|
||||
// It waits a few seconds after the file loads so external subtitles mpv or SubMiner add count.
|
||||
|
||||
const DEFAULT_SETTINGS = {
|
||||
// Built-in action to run, e.g. `openTsukihime` or `openSubtitleSelection`.
|
||||
action: 'openJimaku',
|
||||
// Track `lang` codes that count as Japanese. Track titles with "Japanese" or 日本語 count too.
|
||||
languages: ['ja', 'jp', 'jpn'],
|
||||
delayMs: 3_000,
|
||||
// Also check http(s) streams; Jimaku can only search by name, which streams rarely have.
|
||||
streams: false,
|
||||
};
|
||||
|
||||
const JAPANESE_TITLE = /japanese|日本語/i;
|
||||
|
||||
function activate(ctx) {
|
||||
const settings = { ...DEFAULT_SETTINGS, ...ctx.settings };
|
||||
const languages = new Set(settings.languages.map((lang) => lang.toLowerCase()));
|
||||
let cancelCheck = null;
|
||||
let playbackId = null;
|
||||
|
||||
function hasJapaneseSubtitle(trackList) {
|
||||
return trackList.some(
|
||||
(track) =>
|
||||
track?.type === 'sub' &&
|
||||
(languages.has(String(track.lang ?? '').toLowerCase()) ||
|
||||
JAPANESE_TITLE.test(String(track.title ?? ''))),
|
||||
);
|
||||
}
|
||||
|
||||
async function check(media) {
|
||||
cancelCheck = null;
|
||||
const trackList = await ctx.mpv.getProperty('track-list');
|
||||
if (ctx.media.current()?.playbackId !== media.playbackId || !Array.isArray(trackList)) return;
|
||||
if (hasJapaneseSubtitle(trackList)) return;
|
||||
ctx.log.info(`No Japanese subtitles for ${media.path}; running ${settings.action}.`);
|
||||
await ctx.actions.run(settings.action);
|
||||
}
|
||||
|
||||
ctx.media.on('change', (media) => {
|
||||
if (media?.playbackId === playbackId) return;
|
||||
playbackId = media?.playbackId ?? null;
|
||||
cancelCheck?.();
|
||||
cancelCheck = null;
|
||||
if (!media) return;
|
||||
if (!settings.streams && /^[a-z]+:\/\//i.test(media.path)) return;
|
||||
cancelCheck = ctx.after(settings.delayMs, () => check(media));
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { activate };
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "Auto Jimaku",
|
||||
"version": "1.0.0",
|
||||
"apiVersion": 1,
|
||||
"main": "main.js"
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// Tags each mined card with the show and episode it came from, e.g. `show::Frieren::S01E07`.
|
||||
// The show and episode are parsed from the file name, or from mpv's media title for streams.
|
||||
const path = require('node:path');
|
||||
|
||||
const DEFAULT_SETTINGS = {
|
||||
// Placeholders: {show}, {season}, {episode}, and {ep} (S01E07, or E07 without a season).
|
||||
// A tag stops before the first `::` level whose placeholder has no value.
|
||||
tags: ['show::{show}::{ep}'],
|
||||
notify: false,
|
||||
};
|
||||
|
||||
// Tried in order; the first match wins. Runs on names with brackets and dots already removed.
|
||||
const EPISODE_PATTERNS = [
|
||||
/^(?<show>.*?)\s*S(?<season>\d{1,2})\s?E(?<episode>\d{1,4})\b/i,
|
||||
/^(?<show>.*?)\s*(?:Season\s?(?<season>\d{1,2})\s*)?(?:Episode|Ep)\s?(?<episode>\d{1,4})\b/i,
|
||||
/^(?<show>.*?)\s+-\s+(?<episode>\d{1,4})(?:v\d)?\b/,
|
||||
/^(?<show>.*?)\s+(?<episode>\d{1,4})(?:v\d)?$/,
|
||||
/^(?<show>.*?)\s*第(?<episode>\d{1,4})話/,
|
||||
];
|
||||
|
||||
// `[Group] Show Name - 07 (1080p).mkv` -> { show: 'Show Name', season: null, episode: '07' }
|
||||
function parseMediaName(name) {
|
||||
const cleaned = name
|
||||
.replace(/\.[a-z0-9]{2,4}$/i, '')
|
||||
.replace(/\[[^\]]*\]|\([^)]*\)|【[^】]*】/g, ' ')
|
||||
.replace(/[._]/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
for (const pattern of EPISODE_PATTERNS) {
|
||||
const groups = cleaned.match(pattern)?.groups;
|
||||
const show = groups?.show.replace(/[\s-]+$/, '');
|
||||
if (!show) continue;
|
||||
return { show, season: pad(groups.season), episode: pad(groups.episode) };
|
||||
}
|
||||
return { show: cleaned || null, season: null, episode: null };
|
||||
}
|
||||
|
||||
function pad(number) {
|
||||
return number ? String(Number(number)).padStart(2, '0') : null;
|
||||
}
|
||||
|
||||
// Anki tags can't hold spaces, and a `::` in a value would add a hierarchy level.
|
||||
function tagValue(value) {
|
||||
return value.replace(/::?/g, ' ').trim().replace(/\s+/g, '_').replace(/"/g, '');
|
||||
}
|
||||
|
||||
function renderTag(template, values) {
|
||||
const levels = [];
|
||||
for (const level of template.split('::')) {
|
||||
let missing = false;
|
||||
const rendered = level.replace(/\{(\w+)\}/g, (_, key) => {
|
||||
const value = values[key];
|
||||
if (!value) missing = true;
|
||||
return value ? tagValue(value) : '';
|
||||
});
|
||||
if (missing) break;
|
||||
levels.push(rendered);
|
||||
}
|
||||
return levels.join('::');
|
||||
}
|
||||
|
||||
function mediaName(mediaPath, title) {
|
||||
// A stream URL's last segment is usually an id, so streams use the title mpv shows.
|
||||
if (typeof mediaPath === 'string' && mediaPath && !/^[a-z]+:\/\//i.test(mediaPath)) {
|
||||
return path.basename(mediaPath);
|
||||
}
|
||||
return typeof title === 'string' && title ? title : null;
|
||||
}
|
||||
|
||||
function renderTags(templates, name) {
|
||||
const { show, season, episode } = parseMediaName(name);
|
||||
const ep = episode ? `${season ? `S${season}` : ''}E${episode}` : null;
|
||||
const values = { show, season, episode, ep };
|
||||
return [...new Set(templates.map((template) => renderTag(template, values)))].filter(Boolean);
|
||||
}
|
||||
|
||||
function activate(ctx) {
|
||||
const settings = { ...DEFAULT_SETTINGS, ...ctx.settings };
|
||||
ctx.anki.on('card-mined', async ({ notes }) => {
|
||||
// Notes from the same file share one addTags call.
|
||||
const notesByName = new Map();
|
||||
for (const { noteId, provenance } of notes) {
|
||||
const name = mediaName(provenance?.media?.path, provenance?.media?.title);
|
||||
if (!name) {
|
||||
ctx.log.debug(`Note ${noteId} has no known media source; not tagging it.`);
|
||||
continue;
|
||||
}
|
||||
if (!notesByName.has(name)) notesByName.set(name, new Set());
|
||||
notesByName.get(name).add(noteId);
|
||||
}
|
||||
|
||||
for (const [name, notes] of notesByName) {
|
||||
const tags = renderTags(settings.tags, name);
|
||||
if (tags.length === 0) {
|
||||
ctx.log.debug(`No tags for "${name}".`);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
await ctx.anki.invoke('addTags', { notes: [...notes], tags: tags.join(' ') });
|
||||
ctx.log.info(`Tagged ${notes.size} note(s): ${tags.join(' ')}`);
|
||||
if (settings.notify) ctx.notify(`Tagged ${tags.join(' ')}`);
|
||||
} catch (error) {
|
||||
ctx.log.warn(`Tagging failed: ${error.message}`);
|
||||
ctx.notify(`Episode tagging failed: ${error.message}`, { variant: 'error' });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { activate };
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "Episode Tags",
|
||||
"version": "1.0.0",
|
||||
"apiVersion": 1,
|
||||
"main": "main.js"
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
[Thumbnailer Entry]
|
||||
TryExec=ffmpegthumbnailer
|
||||
Exec=ffmpegthumbnailer -i %i -o %o -s %s -f
|
||||
MimeType=video/matroska;video/matroska-3d;video/x-matroska;video/x-matroska-3d;
|
||||
@@ -1,16 +0,0 @@
|
||||
project_name: 'SubMiner'
|
||||
default_status: 'To Do'
|
||||
statuses: ['To Do', 'In Progress', 'Done']
|
||||
labels: []
|
||||
definition_of_done: []
|
||||
date_format: yyyy-mm-dd
|
||||
max_column_width: 20
|
||||
default_editor: 'nvim'
|
||||
auto_open_browser: false
|
||||
default_port: 6420
|
||||
remote_operations: true
|
||||
auto_commit: false
|
||||
bypass_git_hooks: false
|
||||
check_active_branches: true
|
||||
active_branch_days: 30
|
||||
task_prefix: 'task'
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
id: m-0
|
||||
title: 'Codebase Health Remediation'
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
Follow-up work from the March 6, 2026 codebase review: strengthen the runnable test gate, remove confirmed dead architecture, and continue decomposition of oversized runtime entrypoints.
|
||||
@@ -1,51 +0,0 @@
|
||||
---
|
||||
id: TASK-70
|
||||
title: >-
|
||||
Overlay runtime refactor: remove invisible mode and bind visible overlay to
|
||||
mpv subtitles
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-02-28 02:38'
|
||||
updated_date: '2026-02-28 22:36'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'commit:a14c9da'
|
||||
- 'commit:74554a3'
|
||||
- 'commit:75442a4'
|
||||
- 'commit:dde51f8'
|
||||
- 'commit:9e4e588'
|
||||
- src/main/overlay-runtime.ts
|
||||
- src/main/runtime/overlay-mpv-sub-visibility.ts
|
||||
- src/renderer/renderer.ts
|
||||
- docs/plans/2026-02-26-secondary-subtitles-main-overlay.md
|
||||
priority: medium
|
||||
ordinal: 1000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Scope: Branch-only commits main..HEAD on refactor-overlay (a14c9da through 9e4e588) rebuilt overlay behavior around visible overlay mode and removed legacy invisible overlay paths.
|
||||
|
||||
Delivered behavior:
|
||||
|
||||
- Removed renderer invisible overlay layout/offset helpers and main hover-highlight runtime code paths.
|
||||
- Added explicit overlay-to-mpv subtitle visibility synchronization so visible overlay state controls primary subtitle visibility consistently.
|
||||
- Hardened overlay runtime/bootstrap lifecycle around modal fallback open state and bridge send path edge cases.
|
||||
- Updated plugin/config/docs defaults to reflect visible-overlay-first behavior and subtitle binding controls.
|
||||
|
||||
Risk/impact context:
|
||||
|
||||
- Large cross-layer refactor touching runtime wiring, renderer event handling, and plugin behavior.
|
||||
- Regression coverage added/updated for overlay runtime, mpv protocol handling, renderer cleanup, and subtitle rendering paths.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Completed and validated in branch commit set before merge. Refactor reduces dead overlay modes, centralizes subtitle visibility behavior, and documents new defaults/constraints.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,58 +0,0 @@
|
||||
---
|
||||
id: TASK-71
|
||||
title: >-
|
||||
Anki integration: add local AnkiConnect proxy transport for push-based
|
||||
auto-enrichment
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-02-28 02:38'
|
||||
updated_date: '2026-03-04 13:55'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- src/anki-integration/anki-connect-proxy.ts
|
||||
- src/anki-integration/anki-connect-proxy.test.ts
|
||||
- src/anki-integration.ts
|
||||
- src/config/resolve/anki-connect.ts
|
||||
- src/core/services/tokenizer/yomitan-parser-runtime.ts
|
||||
- src/core/services/tokenizer/yomitan-parser-runtime.test.ts
|
||||
- docs/anki-integration.md
|
||||
- config.example.jsonc
|
||||
priority: medium
|
||||
ordinal: 2000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Scope: Current unmerged working-tree changes implement an optional local AnkiConnect-compatible proxy and transport switching for card enrichment.
|
||||
|
||||
Delivered behavior:
|
||||
|
||||
- Added proxy server that forwards AnkiConnect requests and enqueues addNote/addNotes note IDs for post-create enrichment, with de-duplication and loop-configuration protection.
|
||||
- Added follow-up response-shape compatibility handling so proxy enqueue works for both envelope (`{result,error}`) and bare JSON payloads, including `multi` variants.
|
||||
- Added config schema/defaults/resolution for ankiConnect.proxy (enabled, host, port, upstreamUrl) with validation warnings and fallback behavior.
|
||||
- Runtime now supports transport switching (polling vs proxy) and restarts transport when runtime config patches change transport keys.
|
||||
- Added Yomitan default-profile server sync helper to keep bundled parser profile aligned with configured Anki endpoint.
|
||||
- Updated user docs/config examples for proxy mode setup, troubleshooting, and mining workflow behavior.
|
||||
|
||||
Risk/impact context:
|
||||
|
||||
- New network surface on local host/port; correctness depends on safe proxy upstream configuration and robust response handling.
|
||||
- Tests added for proxy queue behavior, config resolution, and parser sync routines.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Completed implementation in branch working tree; ready to merge once local changes are committed and test gate passes.
|
||||
|
||||
Follow-up fix (2026-03-04):
|
||||
|
||||
- Updated bundled Yomitan server-sync behavior to target `profileCurrent` instead of hardcoded `profiles[0]`.
|
||||
- Added proxy-mode force override so bundled Yomitan always points at SubMiner proxy URL when `ankiConnect.proxy.enabled=true`; this ensures mined cards pass through proxy and trigger auto-enrichment.
|
||||
- Added regression tests for blocked existing-server case and force-override injection path.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,42 +0,0 @@
|
||||
---
|
||||
id: TASK-72
|
||||
title: 'macOS config validation UX: show full warning details in native dialog'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-02-28 02:38'
|
||||
updated_date: '2026-02-28 22:36'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'commit:cc2f9ef'
|
||||
- src/main/config-validation.ts
|
||||
- src/main/runtime/startup-config.ts
|
||||
- docs/configuration.md
|
||||
priority: low
|
||||
ordinal: 3000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Scope: Commit cc2f9ef improves startup config-warning visibility on macOS by ensuring full details are surfaced in the native UI path and reflected in docs.
|
||||
|
||||
Delivered behavior:
|
||||
|
||||
- Config validation/runtime wiring updated so macOS users can access complete warning details instead of truncated notification-only text.
|
||||
- Added/updated tests around config validation and startup config warning flows.
|
||||
- Updated configuration docs to clarify platform-specific warning presentation behavior.
|
||||
|
||||
Risk/impact context:
|
||||
|
||||
- Low runtime risk; primarily user-facing diagnostics clarity improvement.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Completed small follow-up fix to reduce config-debug friction on macOS.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,84 +0,0 @@
|
||||
---
|
||||
id: TASK-73
|
||||
title: 'MPV plugin: split into modules and optimize startup/command runtime'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-02-28 20:50'
|
||||
updated_date: '2026-02-28 22:36'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- plugin/subminer/main.lua
|
||||
- plugin/subminer/bootstrap.lua
|
||||
- plugin/subminer/process.lua
|
||||
- plugin/subminer/aniskip.lua
|
||||
- plugin/subminer/environment.lua
|
||||
- plugin/subminer/lifecycle.lua
|
||||
- plugin/subminer/messages.lua
|
||||
- plugin/subminer/ui.lua
|
||||
- plugin/subminer/hover.lua
|
||||
- plugin/subminer/options.lua
|
||||
- plugin/subminer/state.lua
|
||||
- plugin/subminer.conf
|
||||
- scripts/test-plugin-start-gate.lua
|
||||
- scripts/test-plugin-process-start-retries.lua
|
||||
- launcher/commands/playback-command.ts
|
||||
- launcher/mpv.ts
|
||||
- launcher/mpv.test.ts
|
||||
- launcher/smoke.e2e.test.ts
|
||||
- Makefile
|
||||
- package.json
|
||||
- docs/mpv-plugin.md
|
||||
- docs/installation.md
|
||||
- docs/architecture.md
|
||||
- README.md
|
||||
priority: medium
|
||||
ordinal: 4000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Scope: Replace monolithic `plugin/subminer.lua` with modular plugin runtime; optimize command execution paths; align install/docs/tests; fix launcher smoke instability.
|
||||
|
||||
Delivered behavior:
|
||||
|
||||
- Full plugin cutover to `plugin/subminer/main.lua` + module directory (no runtime compatibility shim with old monolith file).
|
||||
- Process/control command path moved toward async subprocess usage for non-start actions (`stop`, `toggle`, `settings`, restart stop leg), reducing synchronous blocking in mpv script runtime.
|
||||
- AniSkip path guarded: lookup runs only in SubMiner context (launcher metadata, explicit script-message refresh, or detected running app), instead of every opened file.
|
||||
- AniSkip lookup pipeline moved to async subprocess calls (no sync `ps`/`curl` on `file-loaded`) with deferred fetch after auto-start and session-level MAL/title/payload caching.
|
||||
- Startup/runtime loading updated with lazy module initialization via bootstrap proxies.
|
||||
- Plugin install flow updated to copy `plugin/subminer/` directory and remove legacy `~/.config/mpv/scripts/subminer.lua` file.
|
||||
- Added plugin gate script wiring to package scripts (`test:plugin:src`) and launcher test flow.
|
||||
- Smoke tests stabilized across sandbox environments where UNIX socket bind can return `EPERM` while preserving normal-path assertions.
|
||||
- Playback command cleanup race fixed when mpv exits before exit-listener registration.
|
||||
|
||||
Risk/impact context:
|
||||
|
||||
- mpv plugin loading path changed from single-file to module directory; packaging/install paths must stay consistent with release assets.
|
||||
- Async control/AniSkip path changes reduce blocking but can surface timing differences; regression checks added for cold start, file-load gating, and explicit refresh behavior.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
AniSkip gate/async update delivered in plugin runtime:
|
||||
|
||||
- `plugin/subminer/lifecycle.lua`: deferred AniSkip fetch and overlay-start trigger.
|
||||
- `plugin/subminer/aniskip.lua`: async lookup pipeline + context guard + session caches.
|
||||
- `plugin/subminer/environment.lua`: async app-running detection with short cache.
|
||||
- `plugin/subminer/messages.lua`: explicit script-message trigger wiring.
|
||||
|
||||
Regression coverage updated:
|
||||
|
||||
- `scripts/test-plugin-start-gate.lua` now verifies:
|
||||
- no sync `ps`/`curl` on non-context file load
|
||||
- no AniSkip network lookup on non-context file load
|
||||
- script-message refresh forces async AniSkip lookup
|
||||
|
||||
Validation run:
|
||||
|
||||
- `bun run test:plugin:src` pass.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,76 +0,0 @@
|
||||
---
|
||||
id: TASK-74
|
||||
title: 'Startup warmups: configurable warmup vs defer with low-power mode'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-02-27 21:05'
|
||||
updated_date: '2026-03-01 04:14'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- src/types.ts
|
||||
- src/config/definitions/defaults-core.ts
|
||||
- src/config/definitions/options-core.ts
|
||||
- src/config/definitions/template-sections.ts
|
||||
- src/config/resolve/core-domains.ts
|
||||
- src/main/runtime/startup-warmups.ts
|
||||
- src/main/runtime/startup-warmups-main-deps.ts
|
||||
- src/main/runtime/composers/mpv-runtime-composer.ts
|
||||
- src/core/services/startup.ts
|
||||
- src/main.ts
|
||||
- src/config/config.test.ts
|
||||
- src/main/runtime/startup-warmups.test.ts
|
||||
- src/main/runtime/startup-warmups-main-deps.test.ts
|
||||
- src/core/services/app-ready.test.ts
|
||||
priority: medium
|
||||
ordinal: 7000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Add startup warmup controls to allow per-integration warmup or deferred first-use loading.
|
||||
|
||||
Scope:
|
||||
|
||||
- New config section `startupWarmups` with toggles for `mecab`, `yomitanExtension`, `subtitleDictionaries`, and `jellyfinRemoteSession`.
|
||||
- New `startupWarmups.lowPowerMode` policy: defer everything except Yomitan extension.
|
||||
- Keep default behavior as full warmup.
|
||||
- Ensure deferred integrations lazy-load on first real usage path.
|
||||
- Add test coverage for config parsing/defaults and warmup scheduling behavior.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Implemented:
|
||||
|
||||
- Added `startupWarmups` to config types/defaults/options/template/resolve.
|
||||
- Warmup scheduler now uses per-integration gating functions.
|
||||
- Low-power mode now defers MeCab, subtitle dictionaries, and Jellyfin remote session warmups while still warming Yomitan extension.
|
||||
- Tokenization path guarantees lazy first-use init for deferred dependencies (Yomitan extension, MeCab when missing, subtitle dictionaries).
|
||||
- Added/updated tests across config and runtime warmup modules.
|
||||
|
||||
Validation:
|
||||
|
||||
- `bun run test:config:src`
|
||||
- `bun run test:core:src`
|
||||
- `tsc --noEmit`
|
||||
|
||||
Follow-up updates:
|
||||
|
||||
- Startup now triggers warmups earlier in app-ready flow (right after config validation/log-level setup) instead of waiting for initial args/overlay actions. Goal: tokenization warmup is already done or mostly done by first visible-subs toggle.
|
||||
- Tokenization warmup scheduling consolidated as `subtitle-tokenization` stage; when enabled by toggles, it runs Yomitan extension first, then MeCab/dictionary warmups.
|
||||
- Added per-stage debug logs for warmup progress and skip reasons:
|
||||
- `stage start/ready: yomitan-extension`
|
||||
- `stage start/ready: mecab`
|
||||
- `stage start/ready: subtitle-dictionaries`
|
||||
- `stage start/ready: jellyfin-remote-session`
|
||||
- `stage skipped: jellyfin-remote-session (disabled|auto-connect off)`
|
||||
- Added regression tests for stage-level logging and earlier startup ordering:
|
||||
- `src/main/runtime/startup-warmups.test.ts`
|
||||
- `src/main/runtime/startup-warmups-main-deps.test.ts`
|
||||
- `src/core/services/app-ready.test.ts`
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,41 +0,0 @@
|
||||
---
|
||||
id: TASK-75
|
||||
title: 'Tokenizer: configurable POS exclusions for N+1 and frequency annotations'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-01 01:23'
|
||||
updated_date: '2026-03-01 04:14'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: medium
|
||||
ordinal: 6000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
N+1 and frequency highlighting should ignore non-learning tokens (e.g., particles/auxiliary forms) based on MeCab POS1 tags, while remaining user-configurable.
|
||||
|
||||
Problem example: for subtitle phrase containing になれば, the highlighted N+1 target should not be the non-useful inflection/token piece when POS indicates an excluded class.
|
||||
|
||||
Implement configurable exclusion defaults with add/remove overrides so users can tune behavior without code changes.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 Default exclusion set omits non-useful POS1 classes from both N+1 candidate selection and frequency highlighting.
|
||||
- [x] #2 Users can add extra POS1 exclusions and remove defaults via config.
|
||||
- [x] #3 Tokenizer/annotation tests cover default behavior and config add/remove overrides.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Implemented configurable annotation POS exclusions with defaults+add/remove for both MeCab POS1 and POS2, wired to N+1 candidate selection and frequency highlighting. Added POS2 default exclusion (非自立), expanded POS1 defaults for function words, added Yomitan->MeCab enrichment to carry pos2/pos3 metadata, updated config docs/examples, and added regression tests including になれば case.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,39 +0,0 @@
|
||||
---
|
||||
id: TASK-76
|
||||
title: 'Tokenizer: remove POS exclusion config surface and keep hardcoded defaults'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-01 02:45'
|
||||
updated_date: '2026-03-01 04:14'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: medium
|
||||
ordinal: 5000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Remove user-facing config keys for annotation POS exclusions. Keep N+1/frequency POS exclusion behavior as built-in defaults with no config required.
|
||||
|
||||
Scope: remove config parsing/registry/docs/example for annotationFilters.pos1Exclusions/pos2Exclusions while preserving runtime filtering behavior.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 No user-facing config option exists for annotation POS exclusions.
|
||||
- [x] #2 Runtime N+1/frequency exclusion behavior remains active via built-in defaults.
|
||||
- [x] #3 Config/docs/example/tests updated accordingly.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Removed user-facing subtitleStyle.annotationFilters POS exclusion configuration (schema/resolver/options/docs/example). POS-based N+1/frequency filtering now always uses built-in defaults in runtime. Preserved robust exclusion behavior including merged-token overlap POS handling and N+1-only MeCab enrichment path.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,53 +0,0 @@
|
||||
---
|
||||
id: TASK-77
|
||||
title: 'Subtitle hover: auto-pause playback with config toggle'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-02-28 22:43'
|
||||
updated_date: '2026-03-04 12:07'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: medium
|
||||
ordinal: 8000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Add a user-facing subtitle config option to pause mpv playback when the cursor hovers subtitle text and resume playback when the cursor leaves.
|
||||
|
||||
Scope:
|
||||
|
||||
- New config key: `subtitleStyle.autoPauseVideoOnHover`.
|
||||
- Default should be enabled.
|
||||
- Hover pause/resume must not unpause if playback was already paused before hover.
|
||||
- Docs/examples/tests updated.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 `subtitleStyle.autoPauseVideoOnHover` exists and defaults to `true`.
|
||||
- [x] #2 Overlay pauses playback on subtitle hover and resumes on leave only when hover-triggered pause occurred.
|
||||
- [x] #3 Main/renderer IPC exposes pause-state query for safe hover behavior.
|
||||
- [x] #4 Config docs/examples and user docs/readme mention the new behavior and toggle.
|
||||
- [x] #5 Regression tests cover config parsing/validation and hover behavior edge cases.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Implemented `subtitleStyle.autoPauseVideoOnHover` with default `true`, wired through config defaults/resolution/types, renderer state/style, and mouse hover handlers. Added playback pause-state IPC (`getPlaybackPaused`) to avoid false resume when media was already paused. Added renderer hover behavior tests (including race/cancel case) and config/resolve tests. Updated config examples and docs (`README`, usage, shortcuts, mining workflow, configuration) to document default hover pause/resume behavior and disable path.
|
||||
|
||||
Follow-up adjustments (2026-03-04):
|
||||
|
||||
- Hover pause now resumes immediately when leaving subtitle text (no Yomitan-popup hover retention).
|
||||
- Added `subtitleStyle.autoPauseVideoOnYomitanPopup` (default `false`) to optionally keep playback paused while Yomitan popup is open, with auto-resume on close only when SubMiner initiated the popup pause.
|
||||
- Yomitan popup control keybinds added while popup is open: `J/K` scroll, `M` mine, `P` audio play, `[` previous audio variant, `]` next audio variant (within selected source).
|
||||
- Extension copy drift detection widened so popup runtime changes are reliably re-copied on launch (`popup.js`, `popup-main.js`, `display.js`, `display-audio.js`).
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,55 +0,0 @@
|
||||
---
|
||||
id: TASK-78
|
||||
title: 'Launcher + mpv plugin: auto-start visible overlay pause-until-ready and single-start guard'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-02-28 22:45'
|
||||
updated_date: '2026-02-28 22:45'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: medium
|
||||
ordinal: 9000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Add startup gating behavior for wrapper + mpv plugin flow so playback starts paused when visible overlay auto-start is enabled, then auto-resumes only after subtitle tokenization is ready.
|
||||
|
||||
Scope:
|
||||
|
||||
- Plugin option `auto_start_pause_until_ready` (default `yes`).
|
||||
- Launcher reads plugin runtime config and starts mpv paused when `auto_start=yes`, `auto_start_visible_overlay=yes`, and `auto_start_pause_until_ready=yes`.
|
||||
- Main process signals readiness via mpv script message after tokenized subtitle delivery.
|
||||
- Prevent duplicate auto-start attempts from showing `SubMiner already running` OSD.
|
||||
- Keep startup/loading OSD messaging visible and update docs/tests.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 Launcher reads `auto_start`, `auto_start_visible_overlay`, and `auto_start_pause_until_ready` from `subminer.conf` and starts mpv with `--pause=yes` when all are enabled.
|
||||
- [x] #2 Plugin pauses on eligible auto-start and resumes only on readiness signal or timeout fallback.
|
||||
- [x] #3 Main process emits `script-message subminer-autoplay-ready` after subtitle tokenization is ready.
|
||||
- [x] #4 Auto-start duplicate triggers are idempotent (no duplicate `--start` behavior and no spurious `Already running` OSD for auto-start path).
|
||||
- [x] #5 Docs and regression tests cover defaults, startup gating behavior, and duplicate-start suppression.
|
||||
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Implemented startup pause gate across launcher/plugin/main runtime:
|
||||
|
||||
- Added plugin runtime config parsing in launcher (`auto_start`, `auto_start_visible_overlay`, `auto_start_pause_until_ready`) and mpv start-paused behavior for eligible runs.
|
||||
- Added plugin auto-play gate state, timeout fallback, and readiness release via `subminer-autoplay-ready` script message.
|
||||
- Added main-process readiness signaling after tokenization delivery, including unpause fallback command path.
|
||||
- Split auto-start visibility control into separate control commands and added duplicate auto-start idempotency guard to suppress repeated auto-start `Already running` noise.
|
||||
- Updated plugin defaults to enabled (`auto_start=yes`, `auto_start_visible_overlay=yes`, `auto_start_pause_until_ready=yes`) and refreshed docs (`README`, usage, launcher, installation, plugin/config docs).
|
||||
- Added/updated regression coverage (`scripts/test-plugin-start-gate.lua`, launcher smoke/unit tests) validating paused startup, readiness resume, and duplicate-start suppression.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,50 +0,0 @@
|
||||
---
|
||||
id: TASK-79
|
||||
title: 'Jimaku modal: auto-close after successful subtitle load'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-01 13:52'
|
||||
updated_date: '2026-03-01 14:06'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: medium
|
||||
ordinal: 10000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Fix Jimaku modal UX so selecting a subtitle file closes the modal automatically once subtitle download+load succeeds.
|
||||
|
||||
Current behavior:
|
||||
|
||||
- Subtitle file downloads and loads into mpv.
|
||||
- Jimaku modal remains open until manual close.
|
||||
|
||||
Expected behavior:
|
||||
|
||||
- On successful `jimakuDownloadFile` result, close modal immediately.
|
||||
- Keep error behavior unchanged (stay open + show error).
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 Successful subtitle file selection/download in Jimaku closes modal automatically.
|
||||
- [x] #2 Existing error path keeps modal open and shows error.
|
||||
- [x] #3 Regression test covers success auto-close behavior.
|
||||
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Fixed renderer Jimaku success flow to close modal immediately after successful `jimakuDownloadFile` result. Added regression test (`src/renderer/modals/jimaku.test.ts`) that reproduces keyboard file-selection success path and asserts modal close state + `notifyOverlayModalClosed('jimaku')` emission. Kept failure path unchanged.
|
||||
|
||||
Also wired new test into `test:core:src` and `test:core:dist` package scripts.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,50 +0,0 @@
|
||||
---
|
||||
id: TASK-80
|
||||
title: 'Jimaku download: rename subtitle to current video basename'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-01 14:17'
|
||||
updated_date: '2026-03-01 14:19'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: medium
|
||||
ordinal: 11000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
When user selects a Jimaku subtitle, save subtitle with filename derived from currently playing media filename instead of Jimaku release filename.
|
||||
|
||||
Example:
|
||||
|
||||
- Current media: `anime.mkv`
|
||||
- Downloaded subtitle extension: `.srt`
|
||||
- Saved subtitle path: `anime.ja.srt`
|
||||
|
||||
Scope:
|
||||
|
||||
- Apply in Jimaku download IPC path before writing file.
|
||||
- Preserve collision-avoidance behavior (suffix with jimaku entry id/counter when target exists).
|
||||
- Keep mpv load flow unchanged except using renamed path.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 Jimaku subtitle destination name uses current media basename plus `.ja` and subtitle extension.
|
||||
- [x] #2 Existing duplicate filename conflict handling still works.
|
||||
- [x] #3 Regression tests cover renamed destination path behavior.
|
||||
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Jimaku download path generation now derives subtitle filename from currently playing media basename and keeps subtitle extension from Jimaku file (`anime.mkv` + `.srt` => `anime.ja.srt`). Added pure helper `buildJimakuSubtitleFilenameFromMediaPath` and routed IPC download flow through it before existing duplicate-path conflict handling. Added regression tests for local path, missing extension fallback, and remote URL media paths.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,58 +0,0 @@
|
||||
---
|
||||
id: TASK-81
|
||||
title: 'Tokenization performance: disable Yomitan MeCab parser, gate local MeCab init, and add persistent MeCab process'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-02 07:44'
|
||||
updated_date: '2026-03-02 20:44'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: high
|
||||
ordinal: 9001
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Reduce subtitle annotation latency by:
|
||||
|
||||
- disabling Yomitan-side MeCab parser requests (`useMecabParser=false`);
|
||||
- initializing local MeCab only when POS-dependent annotations are enabled (N+1 / JLPT / frequency);
|
||||
- replacing per-line local MeCab process spawning with a persistent parser process that auto-shuts down after idle time and restarts on demand.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 Yomitan parse requests disable MeCab parser path.
|
||||
- [x] #2 MeCab warmup/init is skipped when all POS-dependent annotation toggles are off.
|
||||
- [x] #3 Local MeCab tokenizer uses persistent process across subtitle lines.
|
||||
- [x] #4 Persistent MeCab process auto-shuts down after idle timeout and restarts on next tokenize activity.
|
||||
- [x] #5 Tests cover parser flag, warmup gating, and persistent MeCab lifecycle behavior.
|
||||
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Implemented tokenizer latency optimizations:
|
||||
|
||||
- switched Yomitan parse requests to `useMecabParser: false`;
|
||||
- added annotation-aware MeCab initialization gating in runtime warmup flow;
|
||||
- added persistent local MeCab process (default idle shutdown: 30s) with queued requests, retry-on-process-end, idle auto-shutdown, and automatic restart on new work;
|
||||
- added regression tests for Yomitan parse flag, MeCab warmup gating, and persistent/idle lifecycle behavior;
|
||||
- fixed tokenization warmup gate so first-use warmup completion is sticky (`tokenizationWarmupCompleted`) and sequential `tokenizeSubtitle` calls no longer re-run Yomitan/dictionary warmup path;
|
||||
- added regression coverage in `src/main/runtime/composers/mpv-runtime-composer.test.ts` for sequential tokenize calls (`warmup` side effects run once);
|
||||
- post-review critical fix: treat Yomitan default-profile Anki server sync `no-change` as successful check, so `lastSyncedYomitanAnkiServer` is cached and expensive sync checks do not repeat on every subtitle line;
|
||||
- added regression assertion in `src/core/services/tokenizer/yomitan-parser-runtime.test.ts` for `updated: false` path returning sync success;
|
||||
- post-review performance fix: refactored POS enrichment to pre-index MeCab tokens by surface plus character-position overlap index, replacing repeated active-candidate filtering/full-scan behavior with direct overlap candidate lookup per token;
|
||||
- added regression tests in `src/core/services/tokenizer/parser-enrichment-stage.test.ts` for repeated distant-token scan access and repeated active-candidate filter scans; both fail on scan-based behavior and pass with indexed lookup;
|
||||
- post-review startup fix: moved JLPT/frequency dictionary initialization from synchronous FS APIs to async `fs/promises` path inspection/read and cooperative chunked entry processing to reduce main-thread stall risk during cold start;
|
||||
- post-review first-line latency fix: decoupled tokenization warmup gating so first `tokenizeSubtitle` only waits on Yomitan extension readiness, while MeCab check + dictionary prewarm continue in parallel background warmups;
|
||||
- validated with targeted tests and `tsc --noEmit`.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,60 +0,0 @@
|
||||
---
|
||||
id: TASK-82
|
||||
title: 'Subtitle frequency highlighting: fix noisy Yomitan readings and restore known/N+1 color priority'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-02 20:10'
|
||||
updated_date: '2026-03-02 01:44'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: high
|
||||
ordinal: 9002
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Address frequency-highlighting regressions:
|
||||
|
||||
- tokens like `断じて` missed rank assignment when Yomitan merged-token reading was truncated/noisy;
|
||||
- known/N+1 tokens were incorrectly colored by frequency color instead of known/N+1 color.
|
||||
|
||||
Expected behavior:
|
||||
|
||||
- known/N+1 color always wins;
|
||||
- if token is frequent and within `topX`, frequency rank label can still appear on hover/metadata.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 Frequency lookup succeeds for noisy/truncated merged-token readings via robust fallback behavior.
|
||||
- [x] #2 Merged-token reading normalization restores missing kana suffixes where safe (`headword === surface` path).
|
||||
- [x] #3 Known/N+1 tokens keep known/N+1 color classes; frequency color class does not override them.
|
||||
- [x] #4 Frequency rank hover label remains available for in-range frequent tokens, including known/N+1.
|
||||
- [x] #5 Regression tests added for tokenizer and renderer behavior.
|
||||
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Implemented and validated:
|
||||
|
||||
- tokenizer now normalizes selected Yomitan merged-token readings by appending missing trailing kana suffixes when safe (`headword === surface`);
|
||||
- frequency lookup now does lazy fallback: requests `{term, reading}` first, and only requests `{term, reading: null}` for misses;
|
||||
- this removes eager `(term, null)` payload inflation on medium-frequency lines and reduces extension RPC payload/load;
|
||||
- renderer restored known/N+1 color priority over frequency class coloring;
|
||||
- frequency rank label display remains available for frequent known/N+1 tokens;
|
||||
- added regression tests covering noisy-reading fallback, lazy fallback-query behavior, and renderer class/label precedence.
|
||||
|
||||
Related commits:
|
||||
|
||||
- `17a417e` (`fix(subtitle): improve frequency highlight reliability`)
|
||||
- `79f37f3` (`fix(subtitle): prioritize known and n+1 colors over frequency`)
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,53 +0,0 @@
|
||||
---
|
||||
id: TASK-83
|
||||
title: 'Jellyfin subtitle delay: shift to adjacent cue without seek jumps'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-02 00:06'
|
||||
updated_date: '2026-03-02 00:06'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: high
|
||||
ordinal: 9003
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Add keybinding-friendly special commands that shift `sub-delay` to align current subtitle start with next/previous cue start, without `sub-seek` probing (avoid playback jump).
|
||||
|
||||
Scope:
|
||||
|
||||
- add special commands for next/previous line alignment;
|
||||
- compute delta from active subtitle cue timeline (external subtitle file/URL, including Jellyfin-delivered URLs);
|
||||
- apply `add sub-delay <delta>` and show OSD value;
|
||||
- keep existing proxy OSD behavior for direct `sub-delay` keybinding commands.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 New special commands exist for subtitle-delay shift to next/previous cue boundary.
|
||||
- [x] #2 Shift logic parses active external subtitle source timings (SRT/VTT/ASS) and computes delta from current `sub-start`.
|
||||
- [x] #3 Runtime applies delay shift without `sub-seek` and shows OSD feedback.
|
||||
- [x] #4 Direct `sub-delay` proxy commands also show OSD current value.
|
||||
- [x] #5 Tests added for cue parsing/shift behavior and IPC dispatch wiring.
|
||||
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Implemented no-jump subtitle-delay alignment commands:
|
||||
|
||||
- added `__sub-delay-next-line` and `__sub-delay-prev-line` special commands;
|
||||
- added `createShiftSubtitleDelayToAdjacentCueHandler` to parse cue start times from active external subtitle source and apply `add sub-delay` delta from current `sub-start`;
|
||||
- wired command handling through IPC runtime deps into main runtime;
|
||||
- retained/extended OSD proxy feedback for `sub-delay` keybindings;
|
||||
- updated configuration docs and added regression tests for subtitle-delay shift and IPC command routing.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,37 +0,0 @@
|
||||
---
|
||||
id: TASK-84
|
||||
title: 'Docs Plausible endpoint uses /api/event path'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-03 00:00'
|
||||
updated_date: '2026-03-03 00:00'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: medium
|
||||
ordinal: 12000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Fix VitePress docs Plausible tracker config to post to hosted worker API event endpoint instead of worker root URL.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 Docs theme Plausible `endpoint` points to `https://worker.subminer.moe/api/event`.
|
||||
- [x] #2 Plausible docs test asserts `/api/event` endpoint path.
|
||||
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Updated docs Plausible tracker endpoint to `https://worker.subminer.moe/api/event` and updated regression test expectation accordingly.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,70 +0,0 @@
|
||||
---
|
||||
id: TASK-84
|
||||
title: Migrate AniSkip metadata+lookup orchestration to launcher/Electron
|
||||
status: Done
|
||||
assignee:
|
||||
- Codex
|
||||
created_date: '2026-03-03 08:31'
|
||||
updated_date: '2026-03-03 08:35'
|
||||
labels:
|
||||
- enhancement
|
||||
- aniskip
|
||||
- launcher
|
||||
- mpv-plugin
|
||||
dependencies: []
|
||||
references:
|
||||
- launcher/aniskip-metadata.ts
|
||||
- launcher/mpv.ts
|
||||
- plugin/subminer/aniskip.lua
|
||||
- plugin/subminer/options.lua
|
||||
- plugin/subminer/state.lua
|
||||
- plugin/subminer/lifecycle.lua
|
||||
- plugin/subminer/messages.lua
|
||||
- plugin/subminer.conf
|
||||
- launcher/aniskip-metadata.test.ts
|
||||
documentation:
|
||||
- docs/mpv-plugin.md
|
||||
- launcher/aniskip-metadata.ts
|
||||
- plugin/subminer/aniskip.lua
|
||||
- docs/architecture.md
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Move AniSkip MAL/title-to-MAL lookup and intro payload resolution from mpv Lua to launcher Electron flow, while keeping mpv-side intro skip UX and chapter/chapter prompt behavior in plugin. Launcher should infer/analyze file metadata, fetch AniSkip payload when launching files, and pass resolved skip window via script options; plugin should trust launcher payload and fall back only when absent.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 Launcher infers AniSkip metadata for file targets using existing guessit/fallback logic and performs AniSkip MAL + payload resolution during mpv startup.
|
||||
- [x] #2 Launcher injects script options containing resolved MAL id and intro window fields (or explicit lookup-failure status) into mpv startup.
|
||||
- [x] #3 Lua plugin consumes launcher-provided AniSkip intro data and skips all network lookups when payload is present.
|
||||
- [x] #4 Standalone mpv/plugin usage without launcher payload continues to function using existing async in-plugin lookup path.
|
||||
- [x] #5 Docs and defaults are updated to document new script-option contract.
|
||||
- [x] #6 Launcher tests cover payload generation contract and fallback behavior where metadata is unavailable.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
|
||||
1. Add launcher-side AniSkip payload resolution helpers in launcher/aniskip-metadata.ts (MAL prefix lookup + AniSkip payload fetch + result normalization).
|
||||
2. Wire launcher/mpv.ts + buildSubminerScriptOpts to pass resolved AniSkip fields/mode in --script-opts for file playback.
|
||||
3. Update plugin/subminer/aniskip.lua plus options/state to consume injected payload: if intro_start/end present, apply immediately and skip network lookup; otherwise retain existing async behavior.
|
||||
4. Ensure fallback for standalone mpv usage remains intact for no-launcher/manual refresh.
|
||||
5. Add/update tests/docs/config references for new script-opt contract and edge cases.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Executed end-to-end migration so launcher resolves AniSkip title/MAL/payload before mpv start and injects it via --script-opts. Plugin now parses and consumes launcher payload (JSON/url/base64), applies OP intro from payload, tracks payload metadata in state, and keeps legacy async lookup path for non-launcher/absent payload playback. Added launcher config key aniskip_payload and updated launcher/aniskip-metadata tests for resolve/payload behavior and contract validation.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,38 +0,0 @@
|
||||
---
|
||||
id: TASK-85
|
||||
title: 'Remove docs Plausible analytics integration'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-03 00:00'
|
||||
updated_date: '2026-03-03 00:00'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: medium
|
||||
ordinal: 12001
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Remove Plausible analytics integration from docs theme and dependency graph. Keep docs build/runtime analytics-free.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 Docs theme no longer imports or initializes Plausible tracker.
|
||||
- [x] #2 `@plausible-analytics/tracker` removed from dependencies and lockfile.
|
||||
- [x] #3 Docs analytics test reflects absence of Plausible wiring.
|
||||
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Deleted Plausible runtime wiring from VitePress theme, removed tracker package via `bun remove`, and updated docs test to assert no Plausible integration remains.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,68 +0,0 @@
|
||||
---
|
||||
id: TASK-86
|
||||
title: 'Renderer: keyboard-driven Yomitan lookup mode and popup key forwarding'
|
||||
status: Done
|
||||
assignee:
|
||||
- Codex
|
||||
created_date: '2026-03-04 13:40'
|
||||
updated_date: '2026-03-05 11:30'
|
||||
labels:
|
||||
- enhancement
|
||||
- renderer
|
||||
- yomitan
|
||||
dependencies:
|
||||
- TASK-77
|
||||
references:
|
||||
- src/renderer/handlers/keyboard.ts
|
||||
- src/renderer/handlers/mouse.ts
|
||||
- src/renderer/renderer.ts
|
||||
- src/renderer/state.ts
|
||||
- src/renderer/yomitan-popup.ts
|
||||
- src/core/services/overlay-window.ts
|
||||
- src/preload.ts
|
||||
- src/shared/ipc/contracts.ts
|
||||
- src/types.ts
|
||||
- vendor/yomitan/js/app/frontend.js
|
||||
- vendor/yomitan/js/app/popup.js
|
||||
- vendor/yomitan/js/display/display.js
|
||||
- vendor/yomitan/js/display/popup-main.js
|
||||
- vendor/yomitan/js/display/display-audio.js
|
||||
documentation:
|
||||
- README.md
|
||||
- docs/usage.md
|
||||
- docs/shortcuts.md
|
||||
priority: medium
|
||||
ordinal: 13000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Add true keyboard-driven token lookup flow in overlay:
|
||||
|
||||
- Toggle keyboard token-selection mode and navigate tokens by keyboard (`Arrow` + `HJKL`).
|
||||
- Toggle Yomitan lookup window for selected token via fixed accelerator (`Ctrl/Cmd+Y`) without requiring mouse click.
|
||||
- Preserve keyboard-only workflow while popup is open by forwarding popup keys (`J/K`, `M`, `P`, `[`, `]`) and restoring overlay focus on popup close.
|
||||
- Ensure selection styling and hover metadata tooltips (frequency/JLPT) work for keyboard-selected token.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 Keyboard mode toggle exists and shows visual selection outline for active token.
|
||||
- [x] #2 Navigation works via arrows and vim keys while keyboard mode is enabled.
|
||||
- [x] #3 Lookup window toggles from selected token with `Ctrl/Cmd+Y`; close path restores overlay keyboard focus.
|
||||
- [x] #4 Popup-local controls work via keyboard forwarding (`J/K`, `M`, `P`, `[`, `]`), including mine action.
|
||||
- [x] #5 Frequency/JLPT hover tags render for keyboard-selected token.
|
||||
- [x] #6 Renderer/runtime tests cover new visibility/selection behavior, and docs are updated.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Implemented keyboard-driven Yomitan workflow end-to-end in renderer + bundled Yomitan runtime bridge. Added overlay-level keyboard mode state, token selection sync, lookup toggle routing, popup command forwarding, and focus recovery after popup close. Follow-up fixes kept lookup open while moving between tokens, made popup-local `J/K` and `ArrowUp/ArrowDown` scroll work from overlay-owned focus with key repeat, skipped keyboard/token annotation flow for parser groups that have no dictionary-backed headword, and preserved paused playback when token navigation jumps across subtitle lines. Updated user docs/README to document the final shortcut behavior.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,71 +0,0 @@
|
||||
---
|
||||
id: TASK-87
|
||||
title: >-
|
||||
Codebase health: harden verification and retire dead architecture identified
|
||||
in the March 2026 review
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2026-03-06 03:19'
|
||||
updated_date: '2026-03-06 03:20'
|
||||
labels:
|
||||
- tech-debt
|
||||
- tests
|
||||
- maintainability
|
||||
milestone: m-0
|
||||
dependencies: []
|
||||
references:
|
||||
- package.json
|
||||
- README.md
|
||||
- src/main.ts
|
||||
- src/anki-integration.ts
|
||||
- src/core/services/immersion-tracker-service.test.ts
|
||||
- src/translators/index.ts
|
||||
- src/subsync/engines.ts
|
||||
- src/subtitle/pipeline.ts
|
||||
documentation:
|
||||
- docs/reports/2026-02-22-task-100-dead-code-report.md
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Track the remediation work from the March 6, 2026 code review. The review found that the default test gate only exercises 53 of 241 test files, the dedicated subtitle test lane is a no-op, SQLite-backed immersion tracking tests are conditionally skipped in the standard Bun run, src/main.ts still contains a large dead-symbol backlog, several registry/pipeline modules appear unreferenced from live execution paths, and src/anki-integration.ts remains an oversized orchestration file. This parent task should coordinate a safe sequence: improve verification first, then remove dead code and continue decomposition with good test coverage in place.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [ ] #1 Child tasks are created for each remediation workstream with explicit dependencies and enough context for an isolated agent to execute them.
|
||||
- [ ] #2 The parent task records the recommended sequencing and parallelization strategy so replacement agents can resume without conversation history.
|
||||
- [ ] #3 Completion of the parent task leaves the repository with a materially more trustworthy test gate, less dead architecture, and clearer ownership boundaries for the main runtime and Anki integration surfaces.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
|
||||
Recommended sequencing:
|
||||
|
||||
1. Run TASK-87.1, TASK-87.2, TASK-87.3, and TASK-87.7 first. These are the safety-net and tooling tasks and can largely proceed in parallel.
|
||||
2. Start TASK-87.4 once TASK-87.1 lands so src/main.ts cleanup happens under a more trustworthy test matrix.
|
||||
3. Start TASK-87.5 after TASK-87.1 and TASK-87.2 so dead subsync/pipeline cleanup happens with stronger subtitle and runtime verification.
|
||||
4. Start TASK-87.6 after TASK-87.1 so Anki refactors happen with broader default coverage in place.
|
||||
5. Keep PRs focused: do not combine verification work with architectural cleanup unless a narrow dependency requires it.
|
||||
|
||||
Parallelization guidance:
|
||||
|
||||
- Wave 1 parallel: TASK-87.1, TASK-87.2, TASK-87.3, TASK-87.7
|
||||
- Wave 2 parallel: TASK-87.4, TASK-87.5, TASK-87.6
|
||||
|
||||
Shared review context to restate in child tasks:
|
||||
|
||||
- Standard test scripts currently reference only 53 unique test files out of 241 discovered test and type-test files under src/ and launcher/.
|
||||
- test:subtitle is currently a placeholder echo even though subtitle sync is a user-facing feature.
|
||||
- SQLite-backed immersion tracker tests are conditionally skipped in the standard Bun run.
|
||||
- src/main.ts trips many noUnusedLocals/noUnusedParameters diagnostics.
|
||||
- src/translators/index.ts, src/subsync/engines.ts, src/subtitle/pipeline.ts, src/tokenizers/index.ts, and src/token-mergers/index.ts appeared unreferenced during review and must be re-verified before deletion.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
@@ -1,53 +0,0 @@
|
||||
---
|
||||
id: TASK-87.1
|
||||
title: >-
|
||||
Testing workflow: make standard test commands reflect the maintained test
|
||||
surface
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2026-03-06 03:19'
|
||||
updated_date: '2026-03-06 03:21'
|
||||
labels:
|
||||
- tests
|
||||
- maintainability
|
||||
milestone: m-0
|
||||
dependencies: []
|
||||
references:
|
||||
- package.json
|
||||
- src/main-entry-runtime.test.ts
|
||||
- src/anki-integration/anki-connect-proxy.test.ts
|
||||
- src/main/runtime/jellyfin-remote-playback.test.ts
|
||||
- src/main/runtime/registry.test.ts
|
||||
documentation:
|
||||
- docs/reports/2026-02-22-task-100-dead-code-report.md
|
||||
parent_task_id: TASK-87
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
The current package scripts hand-enumerate a small subset of test files, which leaves the standard green signal misleading. A local audit found 241 test/type-test files under src/ and launcher/, but only 53 unique files referenced by the standard package.json test scripts. This task should redesign the runnable test matrix so maintained tests are either executed by the standard commands or intentionally excluded through a documented rule, instead of silently drifting out of coverage.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [ ] #1 The repository has a documented and reproducible test matrix for standard development commands, including which suites belong in the default lane versus slower or environment-specific lanes.
|
||||
- [ ] #2 The standard test entrypoints stop relying on a brittle hand-maintained allowlist for the currently covered unit and integration suites, or an explicit documented mechanism exists that prevents silent omission of new tests.
|
||||
- [ ] #3 Representative tests that were previously outside the standard lane from src/main/runtime, src/anki-integration, and entry/runtime surfaces are executed by an automated command and included in the documented matrix.
|
||||
- [ ] #4 Documentation for contributors explains which command to run for fast verification, full verification, and environment-specific verification.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
|
||||
1. Inventory the current test surface under src/ and launcher/ and compare it to package.json scripts to classify fast, full, slow, and environment-specific suites.
|
||||
2. Replace or reduce the brittle hand-maintained allowlist so new maintained tests do not silently miss the standard matrix.
|
||||
3. Update contributor docs with the intended fast/full/environment-specific commands.
|
||||
4. Verify the new matrix by running the relevant commands and by demonstrating at least one previously omitted runtime/Anki/entry test now belongs to an automated lane.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
@@ -1,53 +0,0 @@
|
||||
---
|
||||
id: TASK-87.2
|
||||
title: >-
|
||||
Subtitle sync verification: replace the no-op subtitle lane with real
|
||||
automated coverage
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2026-03-06 03:19'
|
||||
updated_date: '2026-03-06 03:21'
|
||||
labels:
|
||||
- tests
|
||||
- subsync
|
||||
milestone: m-0
|
||||
dependencies: []
|
||||
references:
|
||||
- package.json
|
||||
- README.md
|
||||
- src/core/services/subsync.ts
|
||||
- src/core/services/subsync.test.ts
|
||||
- src/subsync/utils.ts
|
||||
documentation:
|
||||
- docs/reports/2026-02-22-task-100-dead-code-report.md
|
||||
parent_task_id: TASK-87
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
SubMiner advertises subtitle syncing with alass and ffsubsync, but the dedicated test:subtitle command currently does not run any tests. There is already lower-level coverage in src/core/services/subsync.test.ts, but the test matrix and contributor-facing commands do not reflect that reality. This task should replace the no-op lane with real verification, align scripts with the existing subsync test surface, and make the user-facing docs honest about how subtitle sync is verified.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [ ] #1 The test:subtitle entrypoint runs real automated verification instead of echoing a placeholder message.
|
||||
- [ ] #2 The subtitle verification lane covers both alass and ffsubsync behavior, including at least one non-happy-path scenario relevant to current functionality.
|
||||
- [ ] #3 Contributor-facing documentation points to the real subtitle verification command and no longer implies a dedicated test lane exists when it does not.
|
||||
- [ ] #4 The resulting verification strategy integrates cleanly with the repository-wide test matrix without duplicating or hiding existing subsync coverage.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
|
||||
1. Audit the existing subtitle-sync test surface, especially src/core/services/subsync.test.ts, and decide whether test:subtitle should reuse or regroup that coverage.
|
||||
2. Replace the placeholder script with a real automated command and keep the matrix legible alongside TASK-87.1 work.
|
||||
3. Update README or related docs so the advertised subtitle verification path matches reality.
|
||||
4. Verify both alass and ffsubsync behavior remain covered by the resulting lane.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
@@ -1,52 +0,0 @@
|
||||
---
|
||||
id: TASK-87.3
|
||||
title: >-
|
||||
Immersion tracking verification: make SQLite-backed persistence tests visible
|
||||
and reproducible
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2026-03-06 03:19'
|
||||
updated_date: '2026-03-06 03:21'
|
||||
labels:
|
||||
- tests
|
||||
- immersion-tracking
|
||||
milestone: m-0
|
||||
dependencies: []
|
||||
references:
|
||||
- src/core/services/immersion-tracker-service.test.ts
|
||||
- src/core/services/immersion-tracker/storage-session.test.ts
|
||||
- src/core/services/immersion-tracker-service.ts
|
||||
- package.json
|
||||
documentation:
|
||||
- docs/reports/2026-02-22-task-100-dead-code-report.md
|
||||
parent_task_id: TASK-87
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
The immersion tracker is persistence-heavy, but its SQLite-backed tests are conditionally skipped in the standard Bun run when node:sqlite support is unavailable. That creates a blind spot around session finalization, telemetry persistence, and retention behavior. This task should establish a reliable automated verification path for the database-backed cases and make the prerequisite/runtime behavior explicit to contributors and CI.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [ ] #1 Database-backed immersion tracking tests run in at least one documented automated command that is practical for contributors or CI to execute.
|
||||
- [ ] #2 If the current runtime cannot execute the SQLite-backed tests, the repository exposes that limitation clearly instead of silently reporting a misleading green result.
|
||||
- [ ] #3 Contributor-facing documentation explains how to run the immersion tracker verification lane and any environment prerequisites it depends on.
|
||||
- [ ] #4 The resulting verification covers session persistence or finalization behavior that is not exercised by the pure seam tests alone.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
|
||||
1. Confirm which SQLite-backed immersion tests are currently skipped and why in the standard Bun environment.
|
||||
2. Establish a reproducible command or lane for the DB-backed cases, or make the unsupported-runtime limitation explicit and actionable.
|
||||
3. Document prerequisites and expected behavior for contributors and CI.
|
||||
4. Verify at least one persistence/finalization path beyond the seam tests is exercised by the new lane.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
@@ -1,53 +0,0 @@
|
||||
---
|
||||
id: TASK-87.4
|
||||
title: >-
|
||||
Runtime composition root: remove dead symbols and tighten module boundaries in
|
||||
src/main.ts
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2026-03-06 03:19'
|
||||
updated_date: '2026-03-06 03:21'
|
||||
labels:
|
||||
- tech-debt
|
||||
- runtime
|
||||
- maintainability
|
||||
milestone: m-0
|
||||
dependencies:
|
||||
- TASK-87.1
|
||||
references:
|
||||
- src/main.ts
|
||||
- src/main/runtime
|
||||
- package.json
|
||||
documentation:
|
||||
- docs/reports/2026-02-22-task-100-dead-code-report.md
|
||||
parent_task_id: TASK-87
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
A noUnusedLocals/noUnusedParameters compile pass reports a large concentration of dead imports and dead locals in src/main.ts. The file is also far beyond the repo’s preferred size guideline, which makes the runtime composition root difficult to review and easy to break. This task should remove confirmed dead symbols, continue extracting coherent slices where that improves readability, and leave the entrypoint materially easier to understand without changing behavior.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [ ] #1 src/main.ts no longer emits dead-symbol diagnostics under a noUnusedLocals/noUnusedParameters compile pass for the areas touched by this cleanup.
|
||||
- [ ] #2 Unused imports, destructured values, and stale locals identified in the current composition root are removed or relocated without behavior changes.
|
||||
- [ ] #3 The resulting composition root has clearer ownership boundaries for at least one runtime slice that is currently buried in the monolith.
|
||||
- [ ] #4 Relevant runtime and startup verification commands pass after the cleanup, and any command changes are documented if needed.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
|
||||
1. Re-run the noUnusedLocals/noUnusedParameters compile pass and capture the src/main.ts diagnostics cluster before editing.
|
||||
2. Remove dead imports, destructured values, and stale locals in small reviewable slices; extract a coherent helper/module only where that materially reduces coupling.
|
||||
3. Keep changes behavior-preserving and avoid mixing unrelated cleanup outside src/main.ts unless required to compile.
|
||||
4. Verify with the updated runtime/startup test commands from TASK-87.1 plus a noUnused compile pass.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
@@ -1,57 +0,0 @@
|
||||
---
|
||||
id: TASK-87.5
|
||||
title: >-
|
||||
Dead architecture cleanup: delete unused registry and pipeline modules that
|
||||
are off the live path
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2026-03-06 03:20'
|
||||
updated_date: '2026-03-06 03:21'
|
||||
labels:
|
||||
- tech-debt
|
||||
- dead-code
|
||||
milestone: m-0
|
||||
dependencies:
|
||||
- TASK-87.1
|
||||
- TASK-87.2
|
||||
references:
|
||||
- src/translators/index.ts
|
||||
- src/subsync/engines.ts
|
||||
- src/subtitle/pipeline.ts
|
||||
- src/tokenizers/index.ts
|
||||
- src/token-mergers/index.ts
|
||||
- src/core/services/subsync.ts
|
||||
- src/core/services/tokenizer.ts
|
||||
documentation:
|
||||
- docs/reports/2026-02-22-task-100-dead-code-report.md
|
||||
parent_task_id: TASK-87
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
The review found several modules that appear self-contained but unused from the application’s live execution paths: src/translators/index.ts, src/subsync/engines.ts, src/subtitle/pipeline.ts, src/tokenizers/index.ts, and src/token-mergers/index.ts. At the same time, the real runtime behavior is implemented elsewhere. This task should verify those modules are truly unused, remove or consolidate them, and clean up any stale exports, docs, or tests so contributors are not misled by duplicate architecture.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [ ] #1 Each candidate module identified in the review is either removed as dead code or justified and reconnected to a real supported execution path.
|
||||
- [ ] #2 Any stale exports, imports, or tests associated with the removed or consolidated modules are cleaned up so the codebase has a single obvious path for the affected behavior.
|
||||
- [ ] #3 The cleanup does not regress live tokenization or subtitle sync behavior and the relevant verification commands remain green.
|
||||
- [ ] #4 Contributor-facing documentation or internal notes no longer imply that removed duplicate architecture is part of the current design.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
|
||||
1. Re-verify each candidate module is off the live path by tracing imports from current runtime entrypoints before deleting anything.
|
||||
2. Remove or consolidate truly dead modules and clean associated exports/imports/tests so only the supported path remains obvious.
|
||||
3. Pay special attention to subtitle sync and tokenization surfaces, since duplicate architecture exists near active code.
|
||||
4. Verify the relevant tokenization and subsync commands/tests still pass and update any stale docs or notes.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
@@ -1,57 +0,0 @@
|
||||
---
|
||||
id: TASK-87.6
|
||||
title: >-
|
||||
Anki integration maintainability: continue decomposing the oversized
|
||||
orchestration layer
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2026-03-06 03:20'
|
||||
updated_date: '2026-03-06 03:21'
|
||||
labels:
|
||||
- tech-debt
|
||||
- anki
|
||||
- maintainability
|
||||
milestone: m-0
|
||||
dependencies:
|
||||
- TASK-87.1
|
||||
references:
|
||||
- src/anki-integration.ts
|
||||
- src/anki-integration/field-grouping-workflow.ts
|
||||
- src/anki-integration/note-update-workflow.ts
|
||||
- src/anki-integration/card-creation.ts
|
||||
- src/anki-integration/anki-connect-proxy.ts
|
||||
- src/anki-integration.test.ts
|
||||
documentation:
|
||||
- docs/reports/2026-02-22-task-100-dead-code-report.md
|
||||
- docs/anki-integration.md
|
||||
parent_task_id: TASK-87
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
src/anki-integration.ts remains an oversized orchestration file even after earlier extractions. It still mixes config normalization, polling setup, media generation, duplicate resolution, field grouping workflows, and user feedback coordination in one class. This task should continue the decomposition so the remaining orchestration surface is smaller and easier to reason about, while preserving existing Anki, proxy, field grouping, and note update behavior.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [ ] #1 The responsibilities currently concentrated in src/anki-integration.ts are split into clearer modules or services with narrow ownership boundaries.
|
||||
- [ ] #2 The resulting orchestration surface is materially smaller and easier to review, with at least one mixed-responsibility cluster extracted behind a well-named interface.
|
||||
- [ ] #3 Existing Anki integration behavior remains covered by automated verification, including note update, field grouping, and proxy-related flows that the refactor touches.
|
||||
- [ ] #4 Any developer-facing docs or notes needed to understand the new structure are updated in the same task.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
|
||||
1. Map the remaining responsibility clusters inside src/anki-integration.ts and choose one or more extraction seams that reduce mixed concerns without changing behavior.
|
||||
2. Move logic behind narrow interfaces/modules rather than creating another giant helper; keep orchestration readable.
|
||||
3. Preserve coverage for field grouping, note update, proxy, and card creation flows touched by the refactor.
|
||||
4. Update docs or internal notes if the new structure changes where contributors should look for a given behavior.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
@@ -1,51 +0,0 @@
|
||||
---
|
||||
id: TASK-87.7
|
||||
title: >-
|
||||
Developer workflow hygiene: make docs watch reproducible and remove stale
|
||||
small-surface drift
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2026-03-06 03:20'
|
||||
updated_date: '2026-03-06 03:21'
|
||||
labels:
|
||||
- tooling
|
||||
- tech-debt
|
||||
milestone: m-0
|
||||
dependencies: []
|
||||
references:
|
||||
- package.json
|
||||
- bun.lock
|
||||
- src/anki-integration/field-grouping-workflow.ts
|
||||
documentation:
|
||||
- docs/reports/2026-02-22-task-100-dead-code-report.md
|
||||
parent_task_id: TASK-87
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
The review found a few low-risk but recurring hygiene issues: docs:watch depends on bunx concurrently even though concurrently is not declared in package metadata, and small stale API surface remains after recent refactors, such as unused parameters in field-grouping workflow code. This task should make the developer workflow reproducible and clean up low-risk stale symbols that do not warrant a dedicated architecture task.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [ ] #1 The docs:watch workflow runs through declared project tooling or is rewritten to avoid undeclared dependencies.
|
||||
- [ ] #2 Small stale symbols or parameters identified during the review outside the main composition-root cleanup are removed without behavior changes.
|
||||
- [ ] #3 Any contributor-facing command changes are reflected in repository documentation.
|
||||
- [ ] #4 The cleanup remains scoped to low-risk workflow and hygiene fixes rather than expanding into large architectural refactors.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
|
||||
1. Fix the docs:watch workflow so it relies on declared project tooling or an equivalent checked-in command path.
|
||||
2. Clean up low-risk stale symbols surfaced by the review outside the main.ts architecture task, such as unused parameters left behind by refactors.
|
||||
3. Keep the task scoped: avoid pulling in main composition-root cleanup or larger Anki/runtime refactors.
|
||||
4. Verify the affected developer commands still work and document any usage changes.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
@@ -0,0 +1,55 @@
|
||||
# Changelog Fragments
|
||||
|
||||
Add one `.md` file per user-visible PR in this directory.
|
||||
|
||||
Use this format:
|
||||
|
||||
```md
|
||||
type: added
|
||||
area: overlay
|
||||
|
||||
- Added keyboard navigation for Yomitan popups.
|
||||
- Added auto-pause toggle when opening the popup.
|
||||
```
|
||||
|
||||
For breaking changes, add `breaking: true`:
|
||||
|
||||
```md
|
||||
type: changed
|
||||
area: config
|
||||
breaking: true
|
||||
|
||||
- Renamed `foo.bar` to `foo.baz`.
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- `type` required: `added`, `changed`, `fixed`, `docs`, or `internal`
|
||||
- `area` required: short product area like `overlay`, `launcher`, `release`
|
||||
- `breaking` optional: set to `true` to flag as a breaking change
|
||||
- each non-empty body line becomes a bullet
|
||||
- `README.md` is ignored by the generator
|
||||
- if a PR should not produce release notes, apply the `skip-changelog` label instead of adding a fragment
|
||||
|
||||
PR branch workflow:
|
||||
|
||||
- Before adding a fragment or bullet, inspect the `changes/*.md` files already changed in the PR
|
||||
- If the new work fixes, modifies, renames, or supersedes behavior introduced or referenced by that fragment, edit or remove the stale bullet instead of adding follow-up churn
|
||||
- Add a new bullet only when it describes a truly separate user-visible outcome
|
||||
- Multiple fragment files are allowed when one PR has genuinely separate release-note outcomes, but keep them minimized and current
|
||||
|
||||
How fragments turn into a release:
|
||||
|
||||
- At release time, `bun run changelog:build` (and `bun run changelog:prerelease-notes`) pipes every pending fragment through `claude -p` to merge related items, drop noise, and rewrite into a clean user-facing release body. Write fragments as raw, informative notes — don't worry about polished prose, deduping across PRs, or line-by-line phrasing. The polish step handles all of that.
|
||||
- The polish step treats pending fragments as the final release outcome, not prerelease history. If a feature is added and then renamed or fixed before the stable cut, ship the final feature bullet instead of separate prerelease-only breaking/fix entries.
|
||||
- `CHANGELOG.md`, GitHub release notes, and prerelease notes all use one item per feature or area, inline by default: `- **Name**: one or two short sentences.` Items with several separate changes may use a few short nested bullets instead of a run-on sentence. Items say what changed and how to reach it; implementation details, edge cases, and step-by-step usage belong in the docs, not the release body.
|
||||
- `internal` fragments stay in `CHANGELOG.md` (inside a collapsed `<details>` block) but are dropped from the GitHub release notes entirely.
|
||||
- The polished `CHANGELOG.md` and `packaging/release-notes/release-notes.md` are committed and reviewed before tagging — edit the Markdown by hand if Claude misses something.
|
||||
|
||||
Prerelease notes:
|
||||
|
||||
- prerelease tags like `v0.11.3-beta.1` and `v0.11.3-rc.1` reuse the current pending fragments to generate `packaging/release-notes/prerelease-notes.md`
|
||||
- from the second prerelease of a base version onward, the notes also open with a `## Changes since <previous tag>` section generated from the fragment diff against the previous beta/RC tag; keep fragment edits meaningful. Editorial-only rewording is filtered out of that section, while genuinely changed behavior and deleted fragments (reverted changes) are reported
|
||||
- existing prerelease notes are a reviewed baseline; later prerelease runs should replace stale beta/RC wording with the current outcome instead of appending fix churn
|
||||
- prerelease note generation does not consume fragments and does not update `CHANGELOG.md` or `docs-site/changelog.md`
|
||||
- the final stable release is the point where `bun run changelog:build` consumes fragments into the stable changelog and release notes
|
||||
@@ -0,0 +1,5 @@
|
||||
type: fixed
|
||||
area: overlay
|
||||
|
||||
- The controller settings window (`Alt+C`) no longer edits a controller it cannot see. A newly opened window cannot see any controller until you press one of its buttons (browsers hide a pad from a new window until then); it used to show and edit the global defaults meanwhile, and its status could stay stuck on "No controllers detected". Now it shows the bindings of the controller the overlay is using (or the saved one), labelled as a preview, and Learn, Clear, Reset and the button style are locked until the press.
|
||||
- With several controllers connected, `Alt+C` now selects the one you just pressed, and Learn listens to that controller.
|
||||
@@ -0,0 +1,5 @@
|
||||
type: fixed
|
||||
area: anilist
|
||||
|
||||
- AniList progress sync now adds a show to your Watching list (or Completed on the final episode) when it is not on your list yet, instead of failing with "AniList update not possible". This only happens when the file title exactly matches an AniList title or the entry is pinned with the character dictionary override; loose matches still require adding the show yourself.
|
||||
- Entries marked Completed, Paused, Dropped, or Rewatching are still never changed.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: anki
|
||||
|
||||
- `ankiConnect.fields.audio` now defaults to `SentenceAudio` instead of `ExpressionAudio`, so generated sentence audio no longer overwrites Yomitan's word audio on default configs. `fields.wordAudio` still defaults to `ExpressionAudio`. If you relied on the old default, set `fields.audio` to `ExpressionAudio` explicitly.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: linux
|
||||
|
||||
- Fixed AppImage builds on Ubuntu 26.04+ (uutils coreutils) releasing their mount before Chromium child processes finished shutting down, which brought back "Service Crash" notifications on quit.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: overlay
|
||||
|
||||
- Controller remaps made in `Alt+C` now apply right away; before, the overlay kept the old bindings until SubMiner restarted.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: added
|
||||
area: overlay
|
||||
|
||||
- `Alt+C` now warns when two controller actions share an input, as the dialog layouts already did. Both rows show "⚠ Same as …", including a button on the D-pad while a **(D-pad)** row under **Navigation** is still on, and a stick direction used by a stick action.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: added
|
||||
area: overlay
|
||||
|
||||
- `Alt+C` has a **Button Style** choice (Xbox, Nintendo or PlayStation), saved per controller. PlayStation is new: buttons and the review and duplicate card shortcuts show ╳ ○ □ △. `controller.faceButtonLabels` accepts `playstation` and stays the default for controllers without their own style.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: changed
|
||||
area: overlay
|
||||
|
||||
- Controller connected and controller-disabled messages are now regular notifications that follow `ankiConnect.behavior.notificationType` (overlay, system, both or none) instead of a separate pill in the corner of the overlay. Connected notices only appear when `controller.enabled` is on.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: overlay
|
||||
|
||||
- Clicking a row in `Alt+C` shows its **Learn**, **Clear** and **Reset** buttons again. The panel opened but was squeezed to nothing once the list was taller than the window, so **Clear** (set a binding to None) could not be reached.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: added
|
||||
area: overlay
|
||||
|
||||
- The duplicate card (field grouping) dialog has a controller layout: the D-pad picks the original or new card, `A` continues and confirms the merge, `B` cancels or goes back from the preview, `X` turns "Delete duplicate" on or off, `Y` switches Compact / Full, and the D-pad scrolls the preview. Each control shows its shortcut, and the layout can be changed per controller in the new **Duplicate card (field grouping)** group of `Alt+C`.
|
||||
@@ -0,0 +1,5 @@
|
||||
type: added
|
||||
area: overlay
|
||||
|
||||
- Added a controller layout for the media timing review dialog covering every button, with L1 / R1 + D-pad combos for the clip start and end, and D-pad actions that repeat while held. Each button shows its controller shortcut while controller support is on, and the first press in a newly opened dialog already counts. The controller's usual actions pause while the dialog is open, including in the hidden subtitle overlay behind it.
|
||||
- Added `controller.faceButtonLabels` (`xbox` or `nintendo`) so fixed controller layouts and the `Alt+C` button names follow the letters printed on the pad. Use `nintendo` for controllers with A on the right and B at the bottom.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: changed
|
||||
area: overlay
|
||||
|
||||
- The controller's **Toggle MPV Pause** button now works without keyboard-only mode, so you can pause while just watching.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: added
|
||||
area: overlay
|
||||
|
||||
- The media timing review controller layout can now be changed in the new **Review media timing** group of `Alt+C`. Learn records a single button or a combo (hold one button, press another), any button can be a hold button, changes are saved per controller, rows warn about clashing inputs, and **Reset all** restores the default layout. The review's button hints follow your bindings.
|
||||
@@ -0,0 +1,5 @@
|
||||
type: added
|
||||
area: overlay
|
||||
|
||||
- Added a controller binding that shows the secondary subtitle only while a button is held (`controller.bindings.holdSecondarySub`). It is unbound by default; assign it with Learn in the `Alt+C` controller modal. It works with or without keyboard-only mode and does not change the saved secondary subtitle mode.
|
||||
- Added controller bindings to select the previous or next word entry in the open Yomitan popup (`controller.bindings.previousEntry` / `nextEntry`). Mine Card mines the selected entry. Both are unbound by default; assign them with Learn in the `Alt+C` controller modal.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: overlay
|
||||
|
||||
- The `Alt+C` controller screen now shows the right button names for controllers that report the W3C standard layout (most Xbox, PlayStation, Switch and 8BitDo pads). Previously Select and Start showed as `L3` and `R3`, and the triggers as `Back` and `Start`. Bindings themselves were unaffected.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: added
|
||||
area: overlay
|
||||
|
||||
- New controller actions for subtitle lines, in the **Subtitles** group of `Alt+C`: **Previous Subtitle**, **Next Subtitle**, **Replay Subtitle** and **Play Next Subtitle**, matching `Shift+H`, `Shift+L`, `Ctrl+Shift+H` and `Ctrl+Shift+L`. They are unbound by default and work with or without keyboard-only mode.
|
||||
@@ -0,0 +1,5 @@
|
||||
type: added
|
||||
area: jimaku
|
||||
|
||||
- Added `jimaku.autoOpenWhenPrimarySubtitleMissing` (off by default). When a local video loads without a Japanese (primary-language) subtitle track, SubMiner pauses playback and opens the Jimaku modal; playback resumes when the modal closes.
|
||||
- Opening Jimaku while it is already open now reloads the title and episode if the video changed, instead of keeping the previous episode's search.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: added
|
||||
area: overlay
|
||||
|
||||
- New **Pause Video In Keyboard-Only Mode** setting (`subtitleStyle.autoPauseVideoOnKeyboardMode`, off by default): turning keyboard-only mode on, from the keyboard or a controller, pauses the video, and turning it off resumes it. A video that was already paused, or that you played or paused yourself in the meantime, is left as it is.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: changed
|
||||
area: overlay
|
||||
|
||||
- Turning keyboard-only mode off, from the keyboard or a controller, now also closes the dictionary popup.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: overlay
|
||||
|
||||
- On macOS, controller buttons now work while mpv is in front: a press brings the overlay forward and runs that button's action, instead of doing nothing until you click the overlay.
|
||||
@@ -0,0 +1,5 @@
|
||||
type: fixed
|
||||
area: overlay
|
||||
|
||||
- On macOS and Windows, one controller press no longer runs its action twice, which cancelled out toggles like mpv pause.
|
||||
- On macOS, the press that brings the overlay forward from mpv no longer runs its action a second time.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: mpv-plugin
|
||||
|
||||
- Fixed the "Overlay loading" message staying on screen when mpv moves to the next file in a playlist, then turning into "Overlay did not become ready; check SubMiner logs" after 30 seconds even though the overlay was working. The overlay window is reused between files, so the plugin no longer restarts the loading message once the overlay has reported ready.
|
||||
@@ -0,0 +1,10 @@
|
||||
type: added
|
||||
area: plugins
|
||||
|
||||
- Added JavaScript plugins loaded at startup from `<config dir>/plugins/<id>/` and enabled under `plugins` in the config. Plugins can listen to mpv events, run timers, register actions bound with `__plugin-action:<id>/<action>` keybindings or run with `--plugin-action`, run built-in actions, react to mined Anki cards and call AnkiConnect, start mpv, run programs, keep state in a per-plugin data folder, and show results in an overlay panel or their own window.
|
||||
- Added an example `ai-explain` plugin that sends the current line and its neighbors to `codex exec` and shows a translation or grammar explanation.
|
||||
- Added an example `anki-sync` plugin that syncs Anki to AnkiWeb after you mine cards, once mining goes quiet or playback ends.
|
||||
- Added an example `episode-tags` plugin that tags mined cards with the show and episode, such as `show::Frieren::S01E07`.
|
||||
- Added an example `auto-jimaku` plugin that opens the Jimaku search when a file starts without a Japanese subtitle track.
|
||||
- Added a versioned plugin API with standalone authoring types, cached playback snapshots, lifecycle cancellation and cleanup, and awaitable mpv commands. Async activation is bounded and actions wait only for their own plugin. Shutdown skips pending loads, runs each deactivation once, and blocks calls from disposed plugins.
|
||||
- Mining events preserve original and kept note ids with the media and subtitle captured when mining starts, so delayed enrichment and merges into older notes keep the source episode. Stats-dashboard cards do not trigger plugin events. Plugin subtitle lookups and mining provenance account for subtitle delay and return timing on the subtitle file's timeline.
|
||||