diff --git a/.cursor/debug-7b27d8.log b/.cursor/debug-7b27d8.log
new file mode 100644
index 000000000..495830bdf
--- /dev/null
+++ b/.cursor/debug-7b27d8.log
@@ -0,0 +1,144 @@
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"iidx-sp","userID":1,"sessionID":"Q578fa997a13b92203dbc59082a452636631ff691","type":"Created","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":0},"timestamp":1778174638000,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qc252f411274450ce5698af2907997cbab60105d2","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":0},"timestamp":1778174660089,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q5d5e46543de37de7144aabde86aaeaf00498195a","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":1},"timestamp":1778174660094,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qc37df566339572b6fee3ca5edd4b3bf0314e29c5","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":2},"timestamp":1778174660099,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q8cbcd83ef2ee60cecb791c09e573cacb82614819","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":3},"timestamp":1778174660105,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q189cf02b87c556a9b3f871e303758dd88d16c96a","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":4},"timestamp":1778174660108,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qe40bb6742d397db1bfedc9aa2da386994054cb8e","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":5},"timestamp":1778174660112,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q7d9e5a5aa58b424f33253e67721fb416d43c0a8b","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":6},"timestamp":1778174660115,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qcafb063529a7746b14be61b5f8b8f785b4c0bb9d","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":7},"timestamp":1778174660121,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qdde44db5089244cc55f662c6a41ffb2fb714a8b8","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":8},"timestamp":1778174660131,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q6e8d8cbad5078faf2b592504808d09e213ed0129","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":9},"timestamp":1778174660136,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q8559d0c86bf3b2f8b15801d36f27fb876ea552b3","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":10},"timestamp":1778174660142,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qe37e28c4561537d764d26d3084f544d4f07fc794","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":11},"timestamp":1778174660147,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q6657d8805d106093b0dddb14cb504b937f132760","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":12},"timestamp":1778174660155,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q48f3114dd0c29c80252fd65a670d97fa4a600f82","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":13},"timestamp":1778174660162,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q990c934c1383480e3d37baf91bd6a765cf8b3a2a","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":14},"timestamp":1778174660165,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q7a417c5a2159bf29e7a01e5250a0711528697f8f","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":15},"timestamp":1778174660170,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q7e1c4f468c77e2fefc8f7c6883e2de759bdd25b9","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":16},"timestamp":1778174660174,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qee252e93a7d251e87e9a7fe6c17139b2d2f99e7a","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":17},"timestamp":1778174660178,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qa105eb34d8423b192e43c238f8ff91083e1bffc9","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":18},"timestamp":1778174660181,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q27daaebb4468a50aae51882418658cc1aa106a34","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":19},"timestamp":1778174660184,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q8c121fa8b73857c8ee117e700842b866e3baf6a7","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":20},"timestamp":1778174660199,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q7210852aa9d6b82fe8d99da03f02d86782285985","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":21},"timestamp":1778174660205,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qcfb1824558a1a99b1538dbd7432289a8be57b3e6","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":22},"timestamp":1778174660209,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q49efff3408ecd0c5b7e41548875f8e443d0dd7f9","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":23},"timestamp":1778174660217,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qc2618e82e8d9911531d1020c4d467859c9214b36","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":24},"timestamp":1778174660223,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q0f170b6b312d00be302e5cc0d4285da541524fbe","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":25},"timestamp":1778174660227,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qdb008e8c2209f91ad53ec0cdee933f67f6e5331e","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":26},"timestamp":1778174660233,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q045d94351296c45a1eaabae9a30029dc68318e19","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":27},"timestamp":1778174660236,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q39b379df7a10f4a72574f3680b52a7ef69751af4","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":28},"timestamp":1778174660245,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q811a4764a3d2534fb4b1da6cbaea1d407a60d0b5","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":29},"timestamp":1778174660248,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qb79f15ac66e1965c35be2004006e577e071b87b7","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":30},"timestamp":1778174660252,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q0f4611dfe6752e90e0ea7ad0a339aed51391d94a","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":31},"timestamp":1778174660257,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q9f0a302cbb6160468da3399669832c52ae7c5910","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":32},"timestamp":1778174660260,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q9418f31ea6a52ffaca93f0657abab15f648d3f24","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":33},"timestamp":1778174660263,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qb4db5061c31ee7743fd4c592a3136cee6c53225d","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":34},"timestamp":1778174660267,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qa7a6babb5c27931e45e0396d81c95272beee00f8","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":35},"timestamp":1778174660270,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q927681faa73a41ffbcc2b2e51bb3220656f7a27c","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":36},"timestamp":1778174660278,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q1edafb103d7f842bd3164bf2d77cca323ddc24d5","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":37},"timestamp":1778174660287,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qc93f8402727171169df28aa12c1852e59a00b0b4","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":38},"timestamp":1778174660294,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qae0736c538299e163942c0fd4fd5c34bcd8ab7c5","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":39},"timestamp":1778174660299,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qa8723107d169ea4e765d28d9dec764e119d4879e","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":40},"timestamp":1778174660303,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qe8e555ad077e1897eab4ece65580c27f1fd8b0bb","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":41},"timestamp":1778174660309,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qfef6ca0ce28485e788d6562db2527b7a8ddce498","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":42},"timestamp":1778174660314,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q5c68d2fe24dc80dfd015b2abcb89e4eba774feaf","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":43},"timestamp":1778174660317,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q4d88a6581e28b7024f020673feb45b75c1d101b3","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":44},"timestamp":1778174660321,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q7d8887d5d3fd265b73b1b29363d305ce13360c94","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":45},"timestamp":1778174660327,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q6f0fb12cb6e8aaf252a1d6040e00d391ea8464d4","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":46},"timestamp":1778174660345,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qd4ec361100e6590f313350b9e3e9150d6387e2ba","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":47},"timestamp":1778174660348,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qe0381db5230e8b7dd310bf2d23d8427dbbb84822","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":48},"timestamp":1778174660350,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q31e15ba0f00783be89720b1aec46550b58976d3b","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":49},"timestamp":1778174660354,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qba5cc5abba6d6f03e6868628b3a4de1461abbc4c","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":50},"timestamp":1778174660360,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qc9d38a91243fdd685b83f61292103dc53da0d4cf","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":51},"timestamp":1778174660365,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q8dabfc9ce6038eb05610efabd1bbe169fe060c46","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":52},"timestamp":1778174660369,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q44c21d76c1f0aceca460bbd6dd9f32e3ba5c7732","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":53},"timestamp":1778174660372,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qba93137b42cdac7a6a33e6b0b8936879e526089c","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":54},"timestamp":1778174660378,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qac474a57764cbaeb6b055f0c98d12bd1dfc75497","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":55},"timestamp":1778174660383,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q54aed62223d5f4a774bbf7283e1d71bffa6a8877","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":56},"timestamp":1778174660389,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qed26c346b3a65815a31b5228e4e632f42f62fa39","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":57},"timestamp":1778174660401,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qaf7a4851b6f7ddf6e9c186c8418b6130bad70798","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":58},"timestamp":1778174660405,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q801affd326b159befcc5ec230f6eac3149603622","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":59},"timestamp":1778174660415,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qc69f6c9c7ab8ecdbcad074aab2f796f052f5d0f8","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":60},"timestamp":1778174660430,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q8b639b6300480f0ecfd5db79b36d2e3c132a54ac","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":61},"timestamp":1778174660438,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qf85158125f1aeaea09a2827da2b3d7d45aba72bc","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":62},"timestamp":1778174660443,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qd9ea6889198ee5e8a099baa6c91bcffa7eca95c3","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":63},"timestamp":1778174660449,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q0585eab487fb017863aaac9f8768e9d1b72cc123","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":64},"timestamp":1778174660453,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q567de464814f23d575e9004f505e1a322be5de31","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":65},"timestamp":1778174660456,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q0b7df3b5afa5e0b50e42c67639b43f175072ce22","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":66},"timestamp":1778174660462,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qdc9e3f944ec4759285e25a25c7a372303c7e18b9","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":67},"timestamp":1778174660465,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q38e82752ad57d923af66a12471ade3d9b883f958","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":68},"timestamp":1778174660469,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q452773b20751ea073659360a00cec1b3861b85ef","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":69},"timestamp":1778174660474,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qd55da19de534bd8038ed3e52b82a890567b6ad19","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":70},"timestamp":1778174660482,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q314a9e746a8b962a00703e9d84a93190c0e915f3","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":71},"timestamp":1778174660487,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qfa374369968d49856bca05e82f36b4d7145ed1ec","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":72},"timestamp":1778174660490,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q76b0603b5a47f4a0236c9087dfbbfeba1fb3bc7b","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":73},"timestamp":1778174660506,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q203f0fe1b402e9a0448ac39e6cd1db01d511a50a","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":74},"timestamp":1778174660514,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q8c27ac1b60300274df3d669847921d35d01289ae","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":75},"timestamp":1778174660520,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q25b244ba922b0d4f1092bf419ea9788b66708f7c","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":76},"timestamp":1778174660524,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q70d5b9a4d9b79fff4f66b8ae300cf64fc488f02a","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":77},"timestamp":1778174660531,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q20e1e28b721c3b0733cd924e720704962e54e3e8","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":78},"timestamp":1778174660553,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qadc97ca77718ccfb8bde7ac9593edaac4bb87a6f","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":79},"timestamp":1778174660569,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q4b2feb98be77e6882f84d6a9cbeca42887abdbf3","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":80},"timestamp":1778174660573,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q77b97d7a7b1bfda84da3f472cc32bacec267219f","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":81},"timestamp":1778174660581,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q424e90d547303ea4923e0258cfc2d8d392679cae","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":82},"timestamp":1778174660586,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q97f2bc192eeb70752c9e30cfe7dcde99bc47e690","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":83},"timestamp":1778174660590,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q59001766f9b8a52c8dcc97bf1d042b454708eae4","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":84},"timestamp":1778174660593,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q00e706d002c2e9ea6086371b935a50b282706d39","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":85},"timestamp":1778174660598,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q0086ff797f2b95359b9472c107f20ae075898b21","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":86},"timestamp":1778174660601,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qe989edaca5a2f24b8d604bf98ad03a48baf0f010","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":87},"timestamp":1778174660606,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q12ef1db3064ea85095e2ff9d74a45a21d7ccc9b5","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":88},"timestamp":1778174660622,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q660bd3bb616c87eeaa3df3b65f25f1a98c57af10","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":89},"timestamp":1778174660626,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q1d038349e88718833f4acd084ca6940a66deb9c4","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":90},"timestamp":1778174660636,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q11339defac1468d048e2b57b1b4d2cc67751ac86","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":91},"timestamp":1778174660639,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q44a76aab9debe86da79c4b870f83d88f76dc4118","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":92},"timestamp":1778174660642,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qe1adc2f7ec768f394813223ed21bd2b571969a4a","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":93},"timestamp":1778174660652,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q489661edfc15cfc92c3834eceb309f1e2d4bb893","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":94},"timestamp":1778174660660,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q489661edfc15cfc92c3834eceb309f1e2d4bb893","type":"Appended","repeatSessionInThisLoad":true,"willPush":false,"runId":"post-fix","priorReturnCount":95},"timestamp":1778174660671,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q06014b95faecab25d0f47b051f1e38d96af5ea7d","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":95},"timestamp":1778174660680,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qdcf58e13a815753fe75b38a564c8e581fef42d17","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":96},"timestamp":1778174660684,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q016e78c91f15ef13016dedc24a16c91284de26ba","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":97},"timestamp":1778174660688,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q12b6a4887a80ecc13a74079376f1259c5cc40e86","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":98},"timestamp":1778174660690,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q577397051d7966d96c1fb5cf649654f3b8dd6edb","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":99},"timestamp":1778174660699,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qa0b0c7a4bb6862f33febafdc80de2efe2d2adb2d","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":100},"timestamp":1778174660702,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qa17b785caf6e37dc996afe0f218baa008a63fb1d","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":101},"timestamp":1778174660705,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qa5bac01b00d12082158013a264ae8c2b10c837c6","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":102},"timestamp":1778174660708,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q6c6e8a526936a57adab446b28d870ae68bee3910","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":103},"timestamp":1778174660715,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q190d862f7853a94510ae603cefa8fec2d0010c17","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":104},"timestamp":1778174660719,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q68b4212ad3a72c9029272f3c8dba2909f6c717d5","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":105},"timestamp":1778174660724,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q61e5881c2e33e7b9c47afc832e54fa23c07657a3","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":106},"timestamp":1778174660729,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q151c94c4fb26eab70ff8db30652dac361d09d11b","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":107},"timestamp":1778174660736,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qb19bcd6ae2f4c2b1bee880fd6021214bfbf05604","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":108},"timestamp":1778174660741,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q952a8c756b74a7c556f19edb77628dbb04c0800b","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":109},"timestamp":1778174660744,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q673e224863b4bd6073c46591576644da16c4324f","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":110},"timestamp":1778174660750,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qd814902d29a9e34622ddc9dfa5089dc8c57f80bc","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":111},"timestamp":1778174660754,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q3ebba12414b002bbde0e9f5ca58134ca37071867","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":112},"timestamp":1778174660758,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qd073fc8da813e7fefa8fe1d6352714f48dd0ff7a","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":113},"timestamp":1778174660761,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q401bdb009b493618518bd7c4a066bd54acdabc10","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":114},"timestamp":1778174660768,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q2ce7b164c51b3bf0e4dcfd4b9b2228892815b856","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":115},"timestamp":1778174660772,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q0f405f56021d7115617fde39cadcaf6b89866705","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":116},"timestamp":1778174660778,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q9b7187fe54b150ef7e249c75257cd2b252084e85","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":117},"timestamp":1778174660797,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q70dc4b3da3675781ee314b6f44f95187cba7d3c1","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":118},"timestamp":1778174660809,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q60b1a04f2e28aab22c166378e89871125de6d563","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":119},"timestamp":1778174660813,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q7fc3d41514a6629a92e0c89674848e4224fed28d","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":120},"timestamp":1778174660818,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q4d84680f2603292d7f650921e4fdea73b0a6c6ca","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":121},"timestamp":1778174660822,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q3d517c038e36209ee4256306c34690fd53a8306e","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":122},"timestamp":1778174660826,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qaa50b6fadf96a3e12d60cca87f87cb5f7f462c69","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":123},"timestamp":1778174660843,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qc769036b5a38cb57c5ee1bf6f17360ed47f5ea54","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":124},"timestamp":1778174660858,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q84b63c6eefe648254d00a5ec71dc3808339dcce2","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":125},"timestamp":1778174660862,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q7a1ed0017220e8b96e861fa6289129b7e7f0cb33","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":126},"timestamp":1778174660867,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q86e26421f03118913a2f7a9adf630a3b10c879f9","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":127},"timestamp":1778174660873,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q7554af752b6d25d731396bdb31432e41e77ac694","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":128},"timestamp":1778174660877,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q100d03555b2f9ffbc2a9da5bb2e973b75356f9e7","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":129},"timestamp":1778174660883,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q9a42fb36c8479ca5c84e5d7c9e9d5103ae61e8fb","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":130},"timestamp":1778174660888,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q935ab1b2ff13b6a00f83bff9dc92e1b816eb8c98","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":131},"timestamp":1778174660893,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q97c8f2cf1053a36e4966d82265ecea04d1051f1e","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":132},"timestamp":1778174660904,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q5ded6f7f8d7f5d37223f280f2d900da118f7a193","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":133},"timestamp":1778174660908,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q7d5db734e4edef4477ef4cab6b6427fb9f45e4a8","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":134},"timestamp":1778174660911,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q948fd05d2c9ed5d49aa75daace5658c6a64aa74c","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":135},"timestamp":1778174660919,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q10363461fbf6a6be6dd31c1f1e978b0c0a9c08a9","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":136},"timestamp":1778174660923,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q55b15f2d894f8e3bcd768ed1510da91c2158992f","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":137},"timestamp":1778174660928,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qb87640a3371880e254627383c3b23cf5230527c9","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":138},"timestamp":1778174660936,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qf199ca3f596016185da0299200f1c4322656cce1","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":139},"timestamp":1778174660941,"hypothesisId":"H1"}
+{"sessionId":"7b27d8","location":"finalize-import-pg.ts:finalizeImportToPostgres:entry","message":"finalizeImportToPostgres called","data":{"importID":"4584b4feb70692dba1707a42c243a56a4b3be56b","runId":"post-fix"},"timestamp":1778174661244,"hypothesisId":"H2"}
+{"sessionId":"7b27d8","location":"finalize-import-pg.ts:import_session:preInsert","message":"import_session insert payload stats","data":{"importID":"4584b4feb70692dba1707a42c243a56a4b3be56b","createdLen":140,"uniqueSessionCount":140,"dupSessionIds":[],"runId":"post-fix"},"timestamp":1778174661246,"hypothesisId":"H1"}
diff --git a/.cursor/rules/sql-fully-qualified-fields.mdc b/.cursor/rules/sql-fully-qualified-fields.mdc
index d36562149..d169600b8 100644
--- a/.cursor/rules/sql-fully-qualified-fields.mdc
+++ b/.cursor/rules/sql-fully-qualified-fields.mdc
@@ -9,7 +9,7 @@ alwaysApply: false
In Kysely `select`, `where`, `orderBy`, `join` conditions, and raw `sql` fragments, **always** qualify column names with the table or join alias. Do not use bare column names when the query references a table (including single-table queries, for consistency and safer refactors).
```typescript
-// ❌ BAD — ambiguous once joins or CTEs are added; harder to grep
+// ❌ BAD - ambiguous once joins or CTEs are added; harder to grep
.select(["data", "chart_id", "user_id"])
.where("id", "=", id)
diff --git a/.cursor/skills/actions-and-pg-migration/SKILL.md b/.cursor/skills/actions-and-pg-migration/SKILL.md
index e3cc6fdb0..0fb977e9f 100644
--- a/.cursor/skills/actions-and-pg-migration/SKILL.md
+++ b/.cursor/skills/actions-and-pg-migration/SKILL.md
@@ -3,7 +3,7 @@ name: actions-and-pg-migration
description: Patterns for writing actions (MakeAction/MakeAnonAction), using the Postgres DB (Kysely), and migrating Express routes from MongoDB to Postgres in the Tachi server. Use when adding a new mutation, migrating a Mongo-backed router to Postgres, writing action files, or writing tests for actions or routers.
---
-# Actions & Postgres Migration — Tachi Server
+# Actions & Postgres Migration - Tachi Server
## Actions
@@ -31,9 +31,9 @@ import { ExpectedErr } from "bliss";
export const ACTION_MyAction = MakeAction(
"MY_ACTION",
async (taker, { fieldA, fieldB }) => {
- // taker.acct.id — authenticated user's numeric ID
- // taker.acct.username — their username
- // taker.ip — request IP (for audit log)
+ // taker.acct.id - authenticated user's numeric ID
+ // taker.acct.username - their username
+ // taker.ip - request IP (for audit log)
if (somethingWrong) {
throw new ExpectedErr(400, "Human-readable reason.");
@@ -50,18 +50,18 @@ export const ACTION_MyAction = MakeAction(
### ExpectedErr
-`ExpectedErr(code, reason)` is an intentional control-flow error. Throw it for 400/403/404/409 etc. The global Express error handler in `server.ts` (`MAIN_ERR_HANDLER`) catches it and returns `{ success: false, description: reason }` with the right HTTP status. **Never wrap action calls in `try/catch`** — let it propagate.
+`ExpectedErr(code, reason)` is an intentional control-flow error. Throw it for 400/403/404/409 etc. The global Express error handler in `server.ts` (`MAIN_ERR_HANDLER`) catches it and returns `{ success: false, description: reason }` with the right HTTP status. **Never wrap action calls in `try/catch`** - let it propagate.
### Calling actions from a router
```typescript
-// taker construction — same pattern everywhere:
+// taker construction - same pattern everywhere:
const user = req.session.tachi?.user;
if (!user) return res.status(401).json({ success: false, description: "..." });
const taker = { ip: req.ip, acct: { id: user.id, username: user.username } };
-// No try/catch — errors reach MAIN_ERR_HANDLER automatically
+// No try/catch - errors reach MAIN_ERR_HANDLER automatically
const result = await ACTION_MyAction(taker, { fieldA: body.fieldA });
return res.status(200).json({ success: true, description: "...", body: result });
@@ -123,7 +123,7 @@ await DB.selectFrom("priv_api_client")
// Insert
await DB.insertInto("priv_api_client").values({ ... }).execute();
-// Update (fetch after separately — don't use .returning() with prefixed column lists)
+// Update (fetch after separately - don't use .returning() with prefixed column lists)
await DB.updateTable("priv_api_client").set({ name: "New" }).where("client_id", "=", id).execute();
const updated = await GetClientByID(id); // re-fetch via existing query helper
@@ -146,12 +146,12 @@ Existing query helpers (e.g. `GetClientByID`) live in `src/utils/queries/`. Use
### Checklist
1. **Identify Mongo collections** → find the equivalent `priv_*` Postgres table.
-2. **Reads** — replace `MONGODB_KILL["collection"].find/findOne()` with Kysely selects. Use the existing `SELECT_*` + `To*Document` helpers from `src/lib/db-formats/`.
-3. **Mutations** — extract each write operation into a `MakeAction`-wrapped file in `src/actions/`. Add its signature to `ActionSignatures` in `actions.ts`.
-4. **Middleware** — update any middleware that does Mongo lookups (e.g. `GetClientFromID`) to use a query helper or direct Kysely query.
-5. **Remove Mongo import** — `MONGODB_KILL` should be gone from the file.
-6. **Ownership checks** — move them inside the action (`taker.acct.id === row.author`) rather than in Express middleware, so the action is self-contained.
-7. **Error handler** — throw `ExpectedErr` instead of returning early; no `try/catch` in routes needed.
+2. **Reads** - replace `MONGODB_KILL["collection"].find/findOne()` with Kysely selects. Use the existing `SELECT_*` + `To*Document` helpers from `src/lib/db-formats/`.
+3. **Mutations** - extract each write operation into a `MakeAction`-wrapped file in `src/actions/`. Add its signature to `ActionSignatures` in `actions.ts`.
+4. **Middleware** - update any middleware that does Mongo lookups (e.g. `GetClientFromID`) to use a query helper or direct Kysely query.
+5. **Remove Mongo import** - `MONGODB_KILL` should be gone from the file.
+6. **Ownership checks** - move them inside the action (`taker.acct.id === row.author`) rather than in Express middleware, so the action is self-contained.
+7. **Error handler** - throw `ExpectedErr` instead of returning early; no `try/catch` in routes needed.
### Permission columns
diff --git a/.cursor/skills/coverage-tools/SKILL.md b/.cursor/skills/coverage-tools/SKILL.md
index 850741e99..3c4e1dcc2 100644
--- a/.cursor/skills/coverage-tools/SKILL.md
+++ b/.cursor/skills/coverage-tools/SKILL.md
@@ -36,9 +36,9 @@ Implementation: workspace package `typescript/coverage-tools` (`bun run --filter
Edit `typescript/coverage-tools/src/manifest.ts`: add a `CoverageSource` with:
-- `id` — short name for `--packages`
-- `packageRoot` — e.g. `typescript/my-pkg`
-- `coverageFinal` — path to `coverage/coverage-final.json` relative to repo root (Vitest default unless `coverage.reportsDirectory` overrides)
+- `id` - short name for `--packages`
+- `packageRoot` - e.g. `typescript/my-pkg`
+- `coverageFinal` - path to `coverage/coverage-final.json` relative to repo root (Vitest default unless `coverage.reportsDirectory` overrides)
## Programmatic use
@@ -50,6 +50,6 @@ import { buildReport, COVERAGE_SOURCES } from "tachi-coverage-tools";
## Related files
-- `typescript/coverage-tools/src/manifest.ts` — source registry
-- `typescript/coverage-tools/src/report-coverage.ts` — CLI flags and `buildReport`
-- `Justfile-test` — `coverage-report` recipe
+- `typescript/coverage-tools/src/manifest.ts` - source registry
+- `typescript/coverage-tools/src/report-coverage.ts` - CLI flags and `buildReport`
+- `Justfile-test` - `coverage-report` recipe
diff --git a/.cursor/skills/db-formats/SKILL.md b/.cursor/skills/db-formats/SKILL.md
index d3cf35997..21e90c4ce 100644
--- a/.cursor/skills/db-formats/SKILL.md
+++ b/.cursor/skills/db-formats/SKILL.md
@@ -23,8 +23,8 @@ description: Guidance on using db-formats/ column lists (SELECT_*) and document
Every Postgres table that is returned through the API needs two things in `src/lib/db-formats/
.ts`:
-1. **`SELECT_*`** — a `const` array of `"table.column"` strings listing exactly the columns needed
-2. **`To*Document`** — a mapper function that converts a typed Kysely row to the matching `tachi-common` document type
+1. **`SELECT_*`** - a `const` array of `"table.column"` strings listing exactly the columns needed
+2. **`To*Document`** - a mapper function that converts a typed Kysely row to the matching `tachi-common` document type
```typescript
// src/lib/db-formats/my-table.ts
@@ -47,7 +47,7 @@ export function ToMyDocument(
}
```
-The `Selection` type ensures the mapper only receives columns that were actually selected — Kysely will error at compile time if the select array and mapper fall out of sync.
+The `Selection` type ensures the mapper only receives columns that were actually selected - Kysely will error at compile time if the select array and mapper fall out of sync.
---
@@ -116,7 +116,7 @@ const { clientSecret: _secret, ...publicDoc } = ToAPIClientDocument(row);
3. Export `ToDocument` typed with `Selection)[number]>`
4. Return the matching `tachi-common` document type (import from `"tachi-common"`)
5. Apply all naming/shape conventions from the table above
-6. Import and use `SELECT_*` + `To*Document` in the router/util — never `.selectAll()`
+6. Import and use `SELECT_*` + `To*Document` in the router/util - never `.selectAll()`
---
diff --git a/.cursor/skills/exhaustive-switch/SKILL.md b/.cursor/skills/exhaustive-switch/SKILL.md
index 3d8bb74cd..425101186 100644
--- a/.cursor/skills/exhaustive-switch/SKILL.md
+++ b/.cursor/skills/exhaustive-switch/SKILL.md
@@ -38,7 +38,7 @@ default: {
}
```
-Reference: `staticAssertUnreachable` in `typescript/server/src/utils/misc.ts` — it takes `never` and throws at runtime if execution reaches it.
+Reference: `staticAssertUnreachable` in `typescript/server/src/utils/misc.ts` - it takes `never` and throws at runtime if execution reaches it.
## Why
@@ -47,13 +47,13 @@ Reference: `staticAssertUnreachable` in `typescript/server/src/utils/misc.ts`
## Avoid
-- A `default` that only `throw new Error("unreachable")` **without** passing a `never`-typed value — you lose exhaustiveness checking.
-- `default: break` or empty `default` on switches that are supposed to be exhaustive — same problem.
+- A `default` that only `throw new Error("unreachable")` **without** passing a `never`-typed value - you lose exhaustiveness checking.
+- `default: break` or empty `default` on switches that are supposed to be exhaustive - same problem.
## When not to use
- Switches that are **intentionally partial** (e.g. only handle some cases and fall through to shared logic). Use an explicit `default` that handles “everything else” with correct typing, not `staticAssertUnreachable`.
-- If the discriminant is typed too widely (e.g. `string`), `default` will not be `never` and the pattern will not typecheck — narrow the type first or handle the real domain of values.
+- If the discriminant is typed too widely (e.g. `string`), `default` will not be `never` and the pattern will not typecheck - narrow the type first or handle the real domain of values.
## Imports
diff --git a/.cursor/skills/mongo-migration-constraints/SKILL.md b/.cursor/skills/mongo-migration-constraints/SKILL.md
index 5feb98ca2..8a3f62c65 100644
--- a/.cursor/skills/mongo-migration-constraints/SKILL.md
+++ b/.cursor/skills/mongo-migration-constraints/SKILL.md
@@ -3,7 +3,7 @@ name: mongo-migration-constraints
description: Migrates server code from MongoDB to Postgres without modifying the legacy Mongo service. Never edits typescript/server/src/services/mongo/ (especially db.ts). Requires removing MONGODB_KILL imports and replacing usage with Postgres (Kysely) in migrated files. Use during Mongo-to-Postgres migration, when migrating routers/actions/utils off #services/mongo/db, or when the user says not to touch mongo/db.ts.
---
-# Mongo → Postgres migration — off-limits Mongo layer
+# Mongo → Postgres migration - off-limits Mongo layer
## Hard rules
diff --git a/.cursor/skills/no-return-await/SKILL.md b/.cursor/skills/no-return-await/SKILL.md
index 70d6bbdd1..1eca7b160 100644
--- a/.cursor/skills/no-return-await/SKILL.md
+++ b/.cursor/skills/no-return-await/SKILL.md
@@ -9,7 +9,7 @@ description: Prefer `return` over `return await` in async functions when the awa
In an `async function`, **`return await x` and `return x` are equivalent** for callers when `x` is a Promise (or thenable): both return a Promise with the same fulfillment and rejection.
-Prefer **`return x`** — the extra `await` adds a microtask and obscures that you are simply forwarding the result.
+Prefer **`return x`** - the extra `await` adds a microtask and obscures that you are simply forwarding the result.
```typescript
// Prefer
@@ -17,7 +17,7 @@ async function load() {
return fetchData();
}
-// Avoid (unless you need await for control flow — see below)
+// Avoid (unless you need await for control flow - see below)
async function load() {
return await fetchData();
}
@@ -40,7 +40,7 @@ async function safeLoad() {
}
```
-- **`finally`** that must run after the inner work settles (same idea — often needs `await` in the `try`).
+- **`finally`** that must run after the inner work settles (same idea - often needs `await` in the `try`).
If there is **no** `try`/`catch`/`finally` depending on that promise settling inside the function, **do not** use `return await`.
diff --git a/.cursor/skills/preserve-comments/SKILL.md b/.cursor/skills/preserve-comments/SKILL.md
index 908b11a7f..5f77dc2a9 100644
--- a/.cursor/skills/preserve-comments/SKILL.md
+++ b/.cursor/skills/preserve-comments/SKILL.md
@@ -9,15 +9,15 @@ description: Preserves existing code comments (line, block, JSDoc, region marker
When changing code, **keep existing comments** in place unless one of the exceptions below applies.
-- **Line comments** (`//`), **block comments** (`/* */`), **JSDoc/TSDoc** (`/** */`), and **region/pragma-style markers** the file already uses — preserve them; do not strip them to “clean up” or shorten the diff.
+- **Line comments** (`//`), **block comments** (`/* */`), **JSDoc/TSDoc** (`/** */`), and **region/pragma-style markers** the file already uses - preserve them; do not strip them to “clean up” or shorten the diff.
- If a comment sits next to changed logic, **update the comment only when the behavior it describes changed**. Otherwise leave it untouched.
- **Do not** replace a nuanced comment with nothing, or with a shorter generic note, unless the user asked for that.
## When removal or heavy edits are OK
- The user **explicitly** asked to remove comments, delete dead documentation, or “dedupe” comments.
-- The comment is **factually wrong** after the edit and would mislead readers — then fix or remove that comment and prefer a short accurate replacement over silence.
-- The **entire block of code** the comment referred to is deleted — remove or relocate the comment so it does not orphan misleading text (or move it if it still applies elsewhere).
+- The comment is **factually wrong** after the edit and would mislead readers - then fix or remove that comment and prefer a short accurate replacement over silence.
+- The **entire block of code** the comment referred to is deleted - remove or relocate the comment so it does not orphan misleading text (or move it if it still applies elsewhere).
## What this is not
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 6bc17f1a7..55c8bb9ff 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -6,7 +6,6 @@
"containerEnv": {
"MINIO_ENDPOINT": "http://tachi-s3:9000",
"TACHI_EMAIL_FROM": "dev@localhost",
- "TACHI_EMAIL_AUTH_POSTMARK": "false",
"TACHI_EMAIL_HOST": "tachi-mailpit",
"TACHI_EMAIL_PORT": "1025",
"TACHI_EMAIL_SECURE": "false"
diff --git a/.dockerignore b/.dockerignore
index 64e8ee5d4..19e94187e 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,7 +1,7 @@
.git
.bun
-# Env files — keep secrets out of the build context; runtime env comes from orchestrator / secrets.
+# Env files - keep secrets out of the build context; runtime env comes from orchestrator / secrets.
.env
.env.*
**/.env
diff --git a/.github/workflows/client.yml b/.github/workflows/client.yml
index 97c8a763a..44f34c995 100644
--- a/.github/workflows/client.yml
+++ b/.github/workflows/client.yml
@@ -69,7 +69,7 @@ jobs:
VITE_RECAPTCHA_KEY: "6LcsYbIpAAAAAEJffjIXmbQcxj_SBZG7BnSPjF4L"
VITE_SERVER_URL: "https://v3-boku.tachi.ac"
VITE_DISCORD: "https://discord.gg/E4n8ZZF5ES"
- VITE_CDN_URL: "https://cdn-boku.tachi.ac"
+ VITE_CDN_URL: "https://cdn-v3-boku.tachi.ac"
TACHI_NAME: "Bokutachi"
BUILD_OUT_DIR: /tmp/boku
@@ -83,7 +83,7 @@ jobs:
VITE_MIN_CLIENT_ID: "A0DBDBB063CD800530EF01C6488B282137E0191E"
VITE_DISCORD: "https://discord.gg/NNgGJbpQUj"
VITE_TCHIC_MODE: "kamai"
- VITE_CDN_URL: "https://cdn-kamai.tachi.ac"
+ VITE_CDN_URL: "https://cdn-v3-kamai.tachi.ac"
VITE_GIT_REPO: "GitHub:zkldi/Tachi"
VITE_RECAPTCHA_KEY: "6LcsYbIpAAAAAEJffjIXmbQcxj_SBZG7BnSPjF4L"
TACHI_NAME: "Kamaitachi"
@@ -95,7 +95,7 @@ jobs:
VITE_MANDATE_LOGIN: true
VITE_SERVER_URL: "https://dev.tachi.ac"
VITE_TCHIC_MODE: "omni"
- VITE_CDN_URL: "https://cdn-staging.tachi.ac"
+ VITE_CDN_URL: "https://cdn-dev.tachi.ac"
VITE_GIT_REPO: "GitHub:zkldi/Tachi"
VITE_RECAPTCHA_KEY: "6LcsYbIpAAAAAEJffjIXmbQcxj_SBZG7BnSPjF4L"
TACHI_NAME: "Tachi Dev"
diff --git a/.github/workflows/database-seeds.yml b/.github/workflows/database-seeds.yml
index f38d8e612..0fd41e418 100644
--- a/.github/workflows/database-seeds.yml
+++ b/.github/workflows/database-seeds.yml
@@ -60,7 +60,7 @@ jobs:
chmod 600 ~/.ssh/id_rsa
printf '%s' "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
- - name: Deploy updates
+ - name: Deploy seeds
env:
TACHI_HOST: ${{ secrets.TACHI_HOST }}
- run: ssh "ci@${TACHI_HOST}" /home/ci/tachi-devops/scripts/deploy_seeds.sh
+ run: ssh -i ~/.ssh/id_rsa "ci@${TACHI_HOST}" /home/ci/tachi-deploy/scripts/deploy-seeds.sh "${{ github.sha }}"
diff --git a/.github/workflows/server.yml b/.github/workflows/server.yml
index c4bb00367..865c1fd7c 100644
--- a/.github/workflows/server.yml
+++ b/.github/workflows/server.yml
@@ -121,12 +121,17 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1
+ - name: Read server package version
+ id: server_version
+ run: |
+ echo "version=$(jq -r .version typescript/server/package.json)" >> "$GITHUB_OUTPUT"
+
- name: Build and push unified server image
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0
with:
context: .
file: docker/Dockerfile.server
- target: prod-unified
+ target: prod
push: ${{ github.ref == 'refs/heads/main' }}
provenance: true
sbom: true
@@ -135,6 +140,7 @@ jobs:
ghcr.io/zkldi/tachi:main
ghcr.io/zkldi/tachi:latest
build-args: |
+ VERSION=${{ steps.server_version.outputs.version }}
COMMIT_HASH=${{ github.sha }}
cache-from: type=gha,scope=server-${{ github.ref_name }}
cache-to: type=gha,mode=max,scope=server-${{ github.ref_name }}
diff --git a/Justfile-backup b/Justfile-backup
index 33f0aaa13..ab1209299 100644
--- a/Justfile-backup
+++ b/Justfile-backup
@@ -1,4 +1,4 @@
-# Postgres backup/restore via Docker (host only). Requires `docker` on PATH — not available
+# Postgres backup/restore via Docker (host only). Requires `docker` on PATH - not available
# inside the devcontainer, so these recipes fail fast there.
POSTGRES_CONTAINER := "tachi-postgres"
diff --git a/Justfile-repo b/Justfile-repo
index c276cf03d..227093f36 100644
--- a/Justfile-repo
+++ b/Justfile-repo
@@ -13,8 +13,10 @@ check-all-docker-builds: assert-docker-host
set -euo pipefail
root="$(git rev-parse --show-toplevel)"
sha="$(git -C "$root" rev-parse --short HEAD)"
- echo "==> server (prod-unified)"
- docker build -f "$root/docker/Dockerfile.server" --target prod-unified \
+ server_ver="$(jq -r .version "$root/typescript/server/package.json")"
+ echo "==> server (prod)"
+ docker build -f "$root/docker/Dockerfile.server" --target prod \
+ --build-arg "VERSION=$server_ver" \
--build-arg "COMMIT_HASH=$sha" -t tachi-server:check "$root"
echo "==> bot"
docker build -f "$root/docker/Dockerfile.bot" -t tachi-bot:check "$root"
diff --git a/db/migrations/20260301154256_genesis.sql b/db/migrations/20260301154256_genesis.sql
index 86ebe2f8d..7915abb63 100644
--- a/db/migrations/20260301154256_genesis.sql
+++ b/db/migrations/20260301154256_genesis.sql
@@ -128,6 +128,9 @@ CREATE TABLE "job_queue" (
CREATE INDEX job_queue_partial_index_btree
ON job_queue (created_at, status)
WHERE status = 0;
+CREATE INDEX job_queue_dequeue_idx
+ON job_queue (scheduled_for ASC, created_at ASC)
+WHERE status = 0;
CREATE TABLE "cron_task" (
id TEXT PRIMARY KEY,
@@ -706,7 +709,6 @@ CREATE TABLE "import_timing" (
CREATE TABLE "score" (
id TEXT PRIMARY KEY NOT NULL,
- -- TODO(zk): Redundant? joins on session(id).
user_id BIGINT REFERENCES account(id) NOT NULL,
chart_id TEXT REFERENCES chart(id) NOT NULL,
@@ -915,6 +917,7 @@ CREATE TABLE "pb" (
CREATE TABLE "pb_composed_from" (
pb_id UUID REFERENCES pb(row_id) NOT NULL,
score_id TEXT REFERENCES score(id) NOT NULL,
+ merge_name TEXT NOT NULL,
PRIMARY KEY (pb_id, score_id)
);
@@ -1047,7 +1050,7 @@ CREATE TABLE "quest" (
name TEXT NOT NULL,
description TEXT NOT NULL,
- -- Array — sections with titles, descs, and goal references.
+ -- Array - sections with titles, descs, and goal references.
-- Too structured/variable to flatten usefully.
quest_data JSONB NOT NULL
);
@@ -1131,7 +1134,7 @@ CREATE INDEX score_user_recent_idx ON score (user_id, time_added DESC);
CREATE INDEX score_user_highlights_idx ON score (user_id, time_added DESC)
WHERE highlight = true;
--- "All scores on chart X" — chart pages, playcount, import duplicate check.
+-- "All scores on chart X" - chart pages, playcount, import duplicate check.
-- Needed separately because score_user_chart_idx has user_id as the left column.
CREATE INDEX score_chart_idx ON score (chart_id);
@@ -1142,7 +1145,7 @@ CREATE INDEX score_import_uncommitted_idx ON score (import_id)
CREATE UNIQUE INDEX orphan_score_orphan_id_key ON orphan_score (orphan_id);
--- pb (5M rows) — hottest table
+-- pb (5M rows) - hottest table
-- The single most important index in the schema.
-- Serves: chart_leaderboard view, leaderboard pagination, and the COUNT rank
-- queries ("how many PBs beat mine?"). chart_id partitions the 5M rows into
@@ -1215,8 +1218,8 @@ CREATE INDEX folder_view_user_idx ON folder_view (user_id, last_viewed DESC);
-- so game-first lookups need their own index.
CREATE INDEX game_stats_snapshot_game_idx ON game_stats_snapshot (game, timestamp DESC);
--- chart — general
--- "All charts for song X" — song pages, chart listing. Postgres does NOT
+-- chart - general
+-- "All charts for song X" - song pages, chart listing. Postgres does NOT
-- auto-index FK columns, so this needs to be explicit.
CREATE INDEX chart_song_idx ON chart (song_id);
@@ -1232,7 +1235,7 @@ CREATE UNIQUE INDEX chart_primary_song_difficulty_idx
ON chart (game, song_id, difficulty)
WHERE is_primary = true;
--- chart — game-specific JSONB expression indexes
+-- chart - game-specific JSONB expression indexes
-- Partial expression indexes mirroring the per-game Mongo collections.
-- Used during score import to look up charts by their game-native ID or hash.
@@ -1303,4 +1306,20 @@ CREATE TABLE "priv_discord_user_map" (
);
CREATE UNIQUE INDEX priv_discord_user_map_user_id_idx ON priv_discord_user_map (user_id);
--- <== End bot
\ No newline at end of file
+-- <== End bot
+
+-- Optional read-only role used by Grafana (may not exist in local/dev).
+DO $grant$
+BEGIN
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'grafana_ro') THEN
+ EXECUTE 'GRANT USAGE ON SCHEMA public TO grafana_ro';
+ EXECUTE 'GRANT SELECT ON ALL TABLES IN SCHEMA public TO grafana_ro';
+ EXECUTE 'GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO grafana_ro';
+ END IF;
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'grafana_ro')
+ AND EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'tachi') THEN
+ EXECUTE 'ALTER DEFAULT PRIVILEGES FOR ROLE tachi IN SCHEMA public GRANT SELECT ON TABLES TO grafana_ro';
+ EXECUTE 'ALTER DEFAULT PRIVILEGES FOR ROLE tachi IN SCHEMA public GRANT SELECT ON SEQUENCES TO grafana_ro';
+ END IF;
+END
+$grant$;
\ No newline at end of file
diff --git a/db/migrations/20260422130000_grant_grafana_ro_public_read.sql b/db/migrations/20260422130000_grant_grafana_ro_public_read.sql
deleted file mode 100644
index 5bbe5d80b..000000000
--- a/db/migrations/20260422130000_grant_grafana_ro_public_read.sql
+++ /dev/null
@@ -1,17 +0,0 @@
--- Grafana local-dev role (see dev/postgres-init.sql): init runs before migrations, so
--- GRANT SELECT ON ALL TABLES saw no tables; default privileges can also miss edge cases.
--- Re-apply read access to all existing objects; no-op when grafana_ro is absent.
-DO $grant$
-BEGIN
- IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'grafana_ro') THEN
- EXECUTE 'GRANT USAGE ON SCHEMA public TO grafana_ro';
- EXECUTE 'GRANT SELECT ON ALL TABLES IN SCHEMA public TO grafana_ro';
- EXECUTE 'GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO grafana_ro';
- END IF;
- IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'grafana_ro')
- AND EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'tachi') THEN
- EXECUTE 'ALTER DEFAULT PRIVILEGES FOR ROLE tachi IN SCHEMA public GRANT SELECT ON TABLES TO grafana_ro';
- EXECUTE 'ALTER DEFAULT PRIVILEGES FOR ROLE tachi IN SCHEMA public GRANT SELECT ON SEQUENCES TO grafana_ro';
- END IF;
-END
-$grant$;
diff --git a/db/migrations/20260423120000_job_queue_dequeue_index.sql b/db/migrations/20260423120000_job_queue_dequeue_index.sql
deleted file mode 100644
index 948087d70..000000000
--- a/db/migrations/20260423120000_job_queue_dequeue_index.sql
+++ /dev/null
@@ -1,4 +0,0 @@
--- Fast dequeue of queued (status = 0) jobs ordered by when they are due.
-CREATE INDEX job_queue_dequeue_idx
-ON job_queue (scheduled_for ASC, created_at ASC)
-WHERE status = 0;
diff --git a/dev/bootstrap.sh b/dev/bootstrap.sh
index 0f3ca237b..b49fb7087 100755
--- a/dev/bootstrap.sh
+++ b/dev/bootstrap.sh
@@ -39,7 +39,7 @@ function seedMinioCdn {
# Debian package `minio-client` installs the `minio-client` binary (same CLI as upstream `mc`).
# override endpoint with MINIO_ENDPOINT (devcontainer sets http://tachi-s3:9000).
if ! command -v minio-client &> /dev/null; then
- echo "Couldn't find MinIO client (minio-client). On Debian: apt install minio-client — see Dockerfile.dev."
+ echo "Couldn't find MinIO client (minio-client). On Debian: apt install minio-client - see Dockerfile.dev."
exit 1
fi
diff --git a/dev/fish-plugins/fisher/README.md b/dev/fish-plugins/fisher/README.md
index 0f6116e89..72fc45691 100644
--- a/dev/fish-plugins/fisher/README.md
+++ b/dev/fish-plugins/fisher/README.md
@@ -1,12 +1,12 @@
# Fisher
-> A plugin manager for [Fish](https://fishshell.com)—your friendly interactive shell. [Snag fresh plugins!](https://github.com/jorgebucaran/awsm.fish#readme)
+> A plugin manager for [Fish](https://fishshell.com)-your friendly interactive shell. [Snag fresh plugins!](https://github.com/jorgebucaran/awsm.fish#readme)
Take control of functions, completions, bindings, and snippets from the command line. Unleash your shell's true potential, perfect your prompt, and craft repeatable configurations across different systems effortlessly. Fisher's zero impact on shell startup keeps your shell zippy and responsive. No gimmicks, just smooth sailing!
- Fisher is 100% pure-Fish, making it easy to contribute or modify
- Scorching fast concurrent plugin downloads that'll make you question reality
-- Zero configuration needed—we're not kidding!
+- Zero configuration needed-we're not kidding!
- Oh My Fish! plugins supported too
> #### ☝️ [Upgrading from Fisher `3.x` or older? Strap in and read this!](https://github.com/jorgebucaran/fisher/issues/652)
@@ -29,7 +29,7 @@ To install plugins, use the `install` command and point it to the GitHub reposit
fisher install jorgebucaran/nvm.fish
```
-> Wanna install from GitLab? No problemo—just prepend `gitlab.com/` to the plugin path.
+> Wanna install from GitLab? No problemo-just prepend `gitlab.com/` to the plugin path.
You can also snag a specific version of a plugin by adding an `@` symbol after the plugin name, followed by a tag, branch, or [commit](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefcommit-ishacommit-ishalsocommittish).
diff --git a/docker/Dockerfile.server b/docker/Dockerfile.server
index 2d56f4828..a996ddd6a 100644
--- a/docker/Dockerfile.server
+++ b/docker/Dockerfile.server
@@ -1,74 +1,39 @@
# Build context must be the repo root (same as GHA docker-push: context `.`).
# Respects .dockerignore (see repo root). Full tree copy keeps workspace installs correct.
-# docker build -f docker/Dockerfile.server --target prod-unified --build-arg COMMIT_HASH="$(git rev-parse --short HEAD)" -t tachi-server:local .
-# docker build -f docker/Dockerfile.server --target prod-api .
-# docker build -f docker/Dockerfile.server --target prod-job-worker .
-# docker build -f docker/Dockerfile.server --target prod-cron-worker .
+# docker build -f docker/Dockerfile.server \
+# --build-arg VERSION="$(jq -r .version typescript/server/package.json)" \
+# --build-arg COMMIT_HASH="$(git rev-parse --short HEAD)" -t tachi-server:local .
+#
+# Default CMD runs the API server. Override to run a worker:
+# docker run tachi-server:local bun run ./typescript/server/src/job-queue-worker.ts
+# docker run tachi-server:local bun run ./typescript/server/src/cron-worker.ts
+#
+# Worker containers use a file-based heartbeat (/tmp/worker-heartbeat) rather than HTTP,
+# so their HEALTHCHECK must be configured at the deployment layer (compose/k8s), not here.
#
# NOTE: bun build --compile is NOT used here because it's
# FUCKING BROKEN
-FROM oven/bun:alpine AS base
+FROM oven/bun:alpine AS deps
WORKDIR /app
-
-# --- install: full tree so bun sees every workspace package ---
-FROM base AS deps
-
COPY . .
RUN bun install --frozen-lockfile
-# --- API image ---
-FROM oven/bun:alpine AS prod-api
-
-ARG COMMIT_HASH
-ENV COMMIT_HASH=${COMMIT_HASH}
-ENV MIGRATIONS_DIR=/app/db/migrations
-ENV PORT=8080
-
-RUN apk add --no-cache curl
-
-WORKDIR /app
-COPY --from=deps /app .
-
-EXPOSE 8080
-
-HEALTHCHECK --interval=15s --timeout=5s --start-period=30s \
- CMD curl -sf http://localhost:8080/.deploy/up || exit 1
-
-CMD ["bun", "run", "./typescript/server/src/main.ts"]
-
-# --- Job queue worker (no HTTP port) ---
-FROM oven/bun:alpine AS prod-job-worker
-
-ARG COMMIT_HASH
-ENV COMMIT_HASH=${COMMIT_HASH}
-ENV MIGRATIONS_DIR=/app/db/migrations
-
-WORKDIR /app
-COPY --from=deps /app .
-
-CMD ["bun", "run", "./typescript/server/src/job-queue-worker.ts"]
-
-# --- Cron worker (no HTTP port) ---
-FROM oven/bun:alpine AS prod-cron-worker
-
-ARG COMMIT_HASH
-ENV COMMIT_HASH=${COMMIT_HASH}
-ENV MIGRATIONS_DIR=/app/db/migrations
-
-WORKDIR /app
-COPY --from=deps /app .
-
-CMD ["bun", "run", "./typescript/server/src/cron-worker.ts"]
-
-# --- All-in-one image.
-FROM oven/bun:alpine AS prod-unified
+FROM oven/bun:alpine AS prod
+ARG VERSION
ARG COMMIT_HASH
+# Fail the build if either arg is omitted (Docker has no required-ARG syntax).
+RUN set -eu; \
+ : "${VERSION:?Dockerfile.server: VERSION build-arg is required}"; \
+ : "${COMMIT_HASH:?Dockerfile.server: COMMIT_HASH build-arg is required}";
+
+ENV VERSION=${VERSION}
ENV COMMIT_HASH=${COMMIT_HASH}
ENV MIGRATIONS_DIR=/app/db/migrations
ENV PORT=8080
+# curl is only needed by the API healthcheck below; workers don't use it.
RUN apk add --no-cache curl
WORKDIR /app
@@ -76,6 +41,7 @@ COPY --from=deps /app .
EXPOSE 8080
+# Matches the default CMD (API server). Worker deployments must override this.
HEALTHCHECK --interval=15s --timeout=5s --start-period=30s \
CMD curl -sf http://localhost:8080/.deploy/up || exit 1
diff --git a/docs/docs/api/routes/admin.md b/docs/docs/api/routes/admin.md
index 6e763994c..e96900f21 100644
--- a/docs/docs/api/routes/admin.md
+++ b/docs/docs/api/routes/admin.md
@@ -69,7 +69,7 @@ Empty Object.
`POST /api/v1/admin/recalc-pbs`
-Inserts every distinct `(user_id, chart_id)` from the **`score`** table into **`pb_dirty`**, then **drains** `pb_dirty` and downstream **`session_dirty`** / **`game_profile_dirty`** queues until nothing remains (same batching as the background worker, but the HTTP request waits until idle). Intended when PBs may be out of sync (e.g. after a bad migration). There is **no request body** and no filter—always all distinct pairs that appear on scores.
+Inserts every distinct `(user_id, chart_id)` from the **`score`** table into **`pb_dirty`**, then **drains** `pb_dirty` and downstream **`session_dirty`** / **`game_profile_dirty`** queues until nothing remains (same batching as the background worker, but the HTTP request waits until idle). Intended when PBs may be out of sync (e.g. after a bad migration). There is **no request body** and no filter-always all distinct pairs that appear on scores.
### Permissions
@@ -191,7 +191,7 @@ Empty Object.
`POST /api/v1/admin/recalc`
-Enqueues **every chart** into **`score_rederive`**, then **drains** `score_rederive` and downstream **`pb_dirty`**, **`session_dirty`**, and **`game_profile_dirty`** queues until nothing remains (the request waits until idle). Re-runs `scoreDeriver` and `scoreCalcs` for every score. There is **no request body** and no filter—always all charts.
+Enqueues **every chart** into **`score_rederive`**, then **drains** `score_rederive` and downstream **`pb_dirty`**, **`session_dirty`**, and **`game_profile_dirty`** queues until nothing remains (the request waits until idle). Re-runs `scoreDeriver` and `scoreCalcs` for every score. There is **no request body** and no filter-always all charts.
### Permissions
diff --git a/docs/docs/codebase/setup/config.md b/docs/docs/codebase/setup/config.md
index 603816e91..1451426c1 100644
--- a/docs/docs/codebase/setup/config.md
+++ b/docs/docs/codebase/setup/config.md
@@ -236,13 +236,13 @@ provide callback URLs inside emails.
SMTP is always configured. Set:
-- `TACHI_EMAIL_FROM` — `From` header (must match a verified sender when using Postmark).
-- `TACHI_EMAIL_AUTH_POSTMARK` — `true` or `false`. When `true`, uses Postmark’s SMTP
- endpoint; set `TACHI_EMAIL_AUTH_USER` and `TACHI_EMAIL_AUTH_PASS` to your server API token
- (both are the token for Postmark SMTP).
-- When Postmark is `false`: `TACHI_EMAIL_HOST`, `TACHI_EMAIL_PORT`, `TACHI_EMAIL_SECURE`
- (`true` / `false`), and optionally `TACHI_EMAIL_AUTH_USER` / `TACHI_EMAIL_AUTH_PASS`
- for servers that require auth (local Mailpit typically needs no auth).
+- `TACHI_EMAIL_FROM` - `From` header (must match a verified sender when using Postmark).
+- `TACHI_EMAIL_HOST`, `TACHI_EMAIL_PORT`, `TACHI_EMAIL_SECURE` (`true` / `false`).
+- Optionally `TACHI_EMAIL_AUTH_USER` / `TACHI_EMAIL_AUTH_PASS` for SMTP auth (local Mailpit
+ typically needs none).
+- For Postmark, use host `smtp.postmarkapp.com` (usually port `587` with `TACHI_EMAIL_SECURE=false`)
+ and set either auth field to your server API token (both username and password are the token for
+ Postmark SMTP).
`TRANSPORT_OPS` is derived from these variables and passed to Nodemailer.
@@ -272,6 +272,18 @@ interface INVITE_CODE_CONFIG: {
};
```
+### TACHI_INVITE_ADMIN_INITIAL_INVITE_CODE
+
+- Type: String (Optional, environment variable only)
+
+A one-time bootstrap invite code for first-time instance setup. When `INVITE_CODE_CONFIG` is
+configured, new instances have a chicken-and-egg problem: registration requires an invite code,
+but invite codes can only be created by an existing user.
+
+Set this environment variable to a long, random secret. The first person to register with this
+code - while the `account` table is still empty - becomes the site admin. Once the first admin
+exists, the code is no longer accepted.
+
### TACHI_CONFIG
- Type: TACHI_CONFIG
diff --git a/observability/grafana/dashboards/tachi-api.json b/observability/grafana/dashboards/tachi-api.json
index bbe592dae..1d2a660ef 100644
--- a/observability/grafana/dashboards/tachi-api.json
+++ b/observability/grafana/dashboards/tachi-api.json
@@ -12,7 +12,7 @@
}
]
},
- "description": "Tachi API — endpoint latencies, score import durations, error rates & system health",
+ "description": "Tachi API - endpoint latencies, score import durations, error rates & system health",
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 42d13640a..afd515981 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -7,7 +7,7 @@
"resolveJsonModule": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": false,
- // all packages run via bun directly from source — no emit needed
+ // all packages run via bun directly from source - no emit needed
"noEmit": true,
"types": []
}
diff --git a/typescript/bot/vitest.config.ts b/typescript/bot/vitest.config.ts
index 81b0ad26d..741571d55 100644
--- a/typescript/bot/vitest.config.ts
+++ b/typescript/bot/vitest.config.ts
@@ -32,7 +32,7 @@ export default defineConfig({
DISCORD_SERVER_ID: "test-server-id",
},
- // Parallel test execution — each worker gets its own isolated Postgres database.
+ // Parallel test execution - each worker gets its own isolated Postgres database.
fileParallelism: true,
globalSetup: "./vitest.globalSetup.ts",
setupFiles: "./vitest.setup.ts",
diff --git a/typescript/bot/vitest.globalSetup.ts b/typescript/bot/vitest.globalSetup.ts
index 154d354fa..a842353d3 100644
--- a/typescript/bot/vitest.globalSetup.ts
+++ b/typescript/bot/vitest.globalSetup.ts
@@ -1,7 +1,7 @@
import { execSync } from "node:child_process";
/**
- * Global vitest setup — runs ONCE before any workers start.
+ * Global vitest setup - runs ONCE before any workers start.
*
* Creates a fully-migrated template database. Workers clone from it
* instead of running migrations themselves, which is much faster.
diff --git a/typescript/bot/vitest.setup.ts b/typescript/bot/vitest.setup.ts
index 5ed7f1142..0af114447 100644
--- a/typescript/bot/vitest.setup.ts
+++ b/typescript/bot/vitest.setup.ts
@@ -20,7 +20,7 @@ const POSTGRES_HOST = "tachi-postgres";
const POSTGRES_USER = "tachi";
const POSTGRES_PASS = "tachi";
-// Set POSTGRES_URL before any app code is imported — config.ts reads it at load time.
+// Set POSTGRES_URL before any app code is imported - config.ts reads it at load time.
process.env.POSTGRES_URL = `postgresql://${POSTGRES_USER}:${POSTGRES_PASS}@${POSTGRES_HOST}/${WORKER_DB_NAME}`;
// Mock global fetch before any app code is imported.
@@ -45,7 +45,7 @@ globalThis.fetch = (url: string | Request | URL): Promise => {
} as unknown as Response);
}
- // Blow up loudly if any unexpected fetch slips through — this keeps tests hermetic.
+ // Blow up loudly if any unexpected fetch slips through - this keeps tests hermetic.
throw new Error(`Unexpected fetch() in test: ${url.toString()}`);
};
diff --git a/typescript/client/src/app/pages/admin/AdminActionsPage.tsx b/typescript/client/src/app/pages/admin/AdminActionsPage.tsx
index 101da36ef..00bffb274 100644
--- a/typescript/client/src/app/pages/admin/AdminActionsPage.tsx
+++ b/typescript/client/src/app/pages/admin/AdminActionsPage.tsx
@@ -33,7 +33,7 @@ type ActionsResponse = {
function durationMs(start: string, end: string): string {
const ms = Date.parse(end) - Date.parse(start);
if (Number.isNaN(ms)) {
- return "—";
+ return "-";
}
if (ms < 1000) {
return `${ms}ms`;
@@ -196,11 +196,11 @@ export default function AdminActionsPage() {
{action.username}
) : (
- —
+ -
)}