Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44d0d2e9c5
|
||
|
|
96c87ae003
|
||
|
|
4842359e39
|
||
|
|
9e53ecb27b
|
||
|
|
e45d302b5d
|
||
|
|
f08d9d1d53
|
||
|
|
01613f1be5 | ||
|
|
bf4e5bb609 | ||
|
|
2aedb3a1fa
|
||
|
|
8809ed38b2
|
||
|
|
7909fcd2cf | ||
|
|
8fc20b2499 | ||
|
|
c18644392d
|
||
|
|
9c351a8603
|
||
|
|
ea2b3a9507 | ||
|
|
6c295586cb
|
||
|
|
0523beef2b
|
||
|
|
a6d96e9966 | ||
|
|
06fc578830
|
||
|
|
7108e086ba | ||
|
|
994483ccd6
|
||
|
|
6350ee928d
|
||
|
|
78a9b6346f | ||
|
|
78eca52b7e | ||
|
|
4d1adebf11
|
||
|
|
e1c4d6ca94
|
||
|
|
e25d472d82
|
||
|
|
20f151482a
|
||
|
|
b48083ddd8 | ||
|
|
4b6842d034 | ||
|
|
170dbbb74c
|
||
|
|
37d3f5e03e
|
||
|
|
55c751830e
|
||
|
|
9ffa2bce94
|
||
|
|
88b73656a7
|
||
|
|
e77aa5c3b3 | ||
|
|
121b688afb | ||
|
|
727be5683a | ||
|
|
138bfe9759 | ||
|
|
0aba17d95b
|
||
|
|
3091d7b2a7
|
||
|
|
4423dd56af
|
||
|
|
a5d0b63792 | ||
|
|
15cb9fe918
|
||
|
|
db45a70ec2
|
||
|
|
a6df21bdc2
|
||
|
|
9e79fa93c2 | ||
|
|
9fadbc8cdd
|
||
|
|
ffbbe698fc | ||
|
|
e5a6e3b178
|
||
|
|
7b72dd2cc4
|
||
|
|
152d2c0626
|
||
|
|
416cd7e9ac
|
||
|
|
bbb0e9b6a5
|
||
|
|
eb749f8c19
|
||
|
|
e09a416422
|
||
|
|
65ac5a7728 | ||
|
|
42f86b0748
|
||
|
|
e69cd5ea06
|
||
|
|
df675cfbfe
|
||
|
|
0cff52f8e4
|
||
|
|
b52692d826
|
||
|
|
e49b5f6589
|
||
|
|
4ab5e37bc5
|
||
|
|
f00b830054
|
||
|
|
0bbd8617f4 | ||
|
|
4e43c5c6e1 | ||
|
|
953abdf42a
|
||
|
|
fdb0a31fff
|
||
|
|
ec73157f37
|
||
|
|
04b878dd93
|
||
|
|
45212c7659
|
||
|
|
b710822db2
|
||
|
|
b347fdf21a
|
||
|
|
07b32626a6 | ||
|
|
c68b73c8c1 | ||
|
|
a4325740b7
|
||
|
|
4648c5e5a1
|
||
|
|
72d0b19d94
|
||
|
|
b24008237b | ||
|
|
63eb228e4f | ||
|
|
c627095e6e
|
||
|
|
ba0155e429 | ||
|
|
dea1136170
|
||
|
|
23af9c6464
|
||
|
|
8825cca642
|
||
|
|
bc73a02623 | ||
|
|
874d2c9f1a
|
||
|
|
52fc1b63e9
|
||
|
|
da5074761f
|
||
|
|
f7230db90c
|
||
|
|
92ef305e8c
|
||
|
|
5096ca217b
|
||
|
|
504faa51e0
|
||
|
|
c10174f9cd
|
||
|
|
285b19ccc3
|
||
|
|
33e694bc61
|
||
|
|
7fbc36dc41
|
||
|
|
430120cc63
|
||
|
|
294e3a777e
|
||
|
|
e93b696e71
|
||
|
|
c673b71053
|
||
|
|
d31a28c54f
|
||
|
|
d100789232
|
||
|
|
9a5fb02cbd
|
||
|
|
7c161723cd | ||
|
|
2ba820f41a
|
||
|
|
c39dbb5a8d
|
||
|
|
2958591ec9
|
||
|
|
c9880efee3
|
||
|
|
f225ac3c89
|
||
|
|
cacaa2b371
|
||
|
|
b0fb39a8f5 | ||
|
|
a7a302bdd0 | ||
|
|
9fe4c7eb33
|
||
|
|
ab5e50adac
|
||
|
|
15c74ef502
|
||
|
|
f0d3c95b4c
|
||
|
|
d7e1d7edc6
|
||
|
|
6a1d1211ae
|
||
|
|
7585d430ae
|
||
|
|
122ff9a3c5
|
||
|
|
5354fdf7b9
|
||
|
|
d9fdc7ef6d
|
||
|
|
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,84 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-test-audit:
|
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:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
bun-version: 1.3.5
|
bun-version: 1.3.5
|
||||||
|
|
||||||
- name: Cache dependencies
|
- name: Point temp files at an uploadable directory
|
||||||
uses: actions/cache@v4
|
run: |
|
||||||
with:
|
# os.tmpdir() follows these, so the kept session logs land under runner.temp.
|
||||||
path: |
|
dir="$RUNNER_TEMP/e2e"
|
||||||
~/.bun/install/cache
|
mkdir -p "$dir"
|
||||||
node_modules
|
{ echo "TMPDIR=$dir"; echo "TEMP=$dir"; echo "TMP=$dir"; } >> "$GITHUB_ENV"
|
||||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
|
|
||||||
restore-keys: |
|
- name: Install mpv and ffmpeg (Linux)
|
||||||
${{ runner.os }}-bun-
|
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: brew install mpv ffmpeg
|
||||||
|
|
||||||
|
- 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
|
- name: Install dependencies
|
||||||
run: bun install --frozen-lockfile
|
run: |
|
||||||
|
bun install --frozen-lockfile
|
||||||
|
cd stats && bun install --frozen-lockfile
|
||||||
|
|
||||||
- name: Build (TypeScript check)
|
- name: Build
|
||||||
# Keep explicit typecheck for fast fail before full build/bundle.
|
run: bun run build
|
||||||
run: bun run tsc --noEmit
|
|
||||||
|
|
||||||
- name: Test suite (source)
|
- name: End-to-end suite
|
||||||
run: bun run test:fast
|
run: bun run test:e2e
|
||||||
|
|
||||||
- name: Launcher smoke suite (source)
|
- name: Dictionary backend windows (Linux)
|
||||||
run: bun run test:launcher:smoke:src
|
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()
|
if: failure()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: launcher-smoke
|
name: e2e-logs-${{ matrix.os }}
|
||||||
path: .tmp/launcher-smoke/**
|
path: ${{ runner.temp }}/e2e/subminer-e2e-*/*.log
|
||||||
if-no-files-found: ignore
|
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
|
|
||||||
@@ -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=(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: Smoke packaged runtime assets
|
||||||
|
shell: bash
|
||||||
|
run: xvfb-run -a bun run test:package "release/linux-unpacked/resources"
|
||||||
|
|
||||||
|
- name: Upload AppImage artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: appimage
|
||||||
|
path: |
|
||||||
|
release/*.AppImage
|
||||||
|
release/latest*.yml
|
||||||
|
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 "release/mac-arm64/SubMiner.app/Contents/Resources"
|
||||||
|
|
||||||
|
- name: Upload macOS artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: macos
|
||||||
|
path: |
|
||||||
|
release/*.dmg
|
||||||
|
release/*.zip
|
||||||
|
release/latest*.yml
|
||||||
|
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 "release/win-unpacked/resources"
|
||||||
|
|
||||||
|
- name: Upload Windows artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: windows
|
||||||
|
path: |
|
||||||
|
release/*.exe
|
||||||
|
release/*.zip
|
||||||
|
release/latest*.yml
|
||||||
|
release/*.blockmap
|
||||||
|
if-no-files-found: error
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
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: release
|
||||||
|
|
||||||
|
- name: Download macOS artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: macos
|
||||||
|
path: release
|
||||||
|
|
||||||
|
- name: Download Windows artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: windows
|
||||||
|
path: 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: |
|
||||||
|
tar -czf "release/subminer-assets.tar.gz" \
|
||||||
|
config.example.jsonc \
|
||||||
|
plugin/subminer \
|
||||||
|
plugin/subminer.conf \
|
||||||
|
assets/themes/subminer.rasi \
|
||||||
|
assets/thumbnailers/subminer-ffmpegthumbnailer.thumbnailer \
|
||||||
|
resources/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/*.exe release/*.zip release/*.tar.gz release/latest*.yml 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
|
||||||
|
: > release/SHA256SUMS.txt
|
||||||
|
for file in "${files[@]}"; do
|
||||||
|
printf '%s %s\n' \
|
||||||
|
"$(sha256sum "$file" | awk '{print $1}')" \
|
||||||
|
"${file##*/}" >> 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 release/prerelease-notes.md ]; then
|
||||||
|
echo "::error::release/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::release/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=(
|
||||||
|
release/*.AppImage
|
||||||
|
release/*.dmg
|
||||||
|
release/*.exe
|
||||||
|
release/*.zip
|
||||||
|
release/*.tar.gz
|
||||||
|
release/*.tar.gz.sha256
|
||||||
|
release/latest*.yml
|
||||||
|
release/*.blockmap
|
||||||
|
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 release/prerelease-notes.md
|
||||||
|
else
|
||||||
|
gh release create "$RELEASE_VERSION" \
|
||||||
|
--draft \
|
||||||
|
--latest=false \
|
||||||
|
--prerelease \
|
||||||
|
--title "$RELEASE_VERSION" \
|
||||||
|
--notes-file release/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 release/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:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
|
- '!v*-beta.*'
|
||||||
|
- '!v*-rc.*'
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: release-${{ github.ref }}
|
group: release-${{ github.ref }}
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
quality-gate:
|
quality-gate:
|
||||||
runs-on: ubuntu-latest
|
permissions:
|
||||||
steps:
|
contents: read
|
||||||
- name: Checkout
|
uses: ./.github/workflows/quality-gate.yml
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
|
|
||||||
- name: Setup Bun
|
package:
|
||||||
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:
|
|
||||||
needs: [quality-gate]
|
needs: [quality-gate]
|
||||||
runs-on: ubuntu-latest
|
permissions:
|
||||||
steps:
|
contents: read
|
||||||
- name: Checkout
|
uses: ./.github/workflows/package-release.yml
|
||||||
uses: actions/checkout@v4
|
secrets:
|
||||||
with:
|
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||||
submodules: true
|
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||||
|
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||||
- name: Setup Bun
|
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||||
uses: oven-sh/setup-bun@v2
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||||
with:
|
SUBMINER_TMDB_API_KEY: ${{ secrets.SUBMINER_TMDB_API_KEY }}
|
||||||
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
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
needs: [build-linux, build-macos]
|
needs: [package]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -211,6 +53,12 @@ jobs:
|
|||||||
name: macos
|
name: macos
|
||||||
path: release
|
path: release
|
||||||
|
|
||||||
|
- name: Download Windows artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: windows
|
||||||
|
path: release
|
||||||
|
|
||||||
- name: Setup Bun
|
- name: Setup Bun
|
||||||
uses: oven-sh/setup-bun@v2
|
uses: oven-sh/setup-bun@v2
|
||||||
with:
|
with:
|
||||||
@@ -229,112 +77,101 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: bun install --frozen-lockfile
|
run: bun install --frozen-lockfile
|
||||||
|
|
||||||
- name: Build Bun subminer wrapper
|
- name: Build launcher runtime artifacts
|
||||||
run: make build-launcher
|
run: make build-launcher
|
||||||
|
|
||||||
- name: Verify Bun subminer wrapper
|
- name: Smoke launcher bundle
|
||||||
run: dist/launcher/subminer --help >/dev/null
|
run: bun dist/launcher/subminer.js --help >/dev/null
|
||||||
|
|
||||||
- name: Enforce generated launcher workflow
|
- name: Enforce generated launcher workflow
|
||||||
run: bash scripts/verify-generated-launcher.sh
|
run: bash scripts/verify-generated-launcher.sh
|
||||||
|
|
||||||
|
- name: Verify generated config examples
|
||||||
|
run: bun run verify:config-example
|
||||||
|
|
||||||
- name: Package optional assets bundle
|
- name: Package optional assets bundle
|
||||||
run: |
|
run: |
|
||||||
tar -czf "release/subminer-assets.tar.gz" \
|
tar -czf "release/subminer-assets.tar.gz" \
|
||||||
config.example.jsonc \
|
config.example.jsonc \
|
||||||
plugin/subminer \
|
plugin/subminer \
|
||||||
plugin/subminer.conf \
|
plugin/subminer.conf \
|
||||||
assets/themes/subminer.rasi
|
assets/themes/subminer.rasi \
|
||||||
|
assets/thumbnailers/subminer-ffmpegthumbnailer.thumbnailer \
|
||||||
|
resources/bun/licenses
|
||||||
|
|
||||||
|
- name: Package Bun corresponding source
|
||||||
|
run: bun scripts/package-bun-source.mjs
|
||||||
|
|
||||||
- name: Generate checksums
|
- name: Generate checksums
|
||||||
run: |
|
run: |
|
||||||
shopt -s nullglob
|
shopt -s nullglob
|
||||||
files=(release/*.AppImage release/*.dmg release/*.zip release/*.tar.gz dist/launcher/subminer)
|
files=(release/*.AppImage release/*.dmg release/*.exe release/*.zip release/*.tar.gz release/latest*.yml release/*.blockmap dist/launcher/subminer dist/launcher/subminer.cmd)
|
||||||
if [ "${#files[@]}" -eq 0 ]; then
|
if [ "${#files[@]}" -eq 0 ]; then
|
||||||
echo "No release artifacts found for checksum generation."
|
echo "No release artifacts found for checksum generation."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
sha256sum "${files[@]}" > release/SHA256SUMS.txt
|
: > release/SHA256SUMS.txt
|
||||||
|
for file in "${files[@]}"; do
|
||||||
|
printf '%s %s\n' \
|
||||||
|
"$(sha256sum "$file" | awk '{print $1}')" \
|
||||||
|
"${file##*/}" >> release/SHA256SUMS.txt
|
||||||
|
done
|
||||||
|
|
||||||
- name: Get version from tag
|
- name: Get version from tag
|
||||||
id: version
|
id: version
|
||||||
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Generate changelog
|
- name: Guard against pending changelog fragments
|
||||||
id: changelog
|
env:
|
||||||
|
RELEASE_VERSION: ${{ steps.version.outputs.VERSION }}
|
||||||
run: |
|
run: |
|
||||||
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
|
if find changes -maxdepth 1 -name '*.md' -not -name README.md -print -quit | grep -q .; then
|
||||||
if [ -n "$PREV_TAG" ]; 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."
|
||||||
CHANGES=$(git log --pretty=format:"- %s" ${PREV_TAG}..HEAD)
|
exit 1
|
||||||
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
|
|
||||||
fi
|
fi
|
||||||
echo "CHANGES<<EOF" >> $GITHUB_OUTPUT
|
|
||||||
echo "$CHANGES" >> $GITHUB_OUTPUT
|
- name: Verify changelog is ready for tagged release
|
||||||
echo "EOF" >> $GITHUB_OUTPUT
|
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
|
- name: Publish Release
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
RELEASE_VERSION: ${{ steps.version.outputs.VERSION }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
cat > release-body.md <<'EOF'
|
if gh release view "$RELEASE_VERSION" >/dev/null 2>&1; then
|
||||||
## Changes
|
# Do not pass the prerelease flag here; gh defaults to a normal release.
|
||||||
${{ steps.changelog.outputs.CHANGES }}
|
gh release edit "$RELEASE_VERSION" \
|
||||||
|
--draft=false \
|
||||||
## Installation
|
--title "$RELEASE_VERSION" \
|
||||||
|
--notes-file release/release-notes.md
|
||||||
### 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
|
|
||||||
else
|
else
|
||||||
gh release create "${{ steps.version.outputs.VERSION }}" \
|
gh release create "$RELEASE_VERSION" \
|
||||||
--title "${{ steps.version.outputs.VERSION }}" \
|
--title "$RELEASE_VERSION" \
|
||||||
--notes-file release-body.md \
|
--notes-file release/release-notes.md
|
||||||
--prerelease false
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
shopt -s nullglob
|
shopt -s nullglob
|
||||||
artifacts=(
|
artifacts=(
|
||||||
release/*.AppImage
|
release/*.AppImage
|
||||||
release/*.dmg
|
release/*.dmg
|
||||||
|
release/*.exe
|
||||||
release/*.zip
|
release/*.zip
|
||||||
release/*.tar.gz
|
release/*.tar.gz
|
||||||
|
release/*.tar.gz.sha256
|
||||||
|
release/latest*.yml
|
||||||
|
release/*.blockmap
|
||||||
release/SHA256SUMS.txt
|
release/SHA256SUMS.txt
|
||||||
dist/launcher/subminer
|
dist/launcher/subminer
|
||||||
|
dist/launcher/subminer.cmd
|
||||||
)
|
)
|
||||||
|
|
||||||
if [ "${#artifacts[@]}" -eq 0 ]; then
|
if [ "${#artifacts[@]}" -eq 0 ]; then
|
||||||
@@ -343,5 +180,145 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
for asset in "${artifacts[@]}"; do
|
for asset in "${artifacts[@]}"; do
|
||||||
gh release upload "${{ steps.version.outputs.VERSION }}" "$asset" --clobber
|
gh release upload "$RELEASE_VERSION" "$asset" --clobber
|
||||||
done
|
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,26 @@
|
|||||||
# Dependencies
|
# Dependencies
|
||||||
node_modules/
|
node_modules/
|
||||||
|
/package-lock.json
|
||||||
|
|
||||||
|
# Superpowers brainstorming
|
||||||
|
.superpowers/
|
||||||
|
|
||||||
# Electron build output
|
# Electron build output
|
||||||
out/
|
out/
|
||||||
dist/
|
dist/
|
||||||
release/
|
release/*
|
||||||
|
!release/
|
||||||
|
!release/release-notes.md
|
||||||
|
!release/prerelease-notes.md
|
||||||
|
build/yomitan/
|
||||||
|
coverage/
|
||||||
|
|
||||||
# Launcher build artifact (produced by make build-launcher)
|
# Launcher build artifact (produced by make build-launcher)
|
||||||
/subminer
|
/subminer
|
||||||
|
/main-entry.js
|
||||||
|
/main-entry.js.map
|
||||||
|
|
||||||
|
/build/hachidori/
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
*.log
|
*.log
|
||||||
@@ -21,9 +34,7 @@ Thumbs.db
|
|||||||
.idea/
|
.idea/
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
**/CLAUDE.md
|
|
||||||
environment.toml
|
environment.toml
|
||||||
**/CLAUDE.md
|
|
||||||
.env
|
.env
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
|
||||||
@@ -34,5 +45,27 @@ docs/.vitepress/cache/
|
|||||||
docs/.vitepress/dist/
|
docs/.vitepress/dist/
|
||||||
tests/*
|
tests/*
|
||||||
.worktrees/
|
.worktrees/
|
||||||
|
.tmp/
|
||||||
.codex/*
|
.codex/*
|
||||||
.agents/*
|
.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"]
|
[submodule "vendor/yomitan-jlpt-vocab"]
|
||||||
path = vendor/yomitan-jlpt-vocab
|
path = vendor/yomitan-jlpt-vocab
|
||||||
url = https://github.com/stephenmk/yomitan-jlpt-vocab
|
url = https://github.com/stephenmk/yomitan-jlpt-vocab
|
||||||
[submodule "yomitan-jlpt-vocab"]
|
[submodule "vendor/subminer-yomitan"]
|
||||||
path = vendor/yomitan-jlpt-vocab
|
path = vendor/subminer-yomitan
|
||||||
url = https://github.com/stephenmk/yomitan-jlpt-vocab
|
url = https://github.com/ksyasuda/subminer-yomitan
|
||||||
|
[submodule "vendor/hachidori"]
|
||||||
|
path = vendor/hachidori
|
||||||
|
url = https://github.com/ksyasuda/hachidori.git
|
||||||
|
branch = subminer
|
||||||
@@ -4,3 +4,4 @@ release
|
|||||||
coverage
|
coverage
|
||||||
vendor
|
vendor
|
||||||
*.log
|
*.log
|
||||||
|
src/core/services/tokenizer/__fixtures__/golden/*.json
|
||||||
@@ -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.
|
`CLAUDE.md` is a symlink to this file; there is one project instruction file, not two.
|
||||||
- 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).
|
|
||||||
|
|
||||||
- **First time working here?** Read the overview resource IMMEDIATELY to learn the workflow
|
## Quick Start
|
||||||
- **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
|
|
||||||
|
|
||||||
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
|
## Build / Test
|
||||||
- Search-first workflow to avoid duplicates
|
|
||||||
- Links to detailed guides for task creation, execution, and finalization
|
|
||||||
- MCP tools reference
|
|
||||||
|
|
||||||
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 mpv window helper (`scripts/get-mpv-window-macos.swift`): compile it 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
|
APP_NAME := subminer
|
||||||
THEME_SOURCE := assets/themes/subminer.rasi
|
THEME_SOURCE := assets/themes/subminer.rasi
|
||||||
|
THUMBNAILER_SOURCE := assets/thumbnailers/subminer-ffmpegthumbnailer.thumbnailer
|
||||||
LAUNCHER_OUT := dist/launcher/$(APP_NAME)
|
LAUNCHER_OUT := dist/launcher/$(APP_NAME)
|
||||||
THEME_FILE := subminer.rasi
|
THEME_FILE := subminer.rasi
|
||||||
PLUGIN_CONF := plugin/subminer.conf
|
THUMBNAILER_FILE := subminer-ffmpegthumbnailer.thumbnailer
|
||||||
|
|
||||||
# Default install prefix for the wrapper script.
|
# Default install prefix for the wrapper script.
|
||||||
PREFIX ?= $(HOME)/.local
|
PREFIX ?= $(HOME)/.local
|
||||||
@@ -20,15 +21,11 @@ MACOS_DATA_DIR ?= $(HOME)/Library/Application Support/SubMiner
|
|||||||
MACOS_APP_DIR ?= $(HOME)/Applications
|
MACOS_APP_DIR ?= $(HOME)/Applications
|
||||||
MACOS_APP_DEST ?= $(MACOS_APP_DIR)/SubMiner.app
|
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/.
|
# If building from source, the AppImage will typically land in release/.
|
||||||
APPIMAGE_SRC := $(firstword $(wildcard release/SubMiner-*.AppImage))
|
APPIMAGE_SRC = $(firstword $(wildcard release/SubMiner-*.AppImage))
|
||||||
MACOS_APP_SRC := $(firstword $(wildcard release/*.app release/*/*.app))
|
MACOS_APP_SRC = $(firstword $(wildcard release/*.app release/*/*.app))
|
||||||
MACOS_ZIP_SRC := $(firstword $(wildcard release/SubMiner-*.zip))
|
MACOS_ZIP_SRC = $(firstword $(wildcard release/SubMiner-*.zip))
|
||||||
|
PRERELEASE_NOTES := release/prerelease-notes.md
|
||||||
|
|
||||||
UNAME_S := $(shell uname -s 2>/dev/null || echo Unknown)
|
UNAME_S := $(shell uname -s 2>/dev/null || echo Unknown)
|
||||||
ifeq ($(OS),Windows_NT)
|
ifeq ($(OS),Windows_NT)
|
||||||
@@ -41,6 +38,17 @@ else
|
|||||||
PLATFORM := unknown
|
PLATFORM := unknown
|
||||||
endif
|
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:
|
help:
|
||||||
@printf '%s\n' \
|
@printf '%s\n' \
|
||||||
"Targets:" \
|
"Targets:" \
|
||||||
@@ -56,20 +64,23 @@ help:
|
|||||||
" dev-watch-macos Start watch loop with forced macOS tracker backend" \
|
" dev-watch-macos Start watch loop with forced macOS tracker backend" \
|
||||||
" dev-toggle Toggle overlay in a running local Electron app" \
|
" dev-toggle Toggle overlay in a running local Electron app" \
|
||||||
" dev-stop Stop a running local Electron app" \
|
" dev-stop Stop a running local Electron app" \
|
||||||
" docs-dev Run VitePress docs dev server" \
|
" docs-test Run docs tests" \
|
||||||
" docs-watch Run VitePress docs dev + Backlog browser together" \
|
" docs-build Build the docs site" \
|
||||||
" docs Build VitePress static docs" \
|
" docs-build-versioned Build production versioned docs site" \
|
||||||
" docs-preview Preview built VitePress docs" \
|
" docs-dev Start the docs dev server" \
|
||||||
" install-linux Install Linux wrapper/theme/app artifacts" \
|
" install-linux Install Linux wrapper/theme/app artifacts" \
|
||||||
" install-macos Install macOS 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" \
|
" generate-config Generate ~/.config/SubMiner/config.jsonc from centralized defaults" \
|
||||||
"" \
|
"" \
|
||||||
"Other targets:" \
|
"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-linux Remove Linux install artifacts" \
|
||||||
" uninstall-macos Remove macOS install artifacts" \
|
" uninstall-macos Remove macOS install artifacts" \
|
||||||
|
" uninstall-windows Remove Windows mpv plugin artifacts" \
|
||||||
" print-dirs Show resolved install locations" \
|
" print-dirs Show resolved install locations" \
|
||||||
|
" lint Lint stats (format check)" \
|
||||||
"" \
|
"" \
|
||||||
"Variables:" \
|
"Variables:" \
|
||||||
" PREFIX=... Override wrapper install prefix (default: $$HOME/.local)" \
|
" PREFIX=... Override wrapper install prefix (default: $$HOME/.local)" \
|
||||||
@@ -78,7 +89,7 @@ help:
|
|||||||
" LINUX_DATA_DIR=... Override Linux app data dir" \
|
" LINUX_DATA_DIR=... Override Linux app data dir" \
|
||||||
" MACOS_DATA_DIR=... Override macOS app data dir" \
|
" MACOS_DATA_DIR=... Override macOS app data dir" \
|
||||||
" MACOS_APP_DIR=... Override macOS app install dir (default: $$HOME/Applications)" \
|
" 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:
|
print-dirs:
|
||||||
@printf '%s\n' \
|
@printf '%s\n' \
|
||||||
@@ -89,26 +100,38 @@ print-dirs:
|
|||||||
"MACOS_DATA_DIR=$(MACOS_DATA_DIR)" \
|
"MACOS_DATA_DIR=$(MACOS_DATA_DIR)" \
|
||||||
"MACOS_APP_DIR=$(MACOS_APP_DIR)" \
|
"MACOS_APP_DIR=$(MACOS_APP_DIR)" \
|
||||||
"MACOS_APP_DEST=$(MACOS_APP_DEST)" \
|
"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)" \
|
"APPIMAGE_SRC=$(APPIMAGE_SRC)" \
|
||||||
"MACOS_APP_SRC=$(MACOS_APP_SRC)" \
|
"MACOS_APP_SRC=$(MACOS_APP_SRC)" \
|
||||||
"MACOS_ZIP_SRC=$(MACOS_ZIP_SRC)"
|
"MACOS_ZIP_SRC=$(MACOS_ZIP_SRC)"
|
||||||
|
|
||||||
deps:
|
submodules:
|
||||||
@$(MAKE) --no-print-directory ensure-bun
|
@git submodule update --init --recursive
|
||||||
|
|
||||||
|
deps: submodules ensure-bun
|
||||||
@bun install
|
@bun install
|
||||||
|
@cd stats && bun install --frozen-lockfile
|
||||||
@cd vendor/texthooker-ui && bun install --frozen-lockfile
|
@cd vendor/texthooker-ui && bun install --frozen-lockfile
|
||||||
|
|
||||||
ensure-bun:
|
ensure-bun:
|
||||||
@command -v bun >/dev/null 2>&1 || { printf '%s\n' "[ERROR] bun not found"; exit 1; }
|
@command -v bun >/dev/null 2>&1 || { printf '%s\n' "[ERROR] bun not found"; exit 1; }
|
||||||
|
|
||||||
pretty: ensure-bun
|
pretty: ensure-bun
|
||||||
@bun run format
|
@bun run format:src
|
||||||
|
@bun run format:stats
|
||||||
|
|
||||||
|
lint: ensure-bun
|
||||||
|
@bun run lint:stats
|
||||||
|
|
||||||
build:
|
build:
|
||||||
@printf '%s\n' "[INFO] Detected platform: $(PLATFORM)"
|
@printf '%s\n' "[INFO] Detected platform: $(PLATFORM)"
|
||||||
@case "$(PLATFORM)" in \
|
@case "$(PLATFORM)" in \
|
||||||
linux) $(MAKE) --no-print-directory build-linux ;; \
|
linux) $(MAKE) --no-print-directory build-linux ;; \
|
||||||
macos) $(MAKE) --no-print-directory build-macos ;; \
|
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 ;; \
|
*) printf '%s\n' "[ERROR] Unsupported OS for this Makefile target: $(PLATFORM)"; exit 1 ;; \
|
||||||
esac
|
esac
|
||||||
|
|
||||||
@@ -117,6 +140,7 @@ install:
|
|||||||
@case "$(PLATFORM)" in \
|
@case "$(PLATFORM)" in \
|
||||||
linux) $(MAKE) --no-print-directory install-linux ;; \
|
linux) $(MAKE) --no-print-directory install-linux ;; \
|
||||||
macos) $(MAKE) --no-print-directory install-macos ;; \
|
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 ;; \
|
*) printf '%s\n' "[ERROR] Unsupported OS for this Makefile target: $(PLATFORM)"; exit 1 ;; \
|
||||||
esac
|
esac
|
||||||
|
|
||||||
@@ -136,18 +160,20 @@ build-macos-unsigned: deps
|
|||||||
@bun run build:mac:unsigned
|
@bun run build:mac:unsigned
|
||||||
|
|
||||||
build-launcher:
|
build-launcher:
|
||||||
@printf '%s\n' "[INFO] Bundling launcher script"
|
@printf '%s\n' "[INFO] Building launcher runtime artifacts"
|
||||||
@install -d "$(dir $(LAUNCHER_OUT))"
|
@bun run build:launcher
|
||||||
@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)"
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
@printf '%s\n' "[INFO] Removing build artifacts"
|
@printf '%s\n' "[INFO] Removing build artifacts"
|
||||||
@rm -rf dist release
|
@if [ -f "$(PRERELEASE_NOTES)" ]; then \
|
||||||
|
PRERELEASE_NOTES_BACKUP="$$(mktemp -t subminer-prerelease-notes.XXXXXX)" && \
|
||||||
|
cp "$(PRERELEASE_NOTES)" "$$PRERELEASE_NOTES_BACKUP" && \
|
||||||
|
rm -rf dist release && \
|
||||||
|
install -d release && \
|
||||||
|
mv "$$PRERELEASE_NOTES_BACKUP" "$(PRERELEASE_NOTES)"; \
|
||||||
|
else \
|
||||||
|
rm -rf dist release; \
|
||||||
|
fi
|
||||||
@rm -f "$(BINDIR)/subminer" "$(BINDIR)/SubMiner.AppImage"
|
@rm -f "$(BINDIR)/subminer" "$(BINDIR)/SubMiner.AppImage"
|
||||||
|
|
||||||
generate-config: ensure-bun
|
generate-config: ensure-bun
|
||||||
@@ -155,21 +181,8 @@ generate-config: ensure-bun
|
|||||||
@bun run electron . --generate-config
|
@bun run electron . --generate-config
|
||||||
|
|
||||||
generate-example-config: ensure-bun
|
generate-example-config: ensure-bun
|
||||||
@bun run build
|
|
||||||
@bun run generate:config-example
|
@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
|
dev-start: ensure-bun
|
||||||
@bun run build
|
@bun run build
|
||||||
@bun run electron . --start
|
@bun run electron . --start
|
||||||
@@ -190,20 +203,36 @@ dev-toggle: ensure-bun
|
|||||||
dev-stop: ensure-bun
|
dev-stop: ensure-bun
|
||||||
@bun run electron . --stop
|
@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
|
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 -d "$(BINDIR)"
|
||||||
@install -m 0755 "$(LAUNCHER_OUT)" "$(BINDIR)/$(APP_NAME)"
|
@install -m 0755 "$(LAUNCHER_OUT)" "$(BINDIR)/$(APP_NAME)"
|
||||||
@install -d "$(LINUX_DATA_DIR)/themes"
|
@install -d "$(LINUX_DATA_DIR)/themes"
|
||||||
@install -m 0644 "./$(THEME_SOURCE)" "$(LINUX_DATA_DIR)/themes/$(THEME_FILE)"
|
@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 \
|
@if [ -n "$(APPIMAGE_SRC)" ]; then \
|
||||||
install -m 0755 "$(APPIMAGE_SRC)" "$(BINDIR)/SubMiner.AppImage"; \
|
install -m 0755 "$(APPIMAGE_SRC)" "$(BINDIR)/SubMiner.AppImage"; \
|
||||||
else \
|
else \
|
||||||
printf '%s\n' "[WARN] No release/SubMiner-*.AppImage found; skipping AppImage install"; \
|
printf '%s\n' "[WARN] No release/SubMiner-*.AppImage found; skipping AppImage install"; \
|
||||||
printf '%s\n' " Build one with: make build"; \
|
printf '%s\n' " Build one with: make build"; \
|
||||||
fi
|
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
|
install-macos: build-launcher
|
||||||
@printf '%s\n' "[INFO] Installing macOS wrapper/theme/app artifacts"
|
@printf '%s\n' "[INFO] Installing macOS wrapper/theme/app artifacts"
|
||||||
@@ -211,6 +240,8 @@ install-macos: build-launcher
|
|||||||
@install -m 0755 "$(LAUNCHER_OUT)" "$(BINDIR)/$(APP_NAME)"
|
@install -m 0755 "$(LAUNCHER_OUT)" "$(BINDIR)/$(APP_NAME)"
|
||||||
@install -d "$(MACOS_DATA_DIR)/themes"
|
@install -d "$(MACOS_DATA_DIR)/themes"
|
||||||
@install -m 0644 "./$(THEME_SOURCE)" "$(MACOS_DATA_DIR)/themes/$(THEME_FILE)"
|
@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)"
|
@install -d "$(MACOS_APP_DIR)"
|
||||||
@if [ -n "$(MACOS_APP_SRC)" ]; then \
|
@if [ -n "$(MACOS_APP_SRC)" ]; then \
|
||||||
rm -rf "$(MACOS_APP_DEST)"; \
|
rm -rf "$(MACOS_APP_DEST)"; \
|
||||||
@@ -226,26 +257,34 @@ install-macos: build-launcher
|
|||||||
fi
|
fi
|
||||||
@printf '%s\n' "Installed to:" " $(BINDIR)/subminer" " $(MACOS_DATA_DIR)/themes/$(THEME_FILE)" " $(MACOS_APP_DEST)"
|
@printf '%s\n' "Installed to:" " $(BINDIR)/subminer" " $(MACOS_DATA_DIR)/themes/$(THEME_FILE)" " $(MACOS_APP_DEST)"
|
||||||
|
|
||||||
install-plugin:
|
install-windows:
|
||||||
@printf '%s\n' "[INFO] Installing mpv plugin artifacts"
|
@printf '%s\n' "[INFO] Windows builds run via: bun run build:win"
|
||||||
@install -d "$(MPV_SCRIPTS_DIR)"
|
@printf '%s\n' "[INFO] SubMiner-managed mpv launches inject the bundled runtime plugin; no global mpv plugin install is needed."
|
||||||
@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"
|
|
||||||
|
|
||||||
# Uninstall behavior kept unchanged by default.
|
uninstall:
|
||||||
uninstall: uninstall-linux
|
@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:
|
uninstall-linux:
|
||||||
@rm -f "$(BINDIR)/subminer" "$(BINDIR)/SubMiner.AppImage"
|
@rm -f "$(BINDIR)/subminer" "$(BINDIR)/SubMiner.AppImage"
|
||||||
@rm -f "$(LINUX_DATA_DIR)/themes/$(THEME_FILE)"
|
@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:
|
uninstall-macos:
|
||||||
@rm -f "$(BINDIR)/subminer"
|
@rm -f "$(BINDIR)/subminer"
|
||||||
@rm -f "$(MACOS_DATA_DIR)/themes/$(THEME_FILE)"
|
@rm -f "$(MACOS_DATA_DIR)/themes/$(THEME_FILE)"
|
||||||
|
@rm -rf "$(MACOS_DATA_DIR)/plugin/subminer"
|
||||||
@rm -rf "$(MACOS_APP_DEST)"
|
@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,278 @@
|
|||||||
<div align="center">
|
<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)
|
<img src="assets/SubMiner.png" width="160" alt="SubMiner logo">
|
||||||
[]()
|
|
||||||
[](https://docs.subminer.moe)
|
# 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>
|
</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 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">
|
<div align="center">
|
||||||
|
<img src="docs-site/public/screenshots/yomitan-lookup.png" width="800" alt="Yomitan dictionary popup over annotated subtitles in mpv">
|
||||||
[](./assets/minecard.mp4)
|
|
||||||
|
|
||||||
</div>
|
</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
|
<div align="center">
|
||||||
- **Keyboard-driven lookup mode** — Navigate token-by-token, keep lookup open across tokens, and control popup scrolling/audio/mining without leaving the overlay
|
<img src="docs-site/public/screenshots/one-key-mining.png" width="800" alt="Anki card created from SubMiner with sentence, audio, and screenshot">
|
||||||
- **One-key mining** — Creates Anki cards with sentence, audio, screenshot, and translation
|
</div>
|
||||||
- **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
|
|
||||||
|
|
||||||
## 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
|
<div align="center">
|
||||||
wget https://github.com/ksyasuda/SubMiner/releases/latest/download/SubMiner.AppImage -O ~/.local/bin/SubMiner.AppImage
|
<img src="docs-site/public/screenshots/annotations.png" width="800" alt="Annotated subtitles with frequency coloring, JLPT underlines, and N+1 targets">
|
||||||
chmod +x ~/.local/bin/SubMiner.AppImage
|
</div>
|
||||||
wget https://github.com/ksyasuda/SubMiner/releases/latest/download/subminer -O ~/.local/bin/subminer
|
|
||||||
chmod +x ~/.local/bin/subminer
|
|
||||||
|
|
||||||
```
|
<br>
|
||||||
|
|
||||||
> [!NOTE]
|
### Immersion Dashboard
|
||||||
> The `subminer` wrapper uses a [Bun](https://bun.sh) shebang. Make sure `bun` is on your `PATH`.
|
|
||||||
|
|
||||||
**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
|
<br>
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Set up Yomitan Dictionaries
|
### Integrations
|
||||||
|
|
||||||
```bash
|
<table>
|
||||||
subminer app --yomitan
|
<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>
|
||||||
|
</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
|
## Requirements
|
||||||
|
|
||||||
| Required | Optional |
|
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.
|
||||||
| ------------------------------------------ | -------------------------------------------------- |
|
|
||||||
| `bun` | |
|
| Dependency | Status | What it does |
|
||||||
| `mpv` with IPC socket | `yt-dlp` |
|
| ------------------------ | ---------------- | -------------------------------------------------------------------- |
|
||||||
| `ffmpeg` | `guessit` (better AniSkip title/episode detection) |
|
| mpv | Required | The video player SubMiner draws over |
|
||||||
| `mecab` + `mecab-ipadic` | `fzf` / `rofi` |
|
| fuse2 | Required (Linux) | Running the AppImage |
|
||||||
| Linux: `hyprctl` or `xdotool` + `xwininfo` | `chafa`, `ffmpegthumbnailer` |
|
| Anki + AnkiConnect | Required to mine | Card creation from the Yomitan popup |
|
||||||
| macOS: Accessibility permission | |
|
| 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**, or the overlay cannot follow the mpv window. 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
|
## 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
|
## 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
|
## 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,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,31 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"version": "1.3.5",
|
||||||
|
"bunRevision": "1e86cebd74a5723e818b5c0555276b646bcf0e4c",
|
||||||
|
"releaseTagCommit": "fa5a5bbe556a4bda5bde77b4013aa6c3bb4ec9ab",
|
||||||
|
"artifacts": {
|
||||||
|
"darwin-arm64": {
|
||||||
|
"file": "bun-darwin-aarch64.zip",
|
||||||
|
"sha256": "db17588a4aea8804856825d4bead3f05e1f37276ca606f37e369b4f72f35d3fb"
|
||||||
|
},
|
||||||
|
"darwin-x64": {
|
||||||
|
"file": "bun-darwin-x64-baseline.zip",
|
||||||
|
"sha256": "34b9a56b851058dafa1bc9d61233f2c383aa996889bba30b3180f5ccc2cff1b2"
|
||||||
|
},
|
||||||
|
"linux-arm64": {
|
||||||
|
"file": "bun-linux-aarch64.zip",
|
||||||
|
"sha256": "ed01000f85bd97785228ad2845dc92a1860b8054856826d7317690ac8f8ee74b"
|
||||||
|
},
|
||||||
|
"linux-x64": {
|
||||||
|
"file": "bun-linux-x64-baseline.zip",
|
||||||
|
"sha256": "6bddacd6a65855698b9816f2d74871eda4dd0b7fa921140c6445248f94a742fd"
|
||||||
|
},
|
||||||
|
"win32-x64": {
|
||||||
|
"file": "bun-windows-x64-baseline.zip",
|
||||||
|
"sha256": "bf447dcc3b06aba9b9706a9db46fcd65e06a4d47d31439922313825d06eb47ca"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"licenseInventoryStatus": "source-and-notices-pinned-to-binary-revision",
|
||||||
|
"sourceManifest": "build/bun-source-manifest.json",
|
||||||
|
"correspondingSourceAsset": "bun-v1.3.5-source.tar.gz"
|
||||||
|
}
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"version": "1.3.5",
|
||||||
|
"releaseTag": "bun-v1.3.5",
|
||||||
|
"releaseTagCommit": "fa5a5bbe556a4bda5bde77b4013aa6c3bb4ec9ab",
|
||||||
|
"bunRevision": "1e86cebd74a5723e818b5c0555276b646bcf0e4c",
|
||||||
|
"archiveName": "bun-v1.3.5-source.tar.gz",
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"name": "bun",
|
||||||
|
"repository": "oven-sh/bun",
|
||||||
|
"revision": "1e86cebd74a5723e818b5c0555276b646bcf0e4c",
|
||||||
|
"destination": "bun",
|
||||||
|
"sha256": "3a766087902a62a4920e5ac5452dbc7143ff97ea19721f945146629fc9f2d82c",
|
||||||
|
"licensePaths": ["LICENSE.md"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "WebKit",
|
||||||
|
"repository": "oven-sh/WebKit",
|
||||||
|
"revision": "6d0f3aac0b817cc01a846b3754b21271adedac12",
|
||||||
|
"destination": "bun/vendor/WebKit",
|
||||||
|
"transport": "git-sparse",
|
||||||
|
"exclude": ["JSTests", "LayoutTests", "ManualTests", "PerformanceTests", "WebDriverTests"],
|
||||||
|
"licensePaths": ["Source/JavaScriptCore/COPYING.LIB"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "boringssl",
|
||||||
|
"repository": "oven-sh/boringssl",
|
||||||
|
"revision": "f1ffd9e83d4f5c28a9c70d73f9a4e6fcf310062f",
|
||||||
|
"destination": "bun/vendor/boringssl",
|
||||||
|
"sha256": "af8fd325793bb261c70114d09a24f36092fbe252be8ec040f0054ec9455f0ea3",
|
||||||
|
"licensePaths": ["LICENSE"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "brotli",
|
||||||
|
"repository": "google/brotli",
|
||||||
|
"revision": "ed738e842d2fbdf2d6459e39267a633c4a9b2f5d",
|
||||||
|
"upstreamReference": "v1.1.0",
|
||||||
|
"destination": "bun/vendor/brotli",
|
||||||
|
"sha256": "aaa739962a45b508b2e783b915e6b2b57ed3b12bd4b0feac73acfb144dffa54f",
|
||||||
|
"licensePaths": ["LICENSE"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cares",
|
||||||
|
"repository": "c-ares/c-ares",
|
||||||
|
"revision": "3ac47ee46edd8ea40370222f91613fc16c434853",
|
||||||
|
"destination": "bun/vendor/cares",
|
||||||
|
"sha256": "8c94116cb366ae4a44e487da4d9f7e736287d329efa6f88fdf077cd2d0a2e4b8",
|
||||||
|
"licensePaths": ["LICENSE.md"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "hdrhistogram",
|
||||||
|
"repository": "HdrHistogram/HdrHistogram_c",
|
||||||
|
"revision": "be60a9987ee48d0abf0d7b6a175bad8d6c1585d1",
|
||||||
|
"destination": "bun/vendor/hdrhistogram",
|
||||||
|
"sha256": "811c5e5ae5303a75ade50688880af6aad5d2f951ec5785f68186bd18635cdfc9",
|
||||||
|
"licensePaths": ["COPYING.txt", "LICENSE.txt"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "highway",
|
||||||
|
"repository": "google/highway",
|
||||||
|
"revision": "ac0d5d297b13ab1b89f48484fc7911082d76a93f",
|
||||||
|
"destination": "bun/vendor/highway",
|
||||||
|
"sha256": "a7a816f4b62a0414ff0d39c0a8875847468dd6f7c9ad71781a24469a756cdea7",
|
||||||
|
"licensePaths": ["LICENSE"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "libarchive",
|
||||||
|
"repository": "libarchive/libarchive",
|
||||||
|
"revision": "9525f90ca4bd14c7b335e2f8c84a4607b0af6bdf",
|
||||||
|
"destination": "bun/vendor/libarchive",
|
||||||
|
"sha256": "944db9ab58a3cbdb5d947db4f04a3cc15f83b3147fcf7830ada592ba8d4a102c",
|
||||||
|
"licensePaths": ["COPYING"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "libdeflate",
|
||||||
|
"repository": "ebiggers/libdeflate",
|
||||||
|
"revision": "c8c56a20f8f621e6a966b716b31f1dedab6a41e3",
|
||||||
|
"destination": "bun/vendor/libdeflate",
|
||||||
|
"sha256": "1e5cc06bdbf3e1245d8b89c9e3588f507e3c8bc53fe8b8229770a9e8661dea81",
|
||||||
|
"licensePaths": ["COPYING"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "libuv",
|
||||||
|
"repository": "libuv/libuv",
|
||||||
|
"revision": "f3ce527ea940d926c40878ba5de219640c362811",
|
||||||
|
"destination": "bun/vendor/libuv",
|
||||||
|
"sha256": "46040d51e8aa86a7c84e377e224d427c35022ec6e3ed8ed399493b0d8574d0cf",
|
||||||
|
"licensePaths": ["LICENSE"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lolhtml",
|
||||||
|
"repository": "cloudflare/lol-html",
|
||||||
|
"revision": "d64457d9ff0143deef025d5df7e8586092b9afb7",
|
||||||
|
"destination": "bun/vendor/lolhtml",
|
||||||
|
"sha256": "893b77b460f4c4f4634c973f72cf35e38ebe30646d14a92db3f50ee7e586c0ad",
|
||||||
|
"licensePaths": ["LICENSE"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lshpack",
|
||||||
|
"repository": "litespeedtech/ls-hpack",
|
||||||
|
"revision": "8905c024b6d052f083a3d11d0a169b3c2735c8a1",
|
||||||
|
"destination": "bun/vendor/lshpack",
|
||||||
|
"sha256": "07d8bf901bb1b15543f38eabd23938519e1210eebadb52f3d651d6ef130ef973",
|
||||||
|
"licensePaths": ["LICENSE"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mimalloc",
|
||||||
|
"repository": "oven-sh/mimalloc",
|
||||||
|
"revision": "1beadf9651a7bfdec6b5367c380ecc3fe1c40d1a",
|
||||||
|
"destination": "bun/vendor/mimalloc",
|
||||||
|
"sha256": "317ec2a83462ece78c344c4955c6fee103b412a8e6a0d6ddf9ec8963ed9c0881",
|
||||||
|
"licensePaths": ["LICENSE"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "picohttpparser",
|
||||||
|
"repository": "h2o/picohttpparser",
|
||||||
|
"revision": "066d2b1e9ab820703db0837a7255d92d30f0c9f5",
|
||||||
|
"destination": "bun/vendor/picohttpparser",
|
||||||
|
"sha256": "637ff2ab6f5c7f7e05a5b5dc393d5cf2fea8d4754fcaceaaf935ffff5c1323ee",
|
||||||
|
"licensePaths": ["picohttpparser.c", "picohttpparser.h"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tinycc",
|
||||||
|
"repository": "oven-sh/tinycc",
|
||||||
|
"revision": "29985a3b59898861442fa3b43f663fc1af2591d7",
|
||||||
|
"destination": "bun/vendor/tinycc",
|
||||||
|
"sha256": "813cc09aafd6cea9c1ae6b745781e6b27576a0c103f49b63b5c8a732bbe3d290",
|
||||||
|
"licensePaths": ["COPYING"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "zlib",
|
||||||
|
"repository": "cloudflare/zlib",
|
||||||
|
"revision": "886098f3f339617b4243b286f5ed364b9989e245",
|
||||||
|
"destination": "bun/vendor/zlib",
|
||||||
|
"sha256": "14bf449df8308696af52f87de88f54c9eb9c91ec5e280587f9d37f7a3f6ed9bb",
|
||||||
|
"licensePaths": ["LICENSE"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "zstd",
|
||||||
|
"repository": "facebook/zstd",
|
||||||
|
"revision": "f8745da6ff1ad1e7bab384bd1f9d742439278e99",
|
||||||
|
"destination": "bun/vendor/zstd",
|
||||||
|
"sha256": "4b0bd1f0cfb25e61b9103c35f27395530ff5b4c0d2513a00fd745849e85ea52c",
|
||||||
|
"licensePaths": ["COPYING", "LICENSE"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
!include "MUI2.nsh"
|
||||||
|
!include "nsDialogs.nsh"
|
||||||
|
|
||||||
|
Var WindowsMpvShortcutStartMenuPath
|
||||||
|
Var WindowsMpvShortcutDesktopPath
|
||||||
|
|
||||||
|
!macro ResolveWindowsMpvShortcutPaths
|
||||||
|
!ifdef MENU_FILENAME
|
||||||
|
StrCpy $WindowsMpvShortcutStartMenuPath "$SMPROGRAMS\${MENU_FILENAME}\SubMiner mpv.lnk"
|
||||||
|
!else
|
||||||
|
StrCpy $WindowsMpvShortcutStartMenuPath "$SMPROGRAMS\SubMiner mpv.lnk"
|
||||||
|
!endif
|
||||||
|
StrCpy $WindowsMpvShortcutDesktopPath "$DESKTOP\SubMiner mpv.lnk"
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!ifndef BUILD_UNINSTALLER
|
||||||
|
Var WindowsMpvShortcutStartMenuCheckbox
|
||||||
|
Var WindowsMpvShortcutDesktopCheckbox
|
||||||
|
Var WindowsMpvShortcutStartMenuEnabled
|
||||||
|
Var WindowsMpvShortcutDesktopEnabled
|
||||||
|
Var WindowsMpvShortcutDefaultsInitialized
|
||||||
|
|
||||||
|
!macro customInit
|
||||||
|
StrCpy $WindowsMpvShortcutStartMenuEnabled "1"
|
||||||
|
StrCpy $WindowsMpvShortcutDesktopEnabled "1"
|
||||||
|
StrCpy $WindowsMpvShortcutDefaultsInitialized "0"
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!macro customPageAfterChangeDir
|
||||||
|
PageEx custom
|
||||||
|
PageCallbacks WindowsMpvShortcutPageCreate WindowsMpvShortcutPageLeave
|
||||||
|
Caption " "
|
||||||
|
PageExEnd
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
Function HasExistingInstallation
|
||||||
|
ReadRegStr $0 SHELL_CONTEXT "Software\${APP_GUID}" InstallLocation
|
||||||
|
${if} $0 == ""
|
||||||
|
Push "0"
|
||||||
|
${else}
|
||||||
|
Push "1"
|
||||||
|
${endif}
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
Function InitializeWindowsMpvShortcutDefaults
|
||||||
|
${if} $WindowsMpvShortcutDefaultsInitialized == "1"
|
||||||
|
Return
|
||||||
|
${endif}
|
||||||
|
|
||||||
|
!insertmacro ResolveWindowsMpvShortcutPaths
|
||||||
|
Call HasExistingInstallation
|
||||||
|
Pop $0
|
||||||
|
|
||||||
|
${if} $0 == "1"
|
||||||
|
${if} ${FileExists} "$WindowsMpvShortcutStartMenuPath"
|
||||||
|
StrCpy $WindowsMpvShortcutStartMenuEnabled "1"
|
||||||
|
${else}
|
||||||
|
StrCpy $WindowsMpvShortcutStartMenuEnabled "0"
|
||||||
|
${endif}
|
||||||
|
|
||||||
|
${if} ${FileExists} "$WindowsMpvShortcutDesktopPath"
|
||||||
|
StrCpy $WindowsMpvShortcutDesktopEnabled "1"
|
||||||
|
${else}
|
||||||
|
StrCpy $WindowsMpvShortcutDesktopEnabled "0"
|
||||||
|
${endif}
|
||||||
|
${else}
|
||||||
|
StrCpy $WindowsMpvShortcutStartMenuEnabled "1"
|
||||||
|
StrCpy $WindowsMpvShortcutDesktopEnabled "1"
|
||||||
|
${endif}
|
||||||
|
|
||||||
|
StrCpy $WindowsMpvShortcutDefaultsInitialized "1"
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
Function WindowsMpvShortcutPageCreate
|
||||||
|
Call InitializeWindowsMpvShortcutDefaults
|
||||||
|
|
||||||
|
!insertmacro MUI_HEADER_TEXT "Windows mpv launcher" "Choose where to create the optional SubMiner mpv shortcuts."
|
||||||
|
|
||||||
|
nsDialogs::Create 1018
|
||||||
|
Pop $0
|
||||||
|
|
||||||
|
${NSD_CreateLabel} 0u 0u 300u 30u "SubMiner mpv launches SubMiner.exe --launch-mpv so people can open mpv with the SubMiner profile from a separate Windows shortcut."
|
||||||
|
Pop $0
|
||||||
|
|
||||||
|
${NSD_CreateCheckbox} 0u 44u 280u 12u "Create Start Menu shortcut"
|
||||||
|
Pop $WindowsMpvShortcutStartMenuCheckbox
|
||||||
|
${if} $WindowsMpvShortcutStartMenuEnabled == "1"
|
||||||
|
${NSD_Check} $WindowsMpvShortcutStartMenuCheckbox
|
||||||
|
${endif}
|
||||||
|
|
||||||
|
${NSD_CreateCheckbox} 0u 64u 280u 12u "Create Desktop shortcut"
|
||||||
|
Pop $WindowsMpvShortcutDesktopCheckbox
|
||||||
|
${if} $WindowsMpvShortcutDesktopEnabled == "1"
|
||||||
|
${NSD_Check} $WindowsMpvShortcutDesktopCheckbox
|
||||||
|
${endif}
|
||||||
|
|
||||||
|
${NSD_CreateLabel} 0u 90u 300u 24u "Upgrades preserve the current SubMiner mpv shortcut locations instead of recreating shortcuts you already removed."
|
||||||
|
Pop $0
|
||||||
|
|
||||||
|
nsDialogs::Show
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
Function WindowsMpvShortcutPageLeave
|
||||||
|
${NSD_GetState} $WindowsMpvShortcutStartMenuCheckbox $0
|
||||||
|
${if} $0 == ${BST_CHECKED}
|
||||||
|
StrCpy $WindowsMpvShortcutStartMenuEnabled "1"
|
||||||
|
${else}
|
||||||
|
StrCpy $WindowsMpvShortcutStartMenuEnabled "0"
|
||||||
|
${endif}
|
||||||
|
|
||||||
|
${NSD_GetState} $WindowsMpvShortcutDesktopCheckbox $0
|
||||||
|
${if} $0 == ${BST_CHECKED}
|
||||||
|
StrCpy $WindowsMpvShortcutDesktopEnabled "1"
|
||||||
|
${else}
|
||||||
|
StrCpy $WindowsMpvShortcutDesktopEnabled "0"
|
||||||
|
${endif}
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
!macro customInstall
|
||||||
|
Call InitializeWindowsMpvShortcutDefaults
|
||||||
|
!insertmacro ResolveWindowsMpvShortcutPaths
|
||||||
|
|
||||||
|
${if} $WindowsMpvShortcutStartMenuEnabled == "1"
|
||||||
|
!ifdef MENU_FILENAME
|
||||||
|
CreateDirectory "$SMPROGRAMS\${MENU_FILENAME}"
|
||||||
|
!endif
|
||||||
|
CreateShortCut "$WindowsMpvShortcutStartMenuPath" "$appExe" "--launch-mpv" "$appExe" 0 "" "" "Launch mpv with the SubMiner profile"
|
||||||
|
# electron-builder's upstream NSIS templates use the same WinShell call for AppUserModelID wiring.
|
||||||
|
# WinShell.dll comes from electron-builder's cached nsis-resources bundle, so bun run build:win needs no extra repo-local setup.
|
||||||
|
ClearErrors
|
||||||
|
WinShell::SetLnkAUMI "$WindowsMpvShortcutStartMenuPath" "${APP_ID}"
|
||||||
|
${else}
|
||||||
|
Delete "$WindowsMpvShortcutStartMenuPath"
|
||||||
|
${endif}
|
||||||
|
|
||||||
|
${if} $WindowsMpvShortcutDesktopEnabled == "1"
|
||||||
|
CreateShortCut "$WindowsMpvShortcutDesktopPath" "$appExe" "--launch-mpv" "$appExe" 0 "" "" "Launch mpv with the SubMiner profile"
|
||||||
|
# ClearErrors keeps the optional AUMI assignment non-fatal if the packaging environment is missing WinShell.
|
||||||
|
ClearErrors
|
||||||
|
WinShell::SetLnkAUMI "$WindowsMpvShortcutDesktopPath" "${APP_ID}"
|
||||||
|
${else}
|
||||||
|
Delete "$WindowsMpvShortcutDesktopPath"
|
||||||
|
${endif}
|
||||||
|
|
||||||
|
System::Call 'Shell32::SHChangeNotify(i 0x8000000, i 0, i 0, i 0)'
|
||||||
|
!macroend
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!macro customUnInstall
|
||||||
|
!insertmacro ResolveWindowsMpvShortcutPaths
|
||||||
|
Delete "$WindowsMpvShortcutStartMenuPath"
|
||||||
|
Delete "$WindowsMpvShortcutDesktopPath"
|
||||||
|
!macroend
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<artifact-configuration xmlns="http://signpath.io/artifact-configuration/v1">
|
||||||
|
<zip-file>
|
||||||
|
<pe-file path="SubMiner-*.exe" max-matches="unbounded">
|
||||||
|
<authenticode-sign />
|
||||||
|
</pe-file>
|
||||||
|
<zip-file path="SubMiner-*.zip" max-matches="unbounded">
|
||||||
|
<directory path="*">
|
||||||
|
<pe-file path="*.exe" max-matches="unbounded">
|
||||||
|
<authenticode-sign />
|
||||||
|
</pe-file>
|
||||||
|
<pe-file path="*.dll" max-matches="unbounded">
|
||||||
|
<authenticode-sign />
|
||||||
|
</pe-file>
|
||||||
|
<pe-file path="*.node" max-matches="unbounded">
|
||||||
|
<authenticode-sign />
|
||||||
|
</pe-file>
|
||||||
|
</directory>
|
||||||
|
</zip-file>
|
||||||
|
</zip-file>
|
||||||
|
</artifact-configuration>
|
||||||
@@ -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 `release/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 `release/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,4 @@
|
|||||||
|
type: internal
|
||||||
|
area: testing
|
||||||
|
|
||||||
|
- Added a headless end-to-end harness (`bun run test:e2e`, `bun run e2e`) that boots the built app with a real mpv, a synthetic clip, a fixture Yomitan dictionary, and a fake AnkiConnect on an offscreen display.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
type: fixed
|
||||||
|
area: anki
|
||||||
|
|
||||||
|
- Media timing review now keeps playback paused while it is open when the dictionary popup or subtitle hover pause ends, as with Hachidori popups closing when the review appears. Playback resumes after the review closes unless a dictionary popup is still open.
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
type: added
|
||||||
|
area: dictionary
|
||||||
|
|
||||||
|
- Added a bundled Hachidori dictionary backend alongside the default Yomitan backend. Select it with `dictionaryBackend` and restart SubMiner.
|
||||||
|
- The tray and dictionary-settings shortcut follow the selected backend. `--hachidori` opens Hachidori settings, while `--yomitan` opens Yomitan settings unless a read-only external Yomitan profile is configured. This restriction also applies with Hachidori active, without blocking Hachidori settings.
|
||||||
|
- Hachidori integrates with subtitle scanning, popup controls, lookup tracking, character dictionaries, and Anki media enrichment, with separate dictionaries and settings for each backend. Character dictionaries reach a linked host over the link (Hachidori 0.2.3 or later, or a current Docker host), retry busy imports, and replace the previous dictionary only after a successful import. Older hosts report where to import the dictionary manually.
|
||||||
|
- Hachidori auto-populates its first Anki template from SubMiner's deck, tags (kept alongside its own `hachidori` tag), and field mappings, detects an unambiguous matching note type, and preserves existing custom templates apart from the deck, which follows `ankiConnect.deck` so polling mode enriches Hachidori cards. Anki discovery retries after an unavailable connection.
|
||||||
|
- Hachidori saves downloadable word audio before sending a note through SubMiner's Anki proxy, so freshly mined animated cards include the word-audio delay.
|
||||||
|
- Both bundled dictionary backends reload their background code on startup so extension updates take effect while preserving installed dictionaries and settings. Failed Yomitan connection-setting updates can be retried without manually resetting the managed Anki endpoint.
|
||||||
|
- First-run setup remembers each backend that finished it, including when reopened for legacy plugin cleanup, so switching back does not repeat setup, and the launcher gates playback on the backend the running app started with. Current incomplete or cancelled setup takes precedence over stale completion history. A running Yomitan session keeps using its external profile until it restarts into Hachidori.
|
||||||
|
- Stats dashboard mining and deck lookup use the selected backend, with the Anki proxy enabled or disabled. Dashboard cards retain the selected history line and media instead of being processed again with the current mpv subtitle, with both Yomitan and Hachidori. Stats cards carry a `SubMiner::Stats` tag so polling also preserves their context. Hachidori word mining supplies dictionary aliases, IDs, and frequency metadata to its native Anki renderer. Settings labels for popup pause and the dictionary deck no longer name Yomitan, and the backend selector sits with the other dictionary settings.
|
||||||
|
- Hachidori scans, dictionary counts, and settings reads wait for the dictionary engine to finish loading or importing instead of caching empty results.
|
||||||
|
- Character dictionary imports report completion as soon as the archive is read instead of waiting out the temporary server's keep-alive timeout.
|
||||||
|
- First-run setup can link an external Hachidori dictionary host in an app, browser, or Docker container, verify its library, and unlink back to local dictionaries. The optional host controls are collapsed by default and explain which apps or containers must stay running. Unresponsive connection checks time out so setup remains usable. Anki mining, media enrichment, and custom toolbar buttons stay in SubMiner, including when editing buttons while linked.
|
||||||
|
- Hachidori frequency highlighting reuses dictionary-entry ranks and fills missing ranks through its existing API. Entries without a matching definition may remain unranked. The bundled integration is maintained in a pinned fork submodule using upstream HoshiDicts and WASM binaries.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
type: docs
|
||||||
|
area: dictionary
|
||||||
|
|
||||||
|
- Documented backend selection, separate Hachidori dictionary and Anki setup, the settings flags and shortcuts, and external host linking with local Anki mining and the running requirements for each host type.
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
type: fixed
|
||||||
|
area: jellyfin
|
||||||
|
|
||||||
|
- Fixed `subminer jellyfin -p` reporting "No Jellyfin libraries found." when SubMiner was already running (for example right after `subminer jellyfin` login). The app now hands library, item, and preview-auth results to the launcher directly instead of through log files, so picker image previews work again and real errors (expired session, unreachable server) are shown instead of an empty list.
|
||||||
|
- The rofi "Jellyfin Search (optional)" step after picking a library is now a compact input box with a "Type to search, or press Enter to browse all" hint instead of a blank list.
|
||||||
|
- Playing from the terminal (`subminer jellyfin -p`) now reports watch progress to Jellyfin even when cast discovery is off, so Jellyfin remembers where you stopped and marks finished episodes as played. Previously progress was only sent while the cast connection was active.
|
||||||
|
- When `subminer jellyfin -p` has to start SubMiner itself, the app now shows its tray icon and quits when you close mpv, the same as playing a local file. Before, it had no tray icon and kept running hidden after playback. A SubMiner that was already running is still reused and left running.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
type: docs
|
||||||
|
area: jellyfin
|
||||||
|
|
||||||
|
- Corrected the Jellyfin docs: SubMiner only joins the cast menu at startup when `startupWarmups.jellyfinRemoteSession` is on (off by default) along with `jellyfin.remoteControlAutoConnect`. Otherwise tick **Jellyfin Discovery** in the tray.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
type: added
|
||||||
|
area: subtitles
|
||||||
|
|
||||||
|
- Japanese subtitle generation now works while playing a Jellyfin episode. SubMiner reads the original file from the server, saves the subtitles to a cache folder, and loads them into mpv. Direct-play and transcoded playback are both supported.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
type: fixed
|
||||||
|
area: overlay
|
||||||
|
|
||||||
|
- Keyboard-only mode no longer swallows `h`, `l`, and the left/right arrows while a modal is open or a text field has focus, so typing into fields like the Jimaku or TsukiHime title works and no longer seeks mpv. Pressing `Ctrl/Cmd+Shift+Y` (or the controller toggle) inside a modal no longer turns on a hidden keyboard-only mode in the modal window.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
type: fixed
|
||||||
|
area: setup
|
||||||
|
|
||||||
|
- Fixed the command-line launcher showing "Not on PATH" on macOS when SubMiner was opened from Finder or the Dock, even though the launcher directory was already on your terminal PATH.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
type: fixed
|
||||||
|
area: overlay
|
||||||
|
|
||||||
|
- Overlay modals such as the media timing review open on the first attempt when their dedicated window is created on demand, instead of after a four-second retry.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
type: fixed
|
||||||
|
area: annotations
|
||||||
|
|
||||||
|
- Subtitle lines shown while two cues overlap (common in Jimaku and Jellyfin SRTs, and usually the long lines) are now prefetched, so their annotations appear immediately instead of after a delay.
|
||||||
|
- Subtitle prefetching no longer restarts each time the same subtitle file is re-selected during startup, and a line tokenized before a restart or seek is kept instead of discarded.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
type: docs
|
||||||
|
area: launcher
|
||||||
|
|
||||||
|
- Removed `SUBMINER_JIMAKU_API_KEY`, `SUBMINER_JIMAKU_API_KEY_COMMAND`, and `SUBMINER_JIMAKU_API_BASE_URL` from the development docs' environment variable table. The launcher never used them; set the Jimaku key with `jimaku.apiKey` / `jimaku.apiKeyCommand` in the config instead.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
type: internal
|
||||||
|
area: cleanup
|
||||||
|
|
||||||
|
- Removed dead code: the unused launcher Jimaku client (the `SUBMINER_JIMAKU_*` env vars it read were documented but had no effect), the mpv plugin's hover-highlight module (no sender since Overlay 2.0; custom mpv scripts that still send `subminer-hover-token` or `yomipv-hover-token` are now ignored), the never-read `anilist.characterDictionary.refreshTtlHours` / `evictionPolicy` config keys, unused root dependencies (eslint, geist fonts), and a committed draft plan doc.
|
||||||
|
- Typecheck now runs with `noUnusedLocals`; removed the unused locals, imports, and the Windows overlay focus-handoff values they fed, which were computed but never used.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
type: internal
|
||||||
|
area: overlay
|
||||||
|
|
||||||
|
- Removed 16 overlay preload/IPC methods that nothing called, plus their main-process handlers. SubMiner no longer observes mpv's `sub-text/ass` or sends every ASS line to overlay windows that never listened.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
type: fixed
|
||||||
|
area: setup
|
||||||
|
|
||||||
|
- First-run setup now labels a missing command-line launcher "Not set up" instead of "Not installed", so the AppImage catalog test no longer reads the setup window as an error screen.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
type: fixed
|
||||||
|
area: overlay
|
||||||
|
|
||||||
|
- The open subtitle sidebar no longer re-fetches the whole subtitle list and re-queries mpv about 12 times a second. It polls only the current time and line, and reloads the list when the subtitle track, file, or parsed cues change (plus a slow ~2 s fallback), cutting idle sidebar IPC and renderer work during playback.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
type: fixed
|
||||||
|
area: stats
|
||||||
|
|
||||||
|
- Subtitle lines recorded before halfwidth katakana widening now show fullwidth katakana in the stats dashboard (word/kanji occurrences, sentence search), and cards mined from stats history get the fullwidth sentence and word.
|
||||||
|
- Sentence search matches katakana in either width, so searching アンタ also finds older lines stored as アンタ.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
type: fixed
|
||||||
|
area: overlay
|
||||||
|
|
||||||
|
- Hyprland and Sway window tracking no longer freezes the app when `hyprctl` or `swaymsg` is slow or hangs: polls now run in the background with a timeout and no longer pile up.
|
||||||
|
- The Hyprland/Sway trackers read each mpv process's command line once instead of spawning `ps` on every poll.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
type: fixed
|
||||||
|
area: overlay
|
||||||
|
|
||||||
|
- Halfwidth katakana in subtitles (common in broadcast-derived Japanese subs, e.g. アンタ, ダキニ) now displays as normal fullwidth katakana, so lines no longer mix squashed and normal-width text. Mined sentences and stats get the fullwidth form too.
|
||||||
@@ -2,22 +2,58 @@
|
|||||||
* SubMiner Example Configuration File
|
* SubMiner Example Configuration File
|
||||||
*
|
*
|
||||||
* This file is auto-generated from src/config/definitions.ts.
|
* This file is auto-generated from src/config/definitions.ts.
|
||||||
* Copy to $XDG_CONFIG_HOME/SubMiner/config.jsonc (or ~/.config/SubMiner/config.jsonc) and edit as needed.
|
* Copy to %APPDATA%/SubMiner/config.jsonc on Windows, or $XDG_CONFIG_HOME/SubMiner/config.jsonc (or ~/.config/SubMiner/config.jsonc) on Linux/macOS.
|
||||||
*/
|
*/
|
||||||
{
|
{
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// Overlay Auto-Start
|
// Dictionary Backend
|
||||||
// When overlay connects to mpv, automatically show overlay and hide mpv subtitles.
|
// Select the dictionary lookup backend: yomitan or hachidori.
|
||||||
|
// Restart SubMiner after changing the backend. Each backend keeps separate settings and dictionaries.
|
||||||
// ==========================================
|
// ==========================================
|
||||||
"auto_start_overlay": false, // When overlay connects to mpv, automatically show overlay and hide mpv subtitles. Values: true | false
|
"dictionaryBackend": "yomitan", // Dictionary lookup backend. Restart SubMiner after changing this setting. Values: yomitan | hachidori
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// Subtitle Selection
|
||||||
|
// Select primary and secondary mpv subtitle tracks from the overlay.
|
||||||
|
// Hot-reload: enabling or disabling updates the session shortcut immediately.
|
||||||
|
// ==========================================
|
||||||
|
"subtitleSelection": {
|
||||||
|
"enabled": false // Use the SubMiner modal to select primary and secondary subtitle tracks. When enabled, its shortcut overrides mpv subtitle selection. Values: true | false
|
||||||
|
}, // Select primary and secondary mpv subtitle tracks from the overlay.
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// Japanese Subtitle Generation
|
||||||
|
// Generate timed Japanese subtitles from local audio using whisper.cpp.
|
||||||
|
// Configure an existing GGML model path or explicitly download a SubMiner-managed model.
|
||||||
|
// Hot-reload: settings apply to the next generation or model download.
|
||||||
|
// ==========================================
|
||||||
|
"subtitleGeneration": {
|
||||||
|
"whisperPath": "", // Optional path override for whisper.cpp. Leave empty to find whisper-cli on PATH.
|
||||||
|
"modelPath": "", // Path to an existing multilingual whisper.cpp GGML model. Leave empty to use a SubMiner-managed model. A configured path always takes precedence.
|
||||||
|
"managedModel": "small", // Multilingual whisper.cpp model to use when modelPath is empty. Download it explicitly from the generation modal or launcher. Values: tiny | tiny-q5_1 | tiny-q8_0 | base | base-q5_1 | base-q8_0 | small | small-q5_1 | small-q8_0 | medium | medium-q5_0 | medium-q8_0 | large-v1 | large-v2 | large-v2-q5_0 | large-v2-q8_0 | large-v3 | large-v3-q5_0 | large-v3-turbo | large-v3-turbo-q5_0 | large-v3-turbo-q8_0
|
||||||
|
"threads": 4, // Positive integer CPU thread count for whisper.cpp Japanese transcription.
|
||||||
|
"ffmpegPath": "", // Optional FFmpeg path override for audio extraction. Leave empty to find ffmpeg on PATH.
|
||||||
|
"ffprobePath": "", // Optional FFprobe path override for audio tracks and timing. Leave empty to find ffprobe on PATH.
|
||||||
|
"vadModelPath": "", // Path to a whisper.cpp Silero VAD model. Enables dialogue-focused generation while retaining uncertain audible sections, which may include songs. Leave empty to transcribe the full audio.
|
||||||
|
"vadPath": "" // Optional speech detector executable override. With vadModelPath configured, leave empty to find whisper-vad-speech-segments or vad-speech-segments on PATH.
|
||||||
|
}, // Generate timed Japanese subtitles from local audio using whisper.cpp.
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// Visible Overlay Auto-Start
|
||||||
|
// Show the visible subtitle overlay automatically after managed mpv playback starts SubMiner.
|
||||||
|
// SubMiner can still auto-start in the background when this is false.
|
||||||
|
// ==========================================
|
||||||
|
"auto_start_overlay": true, // Show the visible subtitle overlay automatically when the bundled mpv plugin starts SubMiner. Values: true | false
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// Texthooker Server
|
// Texthooker Server
|
||||||
// Control whether browser opens automatically for texthooker.
|
// Configure texthooker startup launch and browser opening behavior.
|
||||||
// ==========================================
|
// ==========================================
|
||||||
"texthooker": {
|
"texthooker": {
|
||||||
"openBrowser": true, // Open browser setting. Values: true | false
|
"launchAtStartup": false, // Launch texthooker server automatically when SubMiner starts. Values: true | false
|
||||||
}, // Control whether browser opens automatically for texthooker.
|
"openBrowser": false // Open the texthooker page in the default browser when the server starts. Values: true | false
|
||||||
|
}, // Configure texthooker startup launch and browser opening behavior.
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// WebSocket Server
|
// WebSocket Server
|
||||||
@@ -25,18 +61,162 @@
|
|||||||
// Auto mode disables built-in server if mpv_websocket is detected.
|
// Auto mode disables built-in server if mpv_websocket is detected.
|
||||||
// ==========================================
|
// ==========================================
|
||||||
"websocket": {
|
"websocket": {
|
||||||
"enabled": "auto", // Built-in subtitle websocket server mode. Values: auto | true | false
|
"enabled": false, // Built-in subtitle websocket server mode. Values: auto | true | false
|
||||||
"port": 6677, // Built-in subtitle websocket server port.
|
"port": 6677 // Built-in subtitle websocket server port.
|
||||||
}, // Built-in WebSocket server broadcasts subtitle text to connected clients.
|
}, // Built-in WebSocket server broadcasts subtitle text to connected clients.
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// Annotation WebSocket
|
||||||
|
// Dedicated annotated subtitle websocket for bundled texthooker and token-aware clients.
|
||||||
|
// Independent from websocket.auto and defaults to port 6678.
|
||||||
|
// ==========================================
|
||||||
|
"annotationWebsocket": {
|
||||||
|
"enabled": false, // Annotated subtitle websocket server enabled state. Values: true | false
|
||||||
|
"port": 6678 // Annotated subtitle websocket server port.
|
||||||
|
}, // Dedicated annotated subtitle websocket for bundled texthooker and token-aware clients.
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// Logging
|
// Logging
|
||||||
// Controls logging verbosity.
|
// Controls logging verbosity.
|
||||||
// Set to debug for full runtime diagnostics.
|
// Set to debug for full runtime diagnostics.
|
||||||
|
// Hot-reload: logging.level and logging.files apply live while SubMiner is running.
|
||||||
// ==========================================
|
// ==========================================
|
||||||
"logging": {
|
"logging": {
|
||||||
"level": "info", // Minimum log level for runtime logging. Values: debug | info | warn | error
|
"level": "warn", // Minimum log level for runtime logging. Values: debug | info | warn | error
|
||||||
}, // Controls logging verbosity. Keep this as an object; do not replace with a bare string.
|
"rotation": 7, // Number of days of app, launcher, and mpv logs to retain.
|
||||||
|
"files": {
|
||||||
|
"app": true, // Write SubMiner app runtime logs. Values: true | false
|
||||||
|
"launcher": true, // Write launcher command logs. Values: true | false
|
||||||
|
"mpv": false // Write mpv player logs. Enable temporarily when debugging mpv/plugin startup. Values: true | false
|
||||||
|
} // Files setting.
|
||||||
|
}, // Controls logging verbosity.
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// Controller Support
|
||||||
|
// Gamepad support for the visible overlay while keyboard-only mode is active.
|
||||||
|
// Use Alt+C to pick a preferred controller and remap actions inline with learn mode.
|
||||||
|
// Trigger input mode can be auto, digital-only, or analog-thresholded depending on the controller.
|
||||||
|
// Override controller.buttonIndices when your pad reports non-standard raw button numbers.
|
||||||
|
// ==========================================
|
||||||
|
"controller": {
|
||||||
|
"enabled": false, // Enable overlay controller support through the Chrome Gamepad API. Values: true | false
|
||||||
|
"preferredGamepadId": "", // Preferred controller id saved from the controller config modal.
|
||||||
|
"preferredGamepadLabel": "", // Preferred controller display label saved for diagnostics.
|
||||||
|
"smoothScroll": true, // Use smooth scrolling for controller-driven popup scroll input. Values: true | false
|
||||||
|
"scrollPixelsPerSecond": 900, // Base popup scroll speed for controller stick input.
|
||||||
|
"horizontalJumpPixels": 160, // Popup page-jump distance for controller jump input.
|
||||||
|
"stickDeadzone": 0.2, // Deadzone applied to controller stick axes.
|
||||||
|
"triggerInputMode": "auto", // How controller triggers are interpreted: auto, pressed-only, or thresholded analog. Values: auto | digital | analog
|
||||||
|
"triggerDeadzone": 0.5, // Minimum analog trigger value required when trigger input uses auto or analog mode.
|
||||||
|
"repeatDelayMs": 320, // Delay before repeating held controller actions.
|
||||||
|
"repeatIntervalMs": 120, // Repeat interval for held controller actions.
|
||||||
|
"buttonIndices": {
|
||||||
|
"select": 6, // Raw button index used for the controller select/minus/back button.
|
||||||
|
"buttonSouth": 0, // Raw button index used for controller south/A button input.
|
||||||
|
"buttonEast": 1, // Raw button index used for controller east/B button input.
|
||||||
|
"buttonWest": 2, // Raw button index used for controller west/X button input.
|
||||||
|
"buttonNorth": 3, // Raw button index used for controller north/Y button input.
|
||||||
|
"leftShoulder": 4, // Raw button index used for controller left shoulder input.
|
||||||
|
"rightShoulder": 5, // Raw button index used for controller right shoulder input.
|
||||||
|
"leftStickPress": 9, // Raw button index used for controller L3 input.
|
||||||
|
"rightStickPress": 10, // Raw button index used for controller R3 input.
|
||||||
|
"leftTrigger": 6, // Raw button index used for controller L2 input.
|
||||||
|
"rightTrigger": 7 // Raw button index used for controller R2 input.
|
||||||
|
}, // Semantic button-name reference mapping used for debug output. Updating it does not rewrite existing raw binding descriptors.
|
||||||
|
"bindings": {
|
||||||
|
"toggleLookup": {
|
||||||
|
"kind": "button", // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||||
|
"buttonIndex": 0 // Raw button index captured for this discrete controller action.
|
||||||
|
}, // Controller binding descriptor for toggling lookup. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||||
|
"closeLookup": {
|
||||||
|
"kind": "button", // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||||
|
"buttonIndex": 1 // Raw button index captured for this discrete controller action.
|
||||||
|
}, // Controller binding descriptor for closing lookup. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||||
|
"toggleKeyboardOnlyMode": {
|
||||||
|
"kind": "button", // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||||
|
"buttonIndex": 3 // Raw button index captured for this discrete controller action.
|
||||||
|
}, // Controller binding descriptor for toggling keyboard-only mode. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||||
|
"mineCard": {
|
||||||
|
"kind": "button", // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||||
|
"buttonIndex": 2 // Raw button index captured for this discrete controller action.
|
||||||
|
}, // Controller binding descriptor for mining the active card. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||||
|
"quitMpv": {
|
||||||
|
"kind": "button", // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||||
|
"buttonIndex": 6 // Raw button index captured for this discrete controller action.
|
||||||
|
}, // Controller binding descriptor for quitting mpv. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||||
|
"previousAudio": {
|
||||||
|
"kind": "none" // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||||
|
}, // Controller binding descriptor for previous Yomitan audio. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||||
|
"nextAudio": {
|
||||||
|
"kind": "button", // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||||
|
"buttonIndex": 5 // Raw button index captured for this discrete controller action.
|
||||||
|
}, // Controller binding descriptor for next Yomitan audio. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||||
|
"playCurrentAudio": {
|
||||||
|
"kind": "button", // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||||
|
"buttonIndex": 4 // Raw button index captured for this discrete controller action.
|
||||||
|
}, // Controller binding descriptor for playing the current Yomitan audio. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||||
|
"toggleMpvPause": {
|
||||||
|
"kind": "button", // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||||
|
"buttonIndex": 9 // Raw button index captured for this discrete controller action.
|
||||||
|
}, // Controller binding descriptor for toggling mpv play/pause. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||||
|
"leftStickHorizontal": {
|
||||||
|
"kind": "axis", // Analog binding input source kind. Values: none | axis
|
||||||
|
"axisIndex": 0, // Raw axis index captured for this analog controller action.
|
||||||
|
"dpadFallback": "horizontal" // Optional D-pad fallback used when this analog controller action should also read D-pad input. Values: none | horizontal | vertical
|
||||||
|
}, // Axis binding descriptor used for left/right token selection. Use Alt+C learn mode or set a raw axis descriptor manually.
|
||||||
|
"leftStickVertical": {
|
||||||
|
"kind": "axis", // Analog binding input source kind. Values: none | axis
|
||||||
|
"axisIndex": 1, // Raw axis index captured for this analog controller action.
|
||||||
|
"dpadFallback": "vertical" // Optional D-pad fallback used when this analog controller action should also read D-pad input. Values: none | horizontal | vertical
|
||||||
|
}, // Axis binding descriptor used for primary popup scrolling. Use Alt+C learn mode or set a raw axis descriptor manually.
|
||||||
|
"rightStickHorizontal": {
|
||||||
|
"kind": "axis", // Analog binding input source kind. Values: none | axis
|
||||||
|
"axisIndex": 3, // Raw axis index captured for this analog controller action.
|
||||||
|
"dpadFallback": "none" // Optional D-pad fallback used when this analog controller action should also read D-pad input. Values: none | horizontal | vertical
|
||||||
|
}, // Axis binding descriptor reserved for alternate right-stick mappings. Use Alt+C learn mode or set a raw axis descriptor manually.
|
||||||
|
"rightStickVertical": {
|
||||||
|
"kind": "axis", // Analog binding input source kind. Values: none | axis
|
||||||
|
"axisIndex": 4, // Raw axis index captured for this analog controller action.
|
||||||
|
"dpadFallback": "none" // Optional D-pad fallback used when this analog controller action should also read D-pad input. Values: none | horizontal | vertical
|
||||||
|
} // Axis binding descriptor used for popup page jumps. Use Alt+C learn mode or set a raw axis descriptor manually.
|
||||||
|
}, // Raw controller binding descriptors saved by Alt+C learn mode. For discrete axis bindings, kind "axis" requires axisIndex and direction.
|
||||||
|
"profiles": {} // Per-controller binding and button-index overrides keyed by the controller id reported by the Gamepad API.
|
||||||
|
}, // Gamepad support for the visible overlay while keyboard-only mode is active.
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// Startup Warmups
|
||||||
|
// Background warmup controls for MeCab, Yomitan, dictionaries, and Jellyfin session.
|
||||||
|
// Disable individual warmups to defer load until first real usage.
|
||||||
|
// lowPowerMode defers all warmups except Yomitan extension.
|
||||||
|
// ==========================================
|
||||||
|
"startupWarmups": {
|
||||||
|
"lowPowerMode": false, // Defer startup warmups except Yomitan extension. Values: true | false
|
||||||
|
"mecab": true, // Warm up MeCab tokenizer at startup. Values: true | false
|
||||||
|
"yomitanExtension": true, // Warm up Yomitan extension at startup. Values: true | false
|
||||||
|
"subtitleDictionaries": true, // Warm up subtitle dictionaries at startup. Values: true | false
|
||||||
|
"jellyfinRemoteSession": false // Warm up Jellyfin remote session at startup. Values: true | false
|
||||||
|
}, // Background warmup controls for MeCab, Yomitan, dictionaries, and Jellyfin session.
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// Updates
|
||||||
|
// Automatic update check behavior.
|
||||||
|
// Manual checks from the tray or launcher are always allowed.
|
||||||
|
// ==========================================
|
||||||
|
"updates": {
|
||||||
|
"enabled": true, // Run automatic update checks in the background. Values: true | false
|
||||||
|
"checkIntervalHours": 24, // Minimum hours between automatic update checks.
|
||||||
|
"notificationType": "overlay", // How SubMiner announces available updates. overlay shows notifications on the overlay, system uses OS notifications, both uses overlay and system. osd and osd-system are legacy config-file-only values. Values: overlay | system | both | none | osd | osd-system
|
||||||
|
"channel": "stable" // Release channel used for update checks. Values: stable | prerelease
|
||||||
|
}, // Automatic update check behavior.
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// Notifications
|
||||||
|
// Overlay notification display behavior.
|
||||||
|
// Hot-reload: position changes apply to the next overlay notification.
|
||||||
|
// ==========================================
|
||||||
|
"notifications": {
|
||||||
|
"overlayPosition": "top-right" // Position for in-overlay notification cards. Values: top-left | top | top-right
|
||||||
|
}, // Overlay notification display behavior.
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// Keyboard Shortcuts
|
// Keyboard Shortcuts
|
||||||
@@ -44,51 +224,206 @@
|
|||||||
// Hot-reload: shortcut changes apply live and update the session help modal on reopen.
|
// Hot-reload: shortcut changes apply live and update the session help modal on reopen.
|
||||||
// ==========================================
|
// ==========================================
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
"toggleVisibleOverlayGlobal": "Alt+Shift+O", // Toggle visible overlay global setting.
|
"toggleVisibleOverlayGlobal": "Alt+Shift+O", // Global accelerator that toggles overlay visibility from anywhere on the system. Use null to disable.
|
||||||
"copySubtitle": "CommandOrControl+C", // Copy subtitle setting.
|
"copySubtitle": "CommandOrControl+C", // Accelerator that copies the current subtitle line to the clipboard.
|
||||||
"copySubtitleMultiple": "CommandOrControl+Shift+C", // Copy subtitle multiple setting.
|
"copySubtitleMultiple": "CommandOrControl+Shift+C", // Accelerator that copies consecutive subtitle lines while the multi-copy window stays open.
|
||||||
"updateLastCardFromClipboard": "CommandOrControl+V", // Update last card from clipboard setting.
|
"updateLastCardFromClipboard": "CommandOrControl+V", // Accelerator that updates the last mined Anki card using the current clipboard contents.
|
||||||
"triggerFieldGrouping": "CommandOrControl+G", // Trigger field grouping setting.
|
"triggerFieldGrouping": "CommandOrControl+G", // Accelerator that triggers Kiku field grouping on duplicate cards.
|
||||||
"triggerSubsync": "Ctrl+Alt+S", // Trigger subsync setting.
|
"triggerSubsync": "Ctrl+Alt+S", // Accelerator that triggers subsync against the active subtitle file.
|
||||||
"mineSentence": "CommandOrControl+S", // Mine sentence setting.
|
"mineSentence": "CommandOrControl+S", // Accelerator that mines the current sentence as a new Anki card.
|
||||||
"mineSentenceMultiple": "CommandOrControl+Shift+S", // Mine sentence multiple setting.
|
"mineSentenceMultiple": "CommandOrControl+Shift+S", // Accelerator that mines consecutive sentences while the multi-mine window stays open.
|
||||||
"multiCopyTimeoutMs": 3000, // Timeout for multi-copy/mine modes.
|
"multiCopyTimeoutMs": 3000, // Timeout for multi-copy/mine modes.
|
||||||
"toggleSecondarySub": "CommandOrControl+Shift+V", // Toggle secondary sub setting.
|
"toggleSecondarySub": "CommandOrControl+Shift+V", // Accelerator that toggles the secondary subtitle bar visibility.
|
||||||
"markAudioCard": "CommandOrControl+Shift+A", // Mark audio card setting.
|
"markAudioCard": "CommandOrControl+Shift+A", // Accelerator that marks the last mined card as an audio card.
|
||||||
"openRuntimeOptions": "CommandOrControl+Shift+O", // Open runtime options setting.
|
"openCharacterDictionaryManager": "CommandOrControl+D", // Accelerator that opens the character dictionary manager modal.
|
||||||
"openJimaku": "Ctrl+Shift+J", // Open jimaku setting.
|
"openRuntimeOptions": "CommandOrControl+Shift+O", // Accelerator that opens the runtime options modal.
|
||||||
|
"openJimaku": "Ctrl+Shift+J", // Accelerator that opens the Jimaku subtitle search modal.
|
||||||
|
"openTsukihime": "Ctrl+Shift+T", // Accelerator that opens the TsukiHime subtitle search modal (configured secondary/Japanese primary tabs).
|
||||||
|
"openSubtitleSelection": "g-s", // Open subtitle selection when enabled. Use g-s to press g then s. Set null to unbind.
|
||||||
|
"openSubtitleGeneration": "Ctrl+Shift+G", // Accelerator that opens the standalone Japanese subtitle generation modal.
|
||||||
|
"openSessionHelp": "CommandOrControl+Slash", // Accelerator that opens the session help / keybinding cheatsheet.
|
||||||
|
"openControllerSelect": "Alt+C", // Accelerator that opens the controller selection and learn-mode modal.
|
||||||
|
"openControllerDebug": "Alt+Shift+C", // Accelerator that opens the controller debug modal with live axis/button readouts.
|
||||||
|
"toggleSubtitleSidebar": "Backslash", // Accelerator that toggles the subtitle sidebar visibility.
|
||||||
|
"toggleNotificationHistory": "CommandOrControl+N", // Accelerator that toggles the overlay notification history panel.
|
||||||
|
"appendClipboardVideoToQueue": "CommandOrControl+A" // Accelerator that appends a video path from the clipboard to the mpv playlist.
|
||||||
}, // Overlay keyboard shortcuts. Set a shortcut to null to disable.
|
}, // Overlay keyboard shortcuts. Set a shortcut to null to disable.
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// Keybindings (MPV Commands)
|
// Keybindings (MPV Commands)
|
||||||
// Extra keybindings that are merged with built-in defaults.
|
// Default and custom keybindings that are merged with built-in defaults.
|
||||||
// Set command to null to disable a default keybinding.
|
// Set command to null to disable a default keybinding.
|
||||||
// Hot-reload: keybinding changes apply live and update the session help modal on reopen.
|
// Hot-reload: keybinding changes apply live and update the session help modal on reopen.
|
||||||
// ==========================================
|
// ==========================================
|
||||||
"keybindings": [], // Extra keybindings that are merged with built-in defaults.
|
"keybindings": [
|
||||||
|
{
|
||||||
|
"key": "Space", // Key setting.
|
||||||
|
"command": [
|
||||||
|
"cycle",
|
||||||
|
"pause"
|
||||||
|
] // Command setting.
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "KeyF", // Key setting.
|
||||||
|
"command": [
|
||||||
|
"cycle",
|
||||||
|
"fullscreen"
|
||||||
|
] // Command setting.
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "KeyJ", // Key setting.
|
||||||
|
"command": [
|
||||||
|
"cycle",
|
||||||
|
"sid"
|
||||||
|
] // Command setting.
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Shift+KeyJ", // Key setting.
|
||||||
|
"command": [
|
||||||
|
"cycle",
|
||||||
|
"secondary-sid"
|
||||||
|
] // Command setting.
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "ArrowRight", // Key setting.
|
||||||
|
"command": [
|
||||||
|
"seek",
|
||||||
|
5
|
||||||
|
] // Command setting.
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "ArrowLeft", // Key setting.
|
||||||
|
"command": [
|
||||||
|
"seek",
|
||||||
|
-5
|
||||||
|
] // Command setting.
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "ArrowUp", // Key setting.
|
||||||
|
"command": [
|
||||||
|
"seek",
|
||||||
|
60
|
||||||
|
] // Command setting.
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "ArrowDown", // Key setting.
|
||||||
|
"command": [
|
||||||
|
"seek",
|
||||||
|
-60
|
||||||
|
] // Command setting.
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Shift+KeyH", // Key setting.
|
||||||
|
"command": [
|
||||||
|
"sub-seek",
|
||||||
|
-1
|
||||||
|
] // Command setting.
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Shift+KeyL", // Key setting.
|
||||||
|
"command": [
|
||||||
|
"sub-seek",
|
||||||
|
1
|
||||||
|
] // Command setting.
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Ctrl+Shift+ArrowLeft", // Key setting.
|
||||||
|
"command": [
|
||||||
|
"sub-step",
|
||||||
|
-1
|
||||||
|
] // Command setting.
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Ctrl+Shift+ArrowRight", // Key setting.
|
||||||
|
"command": [
|
||||||
|
"sub-step",
|
||||||
|
1
|
||||||
|
] // Command setting.
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "KeyZ", // Key setting.
|
||||||
|
"command": [
|
||||||
|
"add",
|
||||||
|
"sub-delay",
|
||||||
|
-0.1
|
||||||
|
] // Command setting.
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Shift+KeyZ", // Key setting.
|
||||||
|
"command": [
|
||||||
|
"add",
|
||||||
|
"sub-delay",
|
||||||
|
0.1
|
||||||
|
] // Command setting.
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "KeyX", // Key setting.
|
||||||
|
"command": [
|
||||||
|
"add",
|
||||||
|
"sub-delay",
|
||||||
|
0.1
|
||||||
|
] // Command setting.
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Ctrl+Alt+KeyC", // Key setting.
|
||||||
|
"command": [
|
||||||
|
"__youtube-picker-open"
|
||||||
|
] // Command setting.
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Ctrl+Alt+KeyP", // Key setting.
|
||||||
|
"command": [
|
||||||
|
"__playlist-browser-open"
|
||||||
|
] // Command setting.
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Ctrl+Shift+KeyH", // Key setting.
|
||||||
|
"command": [
|
||||||
|
"__replay-subtitle"
|
||||||
|
] // Command setting.
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Ctrl+Shift+KeyL", // Key setting.
|
||||||
|
"command": [
|
||||||
|
"__play-next-subtitle"
|
||||||
|
] // Command setting.
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "KeyQ", // Key setting.
|
||||||
|
"command": [
|
||||||
|
"quit"
|
||||||
|
] // Command setting.
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Ctrl+KeyW", // Key setting.
|
||||||
|
"command": [
|
||||||
|
"quit"
|
||||||
|
] // Command setting.
|
||||||
|
}
|
||||||
|
], // Default and custom keybindings that are merged with built-in defaults.
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// Secondary Subtitles
|
// Secondary Subtitles
|
||||||
// Dual subtitle track options.
|
// Dual subtitle track options.
|
||||||
// Used by subminer YouTube subtitle generation as secondary language preferences.
|
// Used by managed subtitle loading as secondary language preferences for local and YouTube playback.
|
||||||
// Hot-reload: defaultMode updates live while SubMiner is running.
|
// Hot-reload: defaultMode updates live while SubMiner is running.
|
||||||
// ==========================================
|
// ==========================================
|
||||||
"secondarySub": {
|
"secondarySub": {
|
||||||
"secondarySubLanguages": [], // Secondary sub languages setting.
|
"secondarySubLanguages": [], // Language code priority list used to auto-select a secondary subtitle track when available.
|
||||||
"autoLoadSecondarySub": false, // Auto load secondary sub setting. Values: true | false
|
"autoLoadSecondarySub": false, // Automatically load a matching secondary subtitle when the primary subtitle loads. Values: true | false
|
||||||
"defaultMode": "hover", // Default mode setting.
|
"defaultMode": "hover" // Default visibility mode for the secondary subtitle bar. Values: hidden | visible | hover
|
||||||
}, // Dual subtitle track options.
|
}, // Dual subtitle track options.
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// Auto Subtitle Sync
|
// Subtitle Sync
|
||||||
// Subsync engine and executable paths.
|
// Subsync engine and executable paths.
|
||||||
|
// Hot-reload: subsync changes apply to the next subtitle sync run.
|
||||||
// ==========================================
|
// ==========================================
|
||||||
"subsync": {
|
"subsync": {
|
||||||
"defaultMode": "auto", // Subsync default mode. Values: auto | manual
|
"alass_path": "", // Optional absolute path to the alass binary used by subsync. Leave empty to auto-discover from PATH.
|
||||||
"alass_path": "", // Alass path setting.
|
"ffsubsync_path": "", // Optional absolute path to the ffsubsync binary used by subsync. Leave empty to auto-discover from PATH.
|
||||||
"ffsubsync_path": "", // Ffsubsync path setting.
|
"ffmpeg_path": "", // Optional absolute path to the ffmpeg binary used by subsync. Leave empty to auto-discover from PATH.
|
||||||
"ffmpeg_path": "", // Ffmpeg path setting.
|
"replace": true // Replace the active subtitle file when sync completes. Values: true | false
|
||||||
"replace": true, // Replace active subtitle file when synchronization succeeds.
|
|
||||||
}, // Subsync engine and executable paths.
|
}, // Subsync engine and executable paths.
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
@@ -96,7 +431,7 @@
|
|||||||
// Initial vertical subtitle position from the bottom.
|
// Initial vertical subtitle position from the bottom.
|
||||||
// ==========================================
|
// ==========================================
|
||||||
"subtitlePosition": {
|
"subtitlePosition": {
|
||||||
"yPercent": 10, // Y percent setting.
|
"yPercent": 10 // Vertical position of the subtitle overlay expressed as a percentage from the bottom of the screen.
|
||||||
}, // Initial vertical subtitle position from the bottom.
|
}, // Initial vertical subtitle position from the bottom.
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
@@ -105,166 +440,322 @@
|
|||||||
// Hot-reload: subtitle style changes apply live without restarting SubMiner.
|
// Hot-reload: subtitle style changes apply live without restarting SubMiner.
|
||||||
// ==========================================
|
// ==========================================
|
||||||
"subtitleStyle": {
|
"subtitleStyle": {
|
||||||
|
"primaryDefaultMode": "visible", // Default primary subtitle bar visibility mode. hidden hides it, visible shows it, hover reveals it on hover. Values: hidden | visible | hover
|
||||||
|
"css": {
|
||||||
|
"font-family": "Hiragino Sans, M PLUS 1, Source Han Sans JP, Noto Sans CJK JP", // Font family setting.
|
||||||
|
"color": "#cad3f5", // Color setting.
|
||||||
|
"background-color": "transparent", // Background color setting.
|
||||||
|
"font-size": "35px", // Font size setting.
|
||||||
|
"font-weight": "600", // Font weight setting.
|
||||||
|
"font-style": "normal", // Font style setting.
|
||||||
|
"line-height": "1.35", // Line height setting.
|
||||||
|
"letter-spacing": "-0.01em", // Letter spacing setting.
|
||||||
|
"word-spacing": "0", // Word spacing setting.
|
||||||
|
"font-kerning": "normal", // Font kerning setting.
|
||||||
|
"text-rendering": "geometricPrecision", // Text rendering setting.
|
||||||
|
"text-shadow": "-1px -1px 2px rgba(0,0,0,0.95), 1px -1px 2px rgba(0,0,0,0.95), -1px 1px 2px rgba(0,0,0,0.95), 1px 1px 2px rgba(0,0,0,0.95), 0 0 8px rgba(0,0,0,0.5)", // Text shadow setting.
|
||||||
|
"backdrop-filter": "blur(6px)", // Backdrop filter setting.
|
||||||
|
"--subtitle-hover-token-color": "#f4dbd6", // Subtitle hover token color setting.
|
||||||
|
"--subtitle-hover-token-background-color": "transparent" // Subtitle hover token background color setting.
|
||||||
|
}, // CSS declaration object applied to primary subtitles after normal subtitle style defaults.
|
||||||
"enableJlpt": false, // Enable JLPT vocabulary level underlines. When disabled, JLPT tagging lookup and underlines are skipped. Values: true | false
|
"enableJlpt": false, // Enable JLPT vocabulary level underlines. When disabled, JLPT tagging lookup and underlines are skipped. Values: true | false
|
||||||
"preserveLineBreaks": false, // Preserve line breaks in visible overlay subtitle rendering. When false, line breaks are flattened to spaces for a single-line flow. Values: true | false
|
"preserveLineBreaks": false, // Preserve line breaks in visible overlay subtitle rendering. When false, line breaks are flattened to spaces for a single-line flow. Values: true | false
|
||||||
"autoPauseVideoOnHover": true, // Automatically pause mpv playback while hovering subtitle text, then resume on leave. Values: true | false
|
"autoPauseVideoOnHover": true, // Automatically pause mpv playback while hovering subtitle text, then resume on leave. Values: true | false
|
||||||
"hoverTokenColor": "#f4dbd6", // Hex color used for hovered subtitle token highlight in mpv.
|
"autoPauseVideoOnYomitanPopup": true, // Automatically pause mpv playback while Yomitan popup is open, then resume when popup closes. Values: true | false
|
||||||
"hoverTokenBackgroundColor": "rgba(54, 58, 79, 0.84)", // CSS color used for hovered subtitle token background highlight in mpv.
|
"primaryVisibleOnYomitanPopup": true, // Keep the primary subtitle bar visible while a Yomitan popup is open when primary subtitles are in hover mode. Values: true | false
|
||||||
"fontFamily": "M PLUS 1 Medium, Source Han Sans JP, Noto Sans CJK JP", // Font family setting.
|
"nameMatchEnabled": false, // Enable character dictionary sync and subtitle token coloring for character-name matches. Values: true | false
|
||||||
"fontSize": 35, // Font size setting.
|
"nameMatchImagesEnabled": false, // Show small character portraits beside subtitle tokens matched from the SubMiner character dictionary. Values: true | false
|
||||||
"fontColor": "#cad3f5", // Font color setting.
|
"nameMatchColor": "#f5bde6", // Hex color used when a subtitle token matches an entry from the SubMiner character dictionary.
|
||||||
"fontWeight": "600", // Font weight setting.
|
"nPlusOneColor": "#c6a0f6", // Color used for the single N+1 target token subtitle highlight.
|
||||||
"lineHeight": 1.35, // Line height setting.
|
"knownWordColor": "#a6da95", // Color used for known-word subtitle highlights.
|
||||||
"letterSpacing": "-0.01em", // Letter spacing setting.
|
"knownWordMaturityColors": {
|
||||||
"wordSpacing": 0, // Word spacing setting.
|
"new": "#ee99a0", // Color for known words whose Anki cards are new (never reviewed), when maturity highlighting is enabled.
|
||||||
"fontKerning": "normal", // Font kerning setting.
|
"learning": "#b7bdf8", // Color for known words whose Anki cards are in (re)learning, when maturity highlighting is enabled.
|
||||||
"textRendering": "geometricPrecision", // Text rendering setting.
|
"young": "#91d7e3", // Color for known words whose Anki cards are in review below the mature threshold, when maturity highlighting is enabled.
|
||||||
"textShadow": "0 3px 10px rgba(0,0,0,0.69)", // Text shadow setting.
|
"mature": "#a6da95" // Color for known words whose Anki cards are at or above the mature interval threshold, when maturity highlighting is enabled.
|
||||||
"fontStyle": "normal", // Font style setting.
|
}, // Known word maturity colors setting.
|
||||||
"backgroundColor": "rgb(30, 32, 48, 0.88)", // Background color setting.
|
|
||||||
"backdropFilter": "blur(6px)", // Backdrop filter setting.
|
|
||||||
"nPlusOneColor": "#c6a0f6", // N plus one color setting.
|
|
||||||
"knownWordColor": "#a6da95", // Known word color setting.
|
|
||||||
"jlptColors": {
|
"jlptColors": {
|
||||||
"N1": "#ed8796", // N1 setting.
|
"N1": "#ed8796", // N1 setting.
|
||||||
"N2": "#f5a97f", // N2 setting.
|
"N2": "#f5a97f", // N2 setting.
|
||||||
"N3": "#f9e2af", // N3 setting.
|
"N3": "#f9e2af", // N3 setting.
|
||||||
"N4": "#a6e3a1", // N4 setting.
|
"N4": "#8bd5ca", // N4 setting.
|
||||||
"N5": "#8aadf4", // N5 setting.
|
"N5": "#8aadf4" // N5 setting.
|
||||||
}, // Jlpt colors setting.
|
}, // Jlpt colors setting.
|
||||||
"frequencyDictionary": {
|
"frequencyDictionary": {
|
||||||
"enabled": false, // Enable frequency-dictionary-based highlighting based on token rank. Values: true | false
|
"enabled": false, // Enable frequency-dictionary-based highlighting based on token rank. Values: true | false
|
||||||
"sourcePath": "", // Optional absolute path to a frequency dictionary directory. If empty, SubMiner searches installed/default frequency-dictionary locations.
|
"sourcePath": "", // Optional absolute path to a frequency dictionary directory. If empty, built-in discovery search paths are used.
|
||||||
"topX": 1000, // Only color tokens with frequency rank <= topX (default: 1000).
|
"topX": 10000, // Only color tokens with frequency rank <= topX (default: 10000).
|
||||||
"mode": "single", // single: use one color for all matching tokens. banded: use color ramp by frequency band. Values: single | banded
|
"mode": "single", // single: use one color for all matching tokens. banded: use color ramp by frequency band. Values: single | banded
|
||||||
"matchMode": "headword", // Frequency lookup text selection mode. Values: headword | surface
|
"matchMode": "headword", // headword: frequency lookup uses dictionary form. surface: lookup uses subtitle-visible token text. Values: headword | surface
|
||||||
"singleColor": "#f5a97f", // Color used when frequencyDictionary.mode is `single`.
|
"singleColor": "#f5a97f", // Color used when frequencyDictionary.mode is `single`.
|
||||||
"bandedColors": ["#ed8796", "#f5a97f", "#f9e2af", "#a6e3a1", "#8aadf4"], // Five colors used for rank bands when mode is `banded` (from most common to least within topX).
|
"bandedColors": [
|
||||||
|
"#ed8796",
|
||||||
|
"#f5a97f",
|
||||||
|
"#f9e2af",
|
||||||
|
"#8bd5ca",
|
||||||
|
"#8aadf4"
|
||||||
|
] // Five colors used for rank bands when mode is `banded` (from most common to least within topX).
|
||||||
}, // Frequency dictionary setting.
|
}, // Frequency dictionary setting.
|
||||||
"secondary": {
|
"secondary": {
|
||||||
"fontFamily": "Inter, Noto Sans, Helvetica Neue, sans-serif", // Font family setting.
|
"css": {
|
||||||
"fontSize": 24, // Font size setting.
|
"font-family": "Hiragino Sans, M PLUS 1, Source Han Sans JP, Noto Sans CJK JP", // Font family setting.
|
||||||
"fontColor": "#cad3f5", // Font color setting.
|
"color": "#cad3f5", // Color setting.
|
||||||
"lineHeight": 1.35, // Line height setting.
|
"background-color": "transparent", // Background color setting.
|
||||||
"letterSpacing": "-0.01em", // Letter spacing setting.
|
"font-size": "24px", // Font size setting.
|
||||||
"wordSpacing": 0, // Word spacing setting.
|
"font-weight": "600", // Font weight setting.
|
||||||
"fontKerning": "normal", // Font kerning setting.
|
"font-style": "normal", // Font style setting.
|
||||||
"textRendering": "geometricPrecision", // Text rendering setting.
|
"line-height": "1.35", // Line height setting.
|
||||||
"textShadow": "0 3px 10px rgba(0,0,0,0.69)", // Text shadow setting.
|
"letter-spacing": "-0.01em", // Letter spacing setting.
|
||||||
"backgroundColor": "transparent", // Background color setting.
|
"word-spacing": "0", // Word spacing setting.
|
||||||
"backdropFilter": "blur(6px)", // Backdrop filter setting.
|
"font-kerning": "normal", // Font kerning setting.
|
||||||
"fontWeight": "normal", // Font weight setting.
|
"text-rendering": "geometricPrecision", // Text rendering setting.
|
||||||
"fontStyle": "normal", // Font style setting.
|
"text-shadow": "-1px -1px 2px rgba(0,0,0,0.95), 1px -1px 2px rgba(0,0,0,0.95), -1px 1px 2px rgba(0,0,0,0.95), 1px 1px 2px rgba(0,0,0,0.95), 0 0 8px rgba(0,0,0,0.5)", // Text shadow setting.
|
||||||
}, // Secondary setting.
|
"backdrop-filter": "blur(6px)" // Backdrop filter setting.
|
||||||
|
} // CSS declaration object applied to secondary subtitles after normal subtitle style defaults.
|
||||||
|
} // Secondary setting.
|
||||||
}, // Primary and secondary subtitle styling.
|
}, // Primary and secondary subtitle styling.
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// Subtitle Sidebar
|
||||||
|
// Parsed-subtitle sidebar cue list styling, behavior, and toggle key.
|
||||||
|
// Hot-reload: subtitle sidebar changes apply live without restarting SubMiner.
|
||||||
|
// ==========================================
|
||||||
|
"subtitleSidebar": {
|
||||||
|
"enabled": true, // Enable the subtitle sidebar feature for parsed subtitle sources. Values: true | false
|
||||||
|
"autoOpen": false, // Automatically open the subtitle sidebar once during overlay startup. Values: true | false
|
||||||
|
"layout": "overlay", // Render the subtitle sidebar as a floating overlay or reserve space inside mpv. Values: overlay | embedded
|
||||||
|
"toggleKey": "Backslash", // KeyboardEvent.code used to toggle the subtitle sidebar open and closed.
|
||||||
|
"pauseVideoOnHover": true, // Pause mpv while hovering the subtitle sidebar, then resume on leave. Values: true | false
|
||||||
|
"autoScroll": true, // Auto-scroll the active subtitle cue into view while playback advances. Values: true | false
|
||||||
|
"css": {
|
||||||
|
"font-family": "Hiragino Sans, M PLUS 1, Source Han Sans JP, Noto Sans CJK JP", // Font family setting.
|
||||||
|
"color": "#cad3f5", // Color setting.
|
||||||
|
"background-color": "rgba(73, 77, 100, 0.9)", // Background color setting.
|
||||||
|
"font-size": "16px", // Font size setting.
|
||||||
|
"opacity": "0.95", // Opacity setting.
|
||||||
|
"--subtitle-sidebar-max-width": "420px", // Subtitle sidebar max width setting.
|
||||||
|
"--subtitle-sidebar-timestamp-color": "#a5adcb", // Subtitle sidebar timestamp color setting.
|
||||||
|
"--subtitle-sidebar-active-line-color": "#f5bde6", // Subtitle sidebar active line color setting.
|
||||||
|
"--subtitle-sidebar-active-background-color": "rgba(138, 173, 244, 0.22)", // Subtitle sidebar active background color setting.
|
||||||
|
"--subtitle-sidebar-hover-background-color": "rgba(54, 58, 79, 0.84)" // Subtitle sidebar hover background color setting.
|
||||||
|
} // CSS declaration object applied to the subtitle sidebar. Includes color, background-color, and all font properties.
|
||||||
|
}, // Parsed-subtitle sidebar cue list styling, behavior, and toggle key.
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// Shared AI Provider
|
||||||
|
// Canonical OpenAI-compatible provider transport settings shared by Anki and YouTube subtitle fixing.
|
||||||
|
// ==========================================
|
||||||
|
"ai": {
|
||||||
|
"enabled": false, // Enable shared OpenAI-compatible AI provider features. Values: true | false
|
||||||
|
"apiKey": "", // Static API key for the shared OpenAI-compatible AI provider.
|
||||||
|
"apiKeyCommand": "", // Shell command used to resolve the shared AI provider API key.
|
||||||
|
"model": "openai/gpt-4o-mini", // Default model identifier requested from the shared AI provider.
|
||||||
|
"baseUrl": "https://openrouter.ai/api", // Base URL for the shared OpenAI-compatible AI provider.
|
||||||
|
"systemPrompt": "You are a translation engine. Return only the translated text with no explanations.", // Default system prompt sent with shared AI provider requests.
|
||||||
|
"requestTimeoutMs": 15000 // Timeout in milliseconds for shared AI provider requests.
|
||||||
|
}, // Canonical OpenAI-compatible provider transport settings shared by Anki and YouTube subtitle fixing.
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// AnkiConnect Integration
|
// AnkiConnect Integration
|
||||||
// Automatic Anki updates and media generation options.
|
// Automatic Anki updates and media generation options.
|
||||||
// Hot-reload: AI translation settings update live while SubMiner is running.
|
// Hot-reload: ankiConnect.ai.enabled, media.normalizeAudio/mirrorMpvVolume/reviewTiming, knownWords, nPlusOne, fields.word/audio/wordAudio/image/sentence/miscInfo, behavior.autoUpdateNewCards, isLapis.sentenceCardModel, isKiku.fieldGrouping, isSenren.fieldGrouping, and lapisKiku.wordCardKind update live while SubMiner is running.
|
||||||
|
// Shared AI provider transport settings are read from top-level ai and typically require restart.
|
||||||
// Most other AnkiConnect settings still require restart.
|
// Most other AnkiConnect settings still require restart.
|
||||||
// ==========================================
|
// ==========================================
|
||||||
"ankiConnect": {
|
"ankiConnect": {
|
||||||
"enabled": false, // Enable AnkiConnect integration. Values: true | false
|
"enabled": true, // Enable AnkiConnect integration. Values: true | false
|
||||||
"url": "http://127.0.0.1:8765", // Url setting.
|
"url": "http://127.0.0.1:8765", // Base URL of the AnkiConnect HTTP server.
|
||||||
"pollingRate": 3000, // Polling interval in milliseconds.
|
"pollingRate": 3000, // Polling interval in milliseconds.
|
||||||
"proxy": {
|
"proxy": {
|
||||||
"enabled": false, // Enable local AnkiConnect-compatible proxy for push-based auto-enrichment. Values: true | false
|
"enabled": true, // Enable local AnkiConnect-compatible proxy for push-based auto-enrichment. Values: true | false
|
||||||
"host": "127.0.0.1", // Bind host for local AnkiConnect proxy.
|
"host": "127.0.0.1", // Bind host for local AnkiConnect proxy.
|
||||||
"port": 8766, // Bind port for local AnkiConnect proxy.
|
"port": 8766, // Bind port for local AnkiConnect proxy.
|
||||||
"upstreamUrl": "http://127.0.0.1:8765", // Upstream AnkiConnect URL proxied by local AnkiConnect proxy.
|
"upstreamUrl": "http://127.0.0.1:8765" // Upstream AnkiConnect URL proxied by local AnkiConnect proxy.
|
||||||
}, // Proxy setting.
|
}, // Proxy setting.
|
||||||
"tags": ["SubMiner"], // Tags to add to cards mined or updated by SubMiner. Provide an empty array to disable automatic tagging.
|
"tags": [
|
||||||
|
"SubMiner"
|
||||||
|
], // Tags to add to cards mined or updated by SubMiner. Provide an empty array to disable automatic tagging.
|
||||||
|
"deck": "", // Restrict duplicate detection and card enrichment to this Anki deck. Leave empty to use the Yomitan mining deck when available.
|
||||||
"fields": {
|
"fields": {
|
||||||
"audio": "ExpressionAudio", // Audio setting.
|
"word": "Expression", // Card field for the mined word or expression text.
|
||||||
"image": "Picture", // Image setting.
|
"audio": "ExpressionAudio", // Card field that receives generated sentence audio.
|
||||||
"sentence": "Sentence", // Sentence setting.
|
"wordAudio": "ExpressionAudio", // Existing word-audio field read to time the frozen first frame of animated images. This mapping is only used for synchronization.
|
||||||
"miscInfo": "MiscInfo", // Misc info setting.
|
"image": "Picture", // Card field that receives the captured screenshot or animated image.
|
||||||
"translation": "SelectionText", // Translation setting.
|
"sentence": "Sentence", // Card field that receives the source sentence text.
|
||||||
|
"miscInfo": "MiscInfo", // Card field that receives the miscellaneous info pattern (see ankiConnect.metadata.pattern).
|
||||||
|
"translation": "SelectionText" // Card field that receives the current selection or translated text.
|
||||||
}, // Fields setting.
|
}, // Fields setting.
|
||||||
"ai": {
|
"ai": {
|
||||||
"enabled": false, // Enabled setting. Values: true | false
|
"enabled": false, // Enable AI provider usage for Anki translation/enrichment flows. Values: true | false
|
||||||
"alwaysUseAiTranslation": false, // Always use ai translation setting. Values: true | false
|
"model": "", // Optional model override for Anki AI translation/enrichment flows.
|
||||||
"apiKey": "", // Api key setting.
|
"systemPrompt": "" // Optional system prompt override for Anki AI translation/enrichment flows.
|
||||||
"model": "openai/gpt-4o-mini", // Model setting.
|
|
||||||
"baseUrl": "https://openrouter.ai/api", // Base url setting.
|
|
||||||
"targetLanguage": "English", // Target language setting.
|
|
||||||
"systemPrompt": "You are a translation engine. Return only the translated text with no explanations.", // System prompt setting.
|
|
||||||
}, // Ai setting.
|
}, // Ai setting.
|
||||||
"media": {
|
"media": {
|
||||||
"generateAudio": true, // Generate audio setting. Values: true | false
|
"generateAudio": true, // Generate sentence audio for mined cards. Values: true | false
|
||||||
"generateImage": true, // Generate image setting. Values: true | false
|
"generateImage": true, // Generate screenshot or animated image for mined cards. Values: true | false
|
||||||
"imageType": "static", // Image type setting.
|
"imageType": "static", // Image capture type: "static" for a single still frame, "avif" for an animated AVIF. Values: static | avif
|
||||||
"imageFormat": "jpg", // Image format setting.
|
"imageFormat": "jpg", // Encoding format used when imageType is "static". Values: jpg | png | webp
|
||||||
"imageQuality": 92, // Image quality setting.
|
"imageQuality": 92, // Quality (0-100) used for lossy static image encoders.
|
||||||
"animatedFps": 10, // Animated fps setting.
|
"imageMaxWidth": 0, // Maximum width for static images, in pixels. Set to 0 to preserve the source resolution.
|
||||||
"animatedMaxWidth": 640, // Animated max width setting.
|
"imageMaxHeight": 0, // Maximum height for static images, in pixels. Set to 0 to preserve the source resolution.
|
||||||
"animatedCrf": 35, // Animated crf setting.
|
"animatedFps": 10, // Target frame rate for animated AVIF captures.
|
||||||
"audioPadding": 0.5, // Audio padding setting.
|
"animatedMaxWidth": 640, // Maximum width applied to animated AVIF captures.
|
||||||
"fallbackDuration": 3, // Fallback duration setting.
|
"animatedMaxHeight": 0, // Maximum height for animated AVIF captures, in pixels. Set to 0 to preserve aspect ratio.
|
||||||
"maxMediaDuration": 30, // Max media duration setting.
|
"animatedCrf": 35, // Animated AVIF CRF quality target. Lower values produce larger, higher-quality files.
|
||||||
|
"syncAnimatedImageToWordAudio": true, // For animated AVIF images, prepend a frozen first frame matching the existing word-audio duration so motion starts with sentence audio. Values: true | false
|
||||||
|
"normalizeAudio": true, // Normalize generated sentence audio loudness during media extraction. Changes apply live. Values: true | false
|
||||||
|
"mirrorMpvVolume": true, // Apply mpv's current software volume curve to generated sentence audio. Changes apply live. Values: true | false
|
||||||
|
"reviewTiming": false, // Review and preview subtitle media timing before SubMiner creates or enriches a mined card. Values: true | false
|
||||||
|
"audioPadding": 0, // Seconds of padding appended to both ends of generated sentence audio and animated AVIF clips.
|
||||||
|
"fallbackDuration": 3, // Fallback clip duration in seconds when subtitle timing data is unavailable.
|
||||||
|
"maxMediaDuration": 30 // Maximum allowed media clip duration in seconds. 0 disables the cap.
|
||||||
}, // Media setting.
|
}, // Media setting.
|
||||||
|
"knownWords": {
|
||||||
|
"highlightEnabled": false, // Enable fast local highlighting for words already known in Anki. Values: true | false
|
||||||
|
"maturityEnabled": false, // Color known-word highlights by Anki card maturity (new, learning, young, mature) instead of a single color. Requires known-word highlighting. Values: true | false
|
||||||
|
"matureThresholdDays": 21, // Card interval in days at which a known word counts as mature (Anki convention: 21).
|
||||||
|
"refreshMinutes": 1440, // Minutes between known-word cache refreshes.
|
||||||
|
"addMinedWordsImmediately": true, // Immediately append newly mined card words into the known-word cache. Values: true | false
|
||||||
|
"matchMode": "headword", // Known-word matching strategy for subtitle annotations. Cache matches always receive known-word highlighting even when POS filters suppress other annotation types. Values: headword | surface
|
||||||
|
"decks": {} // Decks and expression/word fields for known-word cache. Object mapping deck names to arrays of field names to extract, e.g. { "Kaishi 1.5k": ["Word"] }. Reading fields (Reading, Word Reading, ExpressionReading) are always probed so cached words match only in the reading their note teaches; words from notes without readings match in any reading.
|
||||||
|
}, // Known words setting.
|
||||||
"behavior": {
|
"behavior": {
|
||||||
"overwriteAudio": true, // Overwrite audio setting. Values: true | false
|
"overwriteAudio": true, // When updating an existing card, overwrite the audio field instead of skipping it. Values: true | false
|
||||||
"overwriteImage": true, // Overwrite image setting. Values: true | false
|
"overwriteImage": true, // When updating an existing card, overwrite the image field instead of skipping it. Values: true | false
|
||||||
"mediaInsertMode": "append", // Media insert mode setting.
|
"mediaInsertMode": "append", // Whether new media is appended after or prepended before existing field contents on update. Values: append | prepend
|
||||||
"highlightWord": true, // Highlight word setting. Values: true | false
|
"highlightWord": true, // Bold the mined word inside the sentence field on the saved Anki card. Values: true | false
|
||||||
"notificationType": "osd", // Notification type setting.
|
"notificationType": "overlay", // Notification surface used to announce mining and update outcomes. overlay shows notifications on the overlay, system uses OS notifications, both uses overlay and system. osd and osd-system are legacy config-file-only values. Values: overlay | system | both | none | osd | osd-system
|
||||||
"autoUpdateNewCards": true, // Automatically update newly added cards. Values: true | false
|
"autoUpdateNewCards": true // Automatically update newly added cards. Values: true | false
|
||||||
}, // Behavior setting.
|
}, // Behavior setting.
|
||||||
"nPlusOne": {
|
"nPlusOne": {
|
||||||
"highlightEnabled": false, // Enable fast local highlighting for words already known in Anki. Values: true | false
|
"enabled": false, // Enable N+1 subtitle highlighting (highlights the one unknown word in a sentence). Requires known-word cache data. Values: true | false
|
||||||
"refreshMinutes": 1440, // Minutes between known-word cache refreshes.
|
"minSentenceWords": 3 // Minimum sentence word count required for N+1 targeting (default: 3).
|
||||||
"matchMode": "headword", // Known-word matching strategy for N+1 highlighting. Values: headword | surface
|
|
||||||
"decks": [], // Decks used for N+1 known-word cache scope. Supports one or more deck names.
|
|
||||||
"minSentenceWords": 3, // Minimum sentence word count required for N+1 targeting (default: 3).
|
|
||||||
"nPlusOne": "#c6a0f6", // Color used for the single N+1 target token highlight.
|
|
||||||
"knownWord": "#a6da95", // Color used for legacy known-word highlights.
|
|
||||||
}, // N plus one setting.
|
}, // N plus one setting.
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"pattern": "[SubMiner] %f (%t)", // Pattern setting.
|
"pattern": "[SubMiner] %f (%t)" // Template used to render the miscInfo field. Placeholders include %f (filename) and %t (timestamp).
|
||||||
}, // Metadata setting.
|
}, // Metadata setting.
|
||||||
"isLapis": {
|
"isLapis": {
|
||||||
"enabled": false, // Enabled setting. Values: true | false
|
"enabled": false, // Enable Lapis-specific mining behaviors and sentence card model targeting. Values: true | false
|
||||||
"sentenceCardModel": "Japanese sentences", // Sentence card model setting.
|
"sentenceCardModel": "Lapis" // Note type name used by Lapis sentence cards.
|
||||||
}, // Is lapis setting.
|
}, // Is lapis setting.
|
||||||
"isKiku": {
|
"isKiku": {
|
||||||
"enabled": false, // Enabled setting. Values: true | false
|
"enabled": false, // Enable Kiku-specific mining behaviors (duplicate handling, field grouping). Values: true | false
|
||||||
"fieldGrouping": "disabled", // Kiku duplicate-card field grouping mode. Values: auto | manual | disabled
|
"fieldGrouping": "disabled", // Kiku duplicate-card field grouping mode. Values: auto | manual | disabled
|
||||||
"deleteDuplicateInAuto": true, // Delete duplicate in auto setting. Values: true | false
|
"deleteDuplicateInAuto": true // When Kiku field grouping is "auto", delete the duplicate source card after grouping completes. Values: true | false
|
||||||
}, // Is kiku setting.
|
}, // Is kiku setting.
|
||||||
|
"isSenren": {
|
||||||
|
"enabled": false, // Enable Senren-specific duplicate handling (scene-switching field grouping, including miscInfo grouping). Mutually exclusive with isKiku.enabled. Values: true | false
|
||||||
|
"fieldGrouping": "auto", // Senren duplicate-card field grouping mode (scene switching). Values: auto | manual | disabled
|
||||||
|
"deleteDuplicateInAuto": true // When Senren field grouping is "auto", delete the duplicate source card after grouping completes. Values: true | false
|
||||||
|
}, // Is senren setting.
|
||||||
|
"lapisKiku": {
|
||||||
|
"wordCardKind": "word-and-sentence" // Card-type flag SubMiner marks on Kiku/Lapis word cards. Only one flag is set at a time; the others are cleared. Requires isKiku.enabled or isLapis.enabled. Values: word-and-sentence | click | sentence | audio | none
|
||||||
|
} // Lapis kiku setting.
|
||||||
}, // Automatic Anki updates and media generation options.
|
}, // Automatic Anki updates and media generation options.
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// Jimaku
|
// Jimaku
|
||||||
// Jimaku API configuration and defaults.
|
// Jimaku API configuration and defaults.
|
||||||
|
// Hot-reload: Jimaku changes apply to the next Jimaku request.
|
||||||
// ==========================================
|
// ==========================================
|
||||||
"jimaku": {
|
"jimaku": {
|
||||||
"apiBaseUrl": "https://jimaku.cc", // Api base url setting.
|
"apiBaseUrl": "https://jimaku.cc", // Base URL of the Jimaku subtitle search API.
|
||||||
|
"apiKey": "", // Jimaku API key. Optional but recommended for higher rate limits. Get one for free at https://jimaku.cc.
|
||||||
|
"apiKeyCommand": "", // Shell command that prints the Jimaku API key to stdout. Used instead of apiKey to avoid storing the key in plain text.
|
||||||
"languagePreference": "ja", // Preferred language used in Jimaku search. Values: ja | en | none
|
"languagePreference": "ja", // Preferred language used in Jimaku search. Values: ja | en | none
|
||||||
"maxEntryResults": 10, // Maximum Jimaku search results returned.
|
"maxEntryResults": 10 // Maximum Jimaku search results returned.
|
||||||
}, // Jimaku API configuration and defaults.
|
}, // Jimaku API configuration and defaults.
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// YouTube Subtitle Generation
|
// TsukiHime
|
||||||
// Defaults for subminer YouTube subtitle extraction/transcription mode.
|
// TsukiHime subtitle search configuration for Japanese primary and configured secondary subtitles. No API key required.
|
||||||
|
// Hot-reload: TsukiHime changes apply to the next TsukiHime request.
|
||||||
// ==========================================
|
// ==========================================
|
||||||
"youtubeSubgen": {
|
"tsukihime": {
|
||||||
"mode": "automatic", // YouTube subtitle generation mode for the launcher script. Values: automatic | preprocess | off
|
"apiBaseUrl": "https://api.tsukihime.org/v1", // Base URL of the TsukiHime API (Animetosho successor). No API key required.
|
||||||
"whisperBin": "", // Path to whisper.cpp CLI used as fallback transcription engine.
|
"maxSearchResults": 10 // Maximum TsukiHime search results returned.
|
||||||
"whisperModel": "", // Path to whisper model used for fallback transcription.
|
}, // TsukiHime subtitle search configuration for Japanese primary and configured secondary subtitles. No API key required.
|
||||||
"primarySubLanguages": ["ja", "jpn"], // Comma-separated primary subtitle language priority used by the launcher.
|
|
||||||
}, // Defaults for subminer YouTube subtitle extraction/transcription mode.
|
// ==========================================
|
||||||
|
// TMDB
|
||||||
|
// TMDB (The Movie Database) metadata for live-action dramas and movies in the stats Library: posters, synopses, and grouping by show.
|
||||||
|
// Hot-reload: TMDB changes apply to the next TMDB request.
|
||||||
|
// ==========================================
|
||||||
|
"tmdb": {
|
||||||
|
"apiKey": "", // Your own TMDB API key or read access token for live-action posters and synopses in the stats Library. Release builds bundle a project key, so set this only to use your own quota or when running from source (free under Settings > API on themoviedb.org).
|
||||||
|
"apiKeyCommand": "" // Shell command that prints the TMDB API key to stdout. Used instead of apiKey to avoid storing the key in plain text.
|
||||||
|
}, // TMDB (The Movie Database) metadata for live-action dramas and movies in the stats Library: posters, synopses, and grouping by show.
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// YouTube Playback Settings
|
||||||
|
// Defaults for managed subtitle language preferences and YouTube subtitle loading.
|
||||||
|
// Hot-reload: primarySubLanguages and subtitleSource apply to the next YouTube subtitle load.
|
||||||
|
// ==========================================
|
||||||
|
"youtube": {
|
||||||
|
"primarySubLanguages": [
|
||||||
|
"ja",
|
||||||
|
"jpn"
|
||||||
|
], // Comma-separated primary subtitle language priority for managed subtitle auto-selection.
|
||||||
|
"subtitleSource": "youtube", // Where primary YouTube subtitles come from. Whisper transcribes the audio locally using the subtitleGeneration settings. Values: youtube | whisper
|
||||||
|
"mediaCache": {
|
||||||
|
"mode": "direct", // How YouTube card audio/images are extracted. Values: direct | background
|
||||||
|
"maxHeight": 720 // Maximum video height downloaded for the YouTube background media cache. Set to 0 for unlimited.
|
||||||
|
} // Media cache setting.
|
||||||
|
}, // Defaults for managed subtitle language preferences and YouTube subtitle loading.
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// Anilist
|
// Anilist
|
||||||
// Anilist API credentials and update behavior.
|
// Anilist API credentials and update behavior.
|
||||||
|
// Includes optional auto-sync for a merged MRU-based character dictionary in bundled Yomitan.
|
||||||
|
// Character dictionaries are keyed by AniList media ID (no season/franchise merge).
|
||||||
// ==========================================
|
// ==========================================
|
||||||
"anilist": {
|
"anilist": {
|
||||||
"enabled": false, // Enable AniList post-watch progress updates. Values: true | false
|
"enabled": false, // Enable AniList post-watch progress updates. Values: true | false
|
||||||
"accessToken": "", // Optional explicit AniList access token override; leave empty to use locally stored token from setup.
|
"accessToken": "", // Optional explicit AniList access token override; leave empty to use locally stored token from setup.
|
||||||
|
"characterDictionary": {
|
||||||
|
"maxLoaded": 3, // Maximum number of most-recently-used anime snapshots included in the merged Yomitan character dictionary.
|
||||||
|
"profileScope": "all", // Yomitan profile scope for character dictionary settings updates. Values: all | active
|
||||||
|
"collapsibleSections": {
|
||||||
|
"description": false, // Open the Description section by default in character dictionary glossary entries. Values: true | false
|
||||||
|
"characterInformation": false, // Open the Character Information section by default in character dictionary glossary entries. Values: true | false
|
||||||
|
"voicedBy": false // Open the Voiced by section by default in character dictionary glossary entries. Values: true | false
|
||||||
|
} // Collapsible sections setting.
|
||||||
|
} // Character dictionary setting.
|
||||||
}, // Anilist API credentials and update behavior.
|
}, // Anilist API credentials and update behavior.
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// Yomitan
|
||||||
|
// Optional external Yomitan profile integration.
|
||||||
|
// Setting yomitan.externalProfilePath switches SubMiner to read-only external-profile mode.
|
||||||
|
// For GameSentenceMiner on Linux, the default overlay profile is usually ~/.config/gsm_overlay.
|
||||||
|
// In external-profile mode SubMiner will not import, delete, or modify Yomitan dictionaries/settings.
|
||||||
|
// ==========================================
|
||||||
|
"yomitan": {
|
||||||
|
"externalProfilePath": "" // Optional external Yomitan Electron profile path to use in read-only mode for shared dictionaries/settings. Example: ~/.config/gsm_overlay
|
||||||
|
}, // Optional external Yomitan profile integration.
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// MPV Launcher
|
||||||
|
// SubMiner-managed mpv launch and bundled plugin options.
|
||||||
|
// Set mpv.socketPath to the IPC socket used by the launcher, Electron app, and bundled plugin.
|
||||||
|
// autoStartSubMiner starts SubMiner in the background; auto_start_overlay only controls visible overlay display.
|
||||||
|
// Set mpv.launchMode to choose normal, maximized, or fullscreen SubMiner-managed mpv playback.
|
||||||
|
// Set mpv.profile to pass an mpv profile to managed mpv launches; leave it blank to pass none.
|
||||||
|
// Leave mpv.executablePath blank to auto-discover mpv.exe from SUBMINER_MPV_PATH or PATH.
|
||||||
|
// ==========================================
|
||||||
|
"mpv": {
|
||||||
|
"executablePath": "", // Optional absolute path to mpv.exe for Windows launch flows. Leave empty to auto-discover from SUBMINER_MPV_PATH or PATH.
|
||||||
|
"launchMode": "normal", // Default window state for SubMiner-managed mpv launches. Values: normal | maximized | fullscreen
|
||||||
|
"profile": "", // Optional mpv profile name passed to SubMiner-managed mpv launches. Leave empty to pass no profile.
|
||||||
|
"socketPath": "/tmp/subminer-socket", // mpv IPC socket path used by SubMiner-managed playback and the bundled mpv plugin.
|
||||||
|
"backend": "auto", // Window tracking backend passed to the bundled mpv plugin. Auto detects the current platform. Values: auto | hyprland | sway | x11 | macos | windows
|
||||||
|
"autoStartSubMiner": true, // Start SubMiner in the background when SubMiner-managed mpv loads a file. Values: true | false
|
||||||
|
"pauseUntilOverlayReady": true, // Pause mpv on visible-overlay auto-start until SubMiner signals subtitle tokenization readiness. Values: true | false
|
||||||
|
"subminerBinaryPath": "", // Optional SubMiner app binary path passed to the bundled mpv plugin. Leave empty to use the launcher-detected app path.
|
||||||
|
"aniskipEnabled": true, // Enable AniSkip intro detection, chapter markers, and the skip-intro key. Values: true | false
|
||||||
|
"aniskipButtonKey": "TAB" // mpv key used to skip the detected intro while the skip prompt is visible.
|
||||||
|
}, // SubMiner-managed mpv launch and bundled plugin options.
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// Jellyfin
|
// Jellyfin
|
||||||
// Optional Jellyfin integration for auth, browsing, and playback launch.
|
// Optional Jellyfin integration for auth, browsing, and playback launch.
|
||||||
@@ -274,20 +765,25 @@
|
|||||||
"jellyfin": {
|
"jellyfin": {
|
||||||
"enabled": false, // Enable optional Jellyfin integration and CLI control commands. Values: true | false
|
"enabled": false, // Enable optional Jellyfin integration and CLI control commands. Values: true | false
|
||||||
"serverUrl": "", // Base Jellyfin server URL (for example: http://localhost:8096).
|
"serverUrl": "", // Base Jellyfin server URL (for example: http://localhost:8096).
|
||||||
|
"recentServers": [], // Recently authenticated Jellyfin server URLs shown in setup.
|
||||||
"username": "", // Default Jellyfin username used during CLI login.
|
"username": "", // Default Jellyfin username used during CLI login.
|
||||||
"deviceId": "subminer", // Device id setting.
|
|
||||||
"clientName": "SubMiner", // Client name setting.
|
|
||||||
"clientVersion": "0.1.0", // Client version setting.
|
|
||||||
"defaultLibraryId": "", // Optional default Jellyfin library ID for item listing.
|
"defaultLibraryId": "", // Optional default Jellyfin library ID for item listing.
|
||||||
"remoteControlEnabled": true, // Enable Jellyfin remote cast control mode. Values: true | false
|
"remoteControlEnabled": true, // Enable Jellyfin remote cast control mode. Values: true | false
|
||||||
"remoteControlAutoConnect": true, // Auto-connect to the configured remote control target. Values: true | false
|
"remoteControlAutoConnect": true, // Auto-connect to the configured remote control target. Values: true | false
|
||||||
"autoAnnounce": false, // When enabled, automatically trigger remote announce/visibility check on websocket connect. Values: true | false
|
"autoAnnounce": false, // When enabled, automatically trigger remote announce/visibility check on websocket connect. Values: true | false
|
||||||
"remoteControlDeviceName": "SubMiner", // Device name reported for Jellyfin remote control sessions.
|
|
||||||
"pullPictures": false, // Enable Jellyfin poster/icon fetching for launcher menus. Values: true | false
|
"pullPictures": false, // Enable Jellyfin poster/icon fetching for launcher menus. Values: true | false
|
||||||
"iconCacheDir": "/tmp/subminer-jellyfin-icons", // Directory used by launcher for cached Jellyfin poster icons.
|
"iconCacheDir": "/tmp/subminer-jellyfin-icons", // Directory used by launcher for cached Jellyfin poster icons.
|
||||||
"directPlayPreferred": true, // Try direct play before server-managed transcoding when possible. Values: true | false
|
"directPlayPreferred": true, // Try direct play before server-managed transcoding when possible. Values: true | false
|
||||||
"directPlayContainers": ["mkv", "mp4", "webm", "mov", "flac", "mp3", "aac"], // Container allowlist for direct play decisions.
|
"directPlayContainers": [
|
||||||
"transcodeVideoCodec": "h264", // Preferred transcode video codec when direct play is unavailable.
|
"mkv",
|
||||||
|
"mp4",
|
||||||
|
"webm",
|
||||||
|
"mov",
|
||||||
|
"flac",
|
||||||
|
"mp3",
|
||||||
|
"aac"
|
||||||
|
], // Container allowlist for direct play decisions.
|
||||||
|
"transcodeVideoCodec": "h264" // Preferred transcode video codec when direct play is unavailable.
|
||||||
}, // Optional Jellyfin integration for auth, browsing, and playback launch.
|
}, // Optional Jellyfin integration for auth, browsing, and playback launch.
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
@@ -296,9 +792,10 @@
|
|||||||
// Uses official SubMiner Discord app assets for polished card visuals.
|
// Uses official SubMiner Discord app assets for polished card visuals.
|
||||||
// ==========================================
|
// ==========================================
|
||||||
"discordPresence": {
|
"discordPresence": {
|
||||||
"enabled": false, // Enable optional Discord Rich Presence updates. Values: true | false
|
"enabled": true, // Enable optional Discord Rich Presence updates. Values: true | false
|
||||||
|
"presenceStyle": "default", // Presence card text preset: "default" (clean bilingual), "meme" (Mining and crafting), "japanese" (fully JP), or "minimal". Values: default | meme | japanese | minimal
|
||||||
"updateIntervalMs": 3000, // Minimum interval between presence payload updates.
|
"updateIntervalMs": 3000, // Minimum interval between presence payload updates.
|
||||||
"debounceMs": 750, // Debounce delay used to collapse bursty presence updates.
|
"debounceMs": 750 // Debounce delay used to collapse bursty presence updates.
|
||||||
}, // Optional Discord Rich Presence activity card updates for current playback/study session.
|
}, // Optional Discord Rich Presence activity card updates for current playback/study session.
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
@@ -315,12 +812,33 @@
|
|||||||
"queueCap": 1000, // In-memory write queue cap before overflow policy applies.
|
"queueCap": 1000, // In-memory write queue cap before overflow policy applies.
|
||||||
"payloadCapBytes": 256, // Max JSON payload size per event before truncation.
|
"payloadCapBytes": 256, // Max JSON payload size per event before truncation.
|
||||||
"maintenanceIntervalMs": 86400000, // Maintenance cadence (prune + rollup + vacuum checks).
|
"maintenanceIntervalMs": 86400000, // Maintenance cadence (prune + rollup + vacuum checks).
|
||||||
|
"retentionMode": "preset", // Retention mode (`preset` uses preset values, `advanced` uses explicit values). Values: preset | advanced
|
||||||
|
"retentionPreset": "balanced", // Retention preset when `retentionMode` is `preset`. Values: minimal | balanced | deep-history
|
||||||
"retention": {
|
"retention": {
|
||||||
"eventsDays": 7, // Raw event retention window in days.
|
"eventsDays": 0, // Raw event retention window in days. Use 0 to keep all.
|
||||||
"telemetryDays": 30, // Telemetry retention window in days.
|
"telemetryDays": 0, // Telemetry retention window in days. Use 0 to keep all.
|
||||||
"dailyRollupsDays": 365, // Daily rollup retention window in days.
|
"sessionsDays": 0, // Session retention window in days. Use 0 to keep all.
|
||||||
"monthlyRollupsDays": 1825, // Monthly rollup retention window in days.
|
"dailyRollupsDays": 0, // Daily rollup retention window in days. Use 0 to keep all.
|
||||||
"vacuumIntervalDays": 7, // Minimum days between VACUUM runs.
|
"monthlyRollupsDays": 0, // Monthly rollup retention window in days. Use 0 to keep all.
|
||||||
|
"vacuumIntervalDays": 0 // Minimum days between VACUUM runs. Use 0 to disable.
|
||||||
}, // Retention setting.
|
}, // Retention setting.
|
||||||
|
"lifetimeSummaries": {
|
||||||
|
"global": true, // Maintain global lifetime stats rows. Values: true | false
|
||||||
|
"anime": true, // Maintain per-anime lifetime stats rows. Values: true | false
|
||||||
|
"media": true // Maintain per-media lifetime stats rows. Values: true | false
|
||||||
|
} // Lifetime summaries setting.
|
||||||
}, // Enable/disable immersion tracking.
|
}, // Enable/disable immersion tracking.
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// Stats Dashboard
|
||||||
|
// Local immersion stats dashboard served on localhost and available as an in-app overlay.
|
||||||
|
// Uses the immersion tracking database for overview, trends, sessions, and vocabulary views.
|
||||||
|
// ==========================================
|
||||||
|
"stats": {
|
||||||
|
"toggleKey": "Backquote", // Key code to toggle the stats overlay.
|
||||||
|
"markWatchedKey": "KeyW", // Key code to mark the current video as watched and advance to the next playlist entry.
|
||||||
|
"serverPort": 6969, // Port for the stats HTTP server.
|
||||||
|
"autoStartServer": true, // Automatically start the stats server on launch. Values: true | false
|
||||||
|
"autoOpenBrowser": false // Automatically open the stats dashboard in a browser when the server starts. Values: true | false
|
||||||
|
} // Local immersion stats dashboard served on localhost and available as an in-app overlay.
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
node_modules/
|
||||||
|
.vitepress/cache/
|
||||||
|
.vitepress/dist/
|
||||||
|
.DS_Store
|
||||||
|
.codex/
|
||||||
|
.agents/
|
||||||
|
**/CLAUDE.md
|
||||||
|
.claude/*
|
||||||
@@ -0,0 +1,322 @@
|
|||||||
|
import { spawnSync } from 'node:child_process';
|
||||||
|
import { existsSync } from 'node:fs';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import type { DefaultTheme, HeadConfig, TransformContext, UserConfig } from 'vitepress';
|
||||||
|
|
||||||
|
const DOCS_HOSTNAME = 'https://docs.subminer.moe';
|
||||||
|
const PLAUSIBLE_PROXY_HOSTNAME = 'https://worker.sudacode.com';
|
||||||
|
const PLAUSIBLE_SITE_SCRIPT_PATH = '/js/pa-h28Pn9ppgTJRmiSJlyPT6.js';
|
||||||
|
const PLAUSIBLE_ENDPOINT = `${PLAUSIBLE_PROXY_HOSTNAME}/api/event`;
|
||||||
|
const PLAUSIBLE_INIT_SCRIPT = [
|
||||||
|
'window.plausible=window.plausible||function(){(plausible.q=plausible.q||[]).push(arguments)},plausible.init=plausible.init||function(i){plausible.o=i||{}};',
|
||||||
|
`plausible.init({ endpoint: '${PLAUSIBLE_ENDPOINT}' });`,
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
type DocsChannel = 'stable-root' | 'stable-archive' | 'main';
|
||||||
|
|
||||||
|
function optionalEnv(value: string | undefined): string | undefined {
|
||||||
|
return value && value !== 'undefined' ? value : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const base = normalizeBase(optionalEnv(process.env.SUBMINER_DOCS_BASE) ?? '/');
|
||||||
|
const outDir = optionalEnv(process.env.SUBMINER_DOCS_OUT_DIR);
|
||||||
|
const docsSourceDir = optionalEnv(process.env.SUBMINER_DOCS_SOURCE_DIR) ?? process.cwd();
|
||||||
|
// The tracked `docs-site/` checkout, which stays a git working tree even when
|
||||||
|
// `docsSourceDir` points at an untracked release snapshot. Used for git lookups only.
|
||||||
|
const repoDocsDir = optionalEnv(process.env.SUBMINER_DOCS_REPO_DIR) ?? process.cwd();
|
||||||
|
const channel = normalizeChannel(optionalEnv(process.env.SUBMINER_DOCS_CHANNEL));
|
||||||
|
const docsVersion = optionalEnv(process.env.SUBMINER_DOCS_VERSION);
|
||||||
|
|
||||||
|
function normalizeBase(value: string): string {
|
||||||
|
if (!value || value === '/') return '/';
|
||||||
|
return `/${value.replace(/^\/+|\/+$/g, '')}/`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeChannel(value: string | undefined): DocsChannel {
|
||||||
|
if (value === 'main' || value === 'stable-archive') return value;
|
||||||
|
return 'stable-root';
|
||||||
|
}
|
||||||
|
|
||||||
|
function withDocsBase(path: string): string {
|
||||||
|
if (/^[a-z]+:\/\//i.test(path)) return path;
|
||||||
|
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
||||||
|
if (base === '/') return normalizedPath;
|
||||||
|
return `${base.replace(/\/$/, '')}${normalizedPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pageToRoute(page: string): string | null {
|
||||||
|
if (page === '404.md') return null;
|
||||||
|
|
||||||
|
const route = page
|
||||||
|
.replace(/(^|\/)index\.md$/, '')
|
||||||
|
.replace(/\.md$/, '')
|
||||||
|
.replace(/\/$/, '');
|
||||||
|
return route ? `/${route}` : '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only the root channel is indexable. `main` and every /v/<version>/ archive are
|
||||||
|
// near-verbatim copies of it, so they own their URL via a self-referential canonical
|
||||||
|
// and are excluded from the index instead of being consolidated onto root. Uniform
|
||||||
|
// self-canonical plus noindex avoids mixing noindex with a cross-page canonical,
|
||||||
|
// which Google treats as a conflicting signal.
|
||||||
|
const isIndexableChannel = channel === 'stable-root';
|
||||||
|
|
||||||
|
function pageToCanonicalHref(page: string): string | null {
|
||||||
|
const route = pageToRoute(page);
|
||||||
|
if (!route) return null;
|
||||||
|
|
||||||
|
if (!isIndexableChannel) {
|
||||||
|
return `${DOCS_HOSTNAME}${canonicalRouteWithBase(route)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return route === '/' ? `${DOCS_HOSTNAME}/` : `${DOCS_HOSTNAME}${route}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function canonicalRouteWithBase(route: string): string {
|
||||||
|
const routeWithBase = withDocsBase(route);
|
||||||
|
return route === '/' ? routeWithBase : routeWithBase.replace(/\/$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformPageHead({ page }: TransformContext): HeadConfig[] {
|
||||||
|
const href = pageToCanonicalHref(page);
|
||||||
|
const head: HeadConfig[] = href ? [['link', { rel: 'canonical', href }]] : [];
|
||||||
|
|
||||||
|
// Crawlable so links still pass through, but out of the index: ~30 archived copies
|
||||||
|
// of every page otherwise soak up the crawl budget the current docs need.
|
||||||
|
if (!isIndexableChannel) {
|
||||||
|
head.push(['meta', { name: 'robots', content: 'noindex,follow' }]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
function linkToPagePath(link: string): string | null {
|
||||||
|
if (!link.startsWith('/') || link.startsWith('/v/') || link.startsWith('/main/')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const withoutHash = link.split('#')[0] ?? '/';
|
||||||
|
const withoutQuery = withoutHash.split('?')[0] ?? '/';
|
||||||
|
const route = withoutQuery.replace(/^\/+|\/+$/g, '');
|
||||||
|
return route ? `${route}.md` : 'index.md';
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasPageForLink(link: string): boolean {
|
||||||
|
const pagePath = linkToPagePath(link);
|
||||||
|
if (!pagePath) return true;
|
||||||
|
return existsSync(join(docsSourceDir, pagePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterNav(items: DefaultTheme.NavItem[]): DefaultTheme.NavItem[] {
|
||||||
|
return items
|
||||||
|
.map((item) => {
|
||||||
|
if ('items' in item && item.items) {
|
||||||
|
return { ...item, items: filterNav(item.items as DefaultTheme.NavItem[]) };
|
||||||
|
}
|
||||||
|
if ('link' in item && item.link && !hasPageForLink(item.link)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
})
|
||||||
|
.filter((item): item is DefaultTheme.NavItem => Boolean(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterSidebar(items: DefaultTheme.SidebarItem[]): DefaultTheme.SidebarItem[] {
|
||||||
|
return items
|
||||||
|
.map((item) => {
|
||||||
|
const filteredChildren = item.items ? filterSidebar(item.items) : undefined;
|
||||||
|
if (item.link && !hasPageForLink(item.link)) return null;
|
||||||
|
if (item.items && filteredChildren?.length === 0 && !item.link) return null;
|
||||||
|
return { ...item, items: filteredChildren };
|
||||||
|
})
|
||||||
|
.filter((item): item is DefaultTheme.SidebarItem => Boolean(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version navigation targets other builds (root, `/main/`, `/versions`), so it links to
|
||||||
|
// production by absolute URL: base-relative links would stay inside this build, and
|
||||||
|
// `target: '_self'` makes the VitePress router do a full page load. The list is
|
||||||
|
// deliberately static; the full release list lives on the root-only `/versions` page
|
||||||
|
// so frozen `/v/<version>/` archives never need a rebuild when a new tag ships.
|
||||||
|
const versionItems: DefaultTheme.NavItemWithLink[] = [
|
||||||
|
{ text: 'Latest stable', link: `${DOCS_HOSTNAME}/` },
|
||||||
|
{ text: 'main', link: `${DOCS_HOSTNAME}/main/` },
|
||||||
|
{ text: 'All versions', link: `${DOCS_HOSTNAME}/versions` },
|
||||||
|
].map((item) => ({ ...item, target: '_self', noIcon: true }));
|
||||||
|
|
||||||
|
function sitemapUrlToPage(url: string): string {
|
||||||
|
const route = url.replace(/\.html$/, '').replace(/^\/+|\/+$/g, '');
|
||||||
|
return route ? `${route}.md` : 'index.md';
|
||||||
|
}
|
||||||
|
|
||||||
|
// VitePress derives <lastmod> by running `git log` inside its source dir. Production
|
||||||
|
// builds point that at an untracked snapshot of the release tag, so the lookup comes
|
||||||
|
// back empty and the sitemap ships with no dates at all. Resolve it from the tracked
|
||||||
|
// checkout at the ref being built instead.
|
||||||
|
function lastModifiedFor(url: string): string | undefined {
|
||||||
|
const ref = docsVersion && docsVersion !== 'main' ? docsVersion : 'HEAD';
|
||||||
|
const result = spawnSync('git', ['log', '-1', '--format=%cI', ref, '--', sitemapUrlToPage(url)], {
|
||||||
|
cwd: repoDocsDir,
|
||||||
|
encoding: 'utf8',
|
||||||
|
});
|
||||||
|
|
||||||
|
return (result.status === 0 && result.stdout.trim()) || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only the root channel publishes a sitemap. Archived and `main` builds would emit
|
||||||
|
// their own copies listing the same canonical URLs, which just advertises the
|
||||||
|
// duplicate trees we are trying to keep out of the index.
|
||||||
|
const sitemap: UserConfig['sitemap'] = isIndexableChannel
|
||||||
|
? {
|
||||||
|
hostname: DOCS_HOSTNAME,
|
||||||
|
transformItems(items) {
|
||||||
|
return items
|
||||||
|
.filter((item) => item.url !== 'README' && item.url !== `${DOCS_HOSTNAME}/README`)
|
||||||
|
.map((item) => ({ ...item, lastmod: item.lastmod ?? lastModifiedFor(item.url) }));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const nav: DefaultTheme.NavItem[] = [
|
||||||
|
{ text: 'Home', link: '/' },
|
||||||
|
{ text: 'Get Started', link: '/installation' },
|
||||||
|
{ text: 'Mining', link: '/mining-workflow' },
|
||||||
|
{ text: 'Configuration', link: '/configuration' },
|
||||||
|
{ text: 'Changelog', link: '/changelog' },
|
||||||
|
{ text: 'Troubleshooting', link: '/troubleshooting' },
|
||||||
|
{ text: docsVersion ?? 'main', items: versionItems },
|
||||||
|
];
|
||||||
|
|
||||||
|
const sidebar: DefaultTheme.SidebarItem[] = [
|
||||||
|
{
|
||||||
|
text: 'Getting Started',
|
||||||
|
items: [
|
||||||
|
{ text: 'Overview', link: '/' },
|
||||||
|
{ text: 'Installation', link: '/installation' },
|
||||||
|
{ text: 'Usage', link: '/usage' },
|
||||||
|
{ text: 'Mining Workflow', link: '/mining-workflow' },
|
||||||
|
{ text: 'Launcher Script', link: '/launcher-script' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Reference',
|
||||||
|
items: [
|
||||||
|
{ text: 'Configuration', link: '/configuration' },
|
||||||
|
{ text: 'Keyboard Shortcuts', link: '/shortcuts' },
|
||||||
|
{ text: 'Subtitle Annotations', link: '/subtitle-annotations' },
|
||||||
|
{ text: 'Subtitle Sidebar', link: '/subtitle-sidebar' },
|
||||||
|
{ text: 'Immersion Tracking', link: '/immersion-tracking' },
|
||||||
|
{ text: 'Troubleshooting', link: '/troubleshooting' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Integrations',
|
||||||
|
items: [
|
||||||
|
{ text: 'MPV Plugin', link: '/mpv-plugin' },
|
||||||
|
{ text: 'Anki', link: '/anki-integration' },
|
||||||
|
{ text: 'Jellyfin', link: '/jellyfin-integration' },
|
||||||
|
{ text: 'YouTube', link: '/youtube-integration' },
|
||||||
|
{ text: 'Jimaku', link: '/jimaku-integration' },
|
||||||
|
{ text: 'Subtitle Generation', link: '/subtitle-generation' },
|
||||||
|
{ text: 'TsukiHime', link: '/tsukihime-integration' },
|
||||||
|
{ text: 'AniList', link: '/anilist-integration' },
|
||||||
|
{ text: 'AniSkip', link: '/aniskip-integration' },
|
||||||
|
{ text: 'Character Dictionary', link: '/character-dictionary' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Development',
|
||||||
|
items: [
|
||||||
|
{ text: 'Building & Testing', link: '/development' },
|
||||||
|
{ text: 'Architecture', link: '/architecture' },
|
||||||
|
{ text: 'IPC + Runtime Contracts', link: '/ipc-contracts' },
|
||||||
|
{ text: 'WebSocket + Texthooker API', link: '/websocket-texthooker-api' },
|
||||||
|
{ text: 'Changelog', link: '/changelog' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const config: UserConfig = {
|
||||||
|
title: 'SubMiner Docs',
|
||||||
|
description:
|
||||||
|
'SubMiner: an MPV immersion-mining overlay with Yomitan and AnkiConnect integration.',
|
||||||
|
base,
|
||||||
|
...(outDir ? { outDir } : {}),
|
||||||
|
head: [
|
||||||
|
['link', { rel: 'preconnect', href: PLAUSIBLE_PROXY_HOSTNAME }],
|
||||||
|
[
|
||||||
|
'script',
|
||||||
|
{
|
||||||
|
async: '',
|
||||||
|
src: `${PLAUSIBLE_PROXY_HOSTNAME}${PLAUSIBLE_SITE_SCRIPT_PATH}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
['script', {}, PLAUSIBLE_INIT_SCRIPT],
|
||||||
|
['link', { rel: 'icon', href: withDocsBase('/favicon.ico'), sizes: 'any' }],
|
||||||
|
[
|
||||||
|
'link',
|
||||||
|
{
|
||||||
|
rel: 'icon',
|
||||||
|
type: 'image/png',
|
||||||
|
href: withDocsBase('/favicon-32x32.png'),
|
||||||
|
sizes: '32x32',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'link',
|
||||||
|
{
|
||||||
|
rel: 'icon',
|
||||||
|
type: 'image/png',
|
||||||
|
href: withDocsBase('/favicon-16x16.png'),
|
||||||
|
sizes: '16x16',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'link',
|
||||||
|
{
|
||||||
|
rel: 'apple-touch-icon',
|
||||||
|
href: withDocsBase('/apple-touch-icon.png'),
|
||||||
|
sizes: '180x180',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
appearance: 'dark',
|
||||||
|
cleanUrls: true,
|
||||||
|
metaChunk: true,
|
||||||
|
sitemap,
|
||||||
|
transformHead: transformPageHead,
|
||||||
|
lastUpdated: true,
|
||||||
|
srcExclude: ['subagents/**', 'README.md'],
|
||||||
|
markdown: {
|
||||||
|
theme: {
|
||||||
|
light: 'catppuccin-latte',
|
||||||
|
dark: 'catppuccin-macchiato',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
themeConfig: {
|
||||||
|
logo: {
|
||||||
|
light: '/assets/SubMiner.png',
|
||||||
|
dark: '/assets/SubMiner.png',
|
||||||
|
},
|
||||||
|
siteTitle: 'SubMiner Docs',
|
||||||
|
nav: filterNav(nav),
|
||||||
|
sidebar: filterSidebar(sidebar),
|
||||||
|
search: {
|
||||||
|
provider: 'local',
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
message: 'Released under the GPL-3.0 License.',
|
||||||
|
copyright: 'Copyright © 2026-present sudacode',
|
||||||
|
},
|
||||||
|
editLink: {
|
||||||
|
pattern: 'https://github.com/ksyasuda/SubMiner/edit/main/docs-site/:path',
|
||||||
|
text: 'Edit this page on GitHub',
|
||||||
|
},
|
||||||
|
outline: { level: [2, 3], label: 'On this page' },
|
||||||
|
externalLinkIcon: true,
|
||||||
|
docFooter: { prev: 'Previous', next: 'Next' },
|
||||||
|
returnToTopLabel: 'Back to top',
|
||||||
|
socialLinks: [{ icon: 'github', link: 'https://github.com/ksyasuda/SubMiner' }],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<script setup>
|
||||||
|
import DefaultTheme from 'vitepress/theme';
|
||||||
|
import StatusLine from './components/StatusLine.vue';
|
||||||
|
import BlinkingCursor from './components/BlinkingCursor.vue';
|
||||||
|
|
||||||
|
const { Layout } = DefaultTheme;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Layout>
|
||||||
|
<template #home-hero-info-after>
|
||||||
|
<BlinkingCursor />
|
||||||
|
</template>
|
||||||
|
<template #layout-bottom>
|
||||||
|
<StatusLine />
|
||||||
|
</template>
|
||||||
|
</Layout>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
<span class="tui-cursor" aria-hidden="true">█</span>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<script setup>
|
||||||
|
import { useRoute, useData } from 'vitepress';
|
||||||
|
import { computed, onMounted, ref } from 'vue';
|
||||||
|
import { formatStatusLineDate, formatStatusLineFilePath } from '../status-line';
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const { frontmatter } = useData();
|
||||||
|
|
||||||
|
const mode = computed(() => {
|
||||||
|
const layout = frontmatter.value.layout;
|
||||||
|
if (layout === 'home') return 'HOME';
|
||||||
|
return 'NORMAL';
|
||||||
|
});
|
||||||
|
|
||||||
|
const filePath = computed(() => {
|
||||||
|
return formatStatusLineFilePath(route.path);
|
||||||
|
});
|
||||||
|
|
||||||
|
const section = computed(() => {
|
||||||
|
const path = route.path;
|
||||||
|
if (path === '/') return 'root';
|
||||||
|
const parts = path.split('/').filter(Boolean);
|
||||||
|
return parts[0] || 'root';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set on the client only, so the prerendered HTML never bakes in the build date.
|
||||||
|
const today = ref('');
|
||||||
|
onMounted(() => {
|
||||||
|
today.value = formatStatusLineDate(new Date());
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<footer class="tui-statusline" aria-label="Page info">
|
||||||
|
<div class="tui-statusline__left">
|
||||||
|
<span class="tui-statusline__mode" :data-mode="mode">{{ mode }}</span>
|
||||||
|
<span class="tui-statusline__sep"></span>
|
||||||
|
<span class="tui-statusline__file">{{ filePath }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="tui-statusline__right">
|
||||||
|
<span class="tui-statusline__section">{{ section }}</span>
|
||||||
|
<span class="tui-statusline__sep"></span>
|
||||||
|
<span v-if="today" class="tui-statusline__date">{{ today }}</span>
|
||||||
|
<span v-if="today" class="tui-statusline__sep"></span>
|
||||||
|
<span class="tui-statusline__branch">GPL-3.0</span>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</template>
|
||||||
@@ -2,7 +2,9 @@ import DefaultTheme from 'vitepress/theme';
|
|||||||
import { useRoute } from 'vitepress';
|
import { useRoute } from 'vitepress';
|
||||||
import { nextTick, onMounted, watch } from 'vue';
|
import { nextTick, onMounted, watch } from 'vue';
|
||||||
import '@catppuccin/vitepress/theme/macchiato/mauve.css';
|
import '@catppuccin/vitepress/theme/macchiato/mauve.css';
|
||||||
|
import './tui-theme.css';
|
||||||
import './mermaid-modal.css';
|
import './mermaid-modal.css';
|
||||||
|
import TuiLayout from './TuiLayout.vue';
|
||||||
|
|
||||||
let mermaidLoader: Promise<any> | null = null;
|
let mermaidLoader: Promise<any> | null = null;
|
||||||
const MERMAID_MODAL_ID = 'mermaid-diagram-modal';
|
const MERMAID_MODAL_ID = 'mermaid-diagram-modal';
|
||||||
@@ -112,6 +114,11 @@ async function getMermaid() {
|
|||||||
startOnLoad: false,
|
startOnLoad: false,
|
||||||
securityLevel: 'loose',
|
securityLevel: 'loose',
|
||||||
theme: 'base',
|
theme: 'base',
|
||||||
|
flowchart: {
|
||||||
|
padding: 16,
|
||||||
|
nodeSpacing: 30,
|
||||||
|
rankSpacing: 40,
|
||||||
|
},
|
||||||
themeVariables: {
|
themeVariables: {
|
||||||
background: '#24273a',
|
background: '#24273a',
|
||||||
primaryColor: '#363a4f',
|
primaryColor: '#363a4f',
|
||||||
@@ -177,7 +184,8 @@ async function renderMermaidBlocks() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
...DefaultTheme,
|
Layout: TuiLayout,
|
||||||
|
extends: DefaultTheme,
|
||||||
setup() {
|
setup() {
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const render = () => {
|
const render = () => {
|
||||||
@@ -1,11 +1,20 @@
|
|||||||
.mermaid-interactive {
|
.mermaid-interactive {
|
||||||
cursor: zoom-in;
|
cursor: zoom-in;
|
||||||
|
transition: outline-color 180ms ease;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-interactive svg {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mermaid-interactive:focus-visible {
|
.mermaid-interactive:focus-visible {
|
||||||
outline: 2px solid var(--vp-c-brand-1);
|
outline: 2px solid var(--vp-c-brand-1);
|
||||||
outline-offset: 4px;
|
outline-offset: 4px;
|
||||||
border-radius: 6px;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mermaid-modal {
|
.mermaid-modal {
|
||||||
@@ -23,6 +32,8 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
background: rgba(0, 0, 0, 0.72);
|
background: rgba(0, 0, 0, 0.72);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
-webkit-backdrop-filter: blur(4px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mermaid-modal__dialog {
|
.mermaid-modal__dialog {
|
||||||
@@ -32,9 +43,9 @@
|
|||||||
width: min(96vw, 1800px);
|
width: min(96vw, 1800px);
|
||||||
max-height: 92vh;
|
max-height: 92vh;
|
||||||
border: 1px solid var(--vp-c-border);
|
border: 1px solid var(--vp-c-border);
|
||||||
border-radius: 12px;
|
border-radius: 0;
|
||||||
background: var(--vp-c-bg);
|
background: var(--vp-c-bg);
|
||||||
box-shadow: var(--vp-shadow-4);
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 0 24px 64px rgba(0, 0, 0, 0.2);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,11 +55,19 @@
|
|||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
border: 1px solid var(--vp-c-border);
|
border: 1px solid var(--vp-c-border);
|
||||||
border-radius: 6px;
|
border-radius: 0;
|
||||||
padding: 4px 10px;
|
padding: 4px 10px;
|
||||||
background: var(--vp-c-bg-soft);
|
background: var(--vp-c-bg-soft);
|
||||||
color: var(--vp-c-text-1);
|
color: var(--vp-c-text-1);
|
||||||
font-size: 14px;
|
font-family: var(--tui-font-mono);
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 180ms ease, color 180ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-modal__close:hover {
|
||||||
|
border-color: var(--vp-c-brand-1);
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mermaid-modal__content {
|
.mermaid-modal__content {
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { expect, test } from 'bun:test';
|
||||||
|
import { formatStatusLineDate, formatStatusLineFilePath } from './status-line';
|
||||||
|
|
||||||
|
test('status line file path formats root home as index markdown', () => {
|
||||||
|
expect(formatStatusLineFilePath('/')).toBe('index.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('status line file path formats version archive home without trailing slash', () => {
|
||||||
|
expect(formatStatusLineFilePath('/v/0.12.0/')).toBe('v/0.12.0.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('status line file path keeps normal docs routes as markdown files', () => {
|
||||||
|
expect(formatStatusLineFilePath('/v/0.12.0/configuration')).toBe('v/0.12.0/configuration.md');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('status line date uses the local calendar day, zero padded', () => {
|
||||||
|
expect(formatStatusLineDate(new Date(2026, 0, 5, 23, 59))).toBe('2026-01-05');
|
||||||
|
});
|
||||||