From 7e2e8cc4e67c21d46466b68204cc3e6b4d6e3f62 Mon Sep 17 00:00:00 2001 From: LIghtJUNction Date: Fri, 9 Oct 2026 23:20:19 +0800 Subject: [PATCH 1/9] ci(browser): stage guarded first-read fix for Python 3.15 verification --- .ci/home-first-read.patch.gz.b64 | 1 + 1 file changed, 1 insertion(+) create mode 100644 .ci/home-first-read.patch.gz.b64 diff --git a/.ci/home-first-read.patch.gz.b64 b/.ci/home-first-read.patch.gz.b64 new file mode 100644 index 00000000..c7122066 --- /dev/null +++ b/.ci/home-first-read.patch.gz.b64 @@ -0,0 +1 @@ +H4sIAAAAAAAC/+19a3cbx5Xgd/6KWnqcBkh040GQoiBBK9lW4uzxQ0eik8nSPEgDKBBtNbrh7gYf5vIcO4lj56HY3omfSXbimZ2Jd8aWPbuTiWdsx+fsT5mIFP0pf2HuvVXVXd3oBiGJkzlnz1I2CXRX3bp1677r1XcGA2aa207E7KoFf4aTbnXXD24OXH83rA79ETcHThBGZsDtvrU/cll3vnILHt9lA8flbOT3OavXamvN5oLj9fkeq4kfy2rWGqvr/bUFsRZtc93qt7EdReWl5fnbuXyZWbWKjW2XK+cO88uX15Y9uwRb7FHAqjBA0bFGRZfWPa91sIyY2NooxPw5yc8jOgBPLKjYSg/M2YyIwx61VG4XY0C2wvHfhCF1aUlI6fA2J1sO15Y7Tthz9/hwX6qVARNhFX83UHki951BbJFr6Mh4p9uvhc4Y3jfG/LeTYLdoZ52sKSCZ4339TpzEdQQFAqHkhpdIAA0ErbY5sh2vK3/T68ceqkSHegVkKaHxBvzYOSEoeN7glA934u4FwEhBTPC994kCLjX26f324E/GbeYREVrxfyzgwMmULECPmCHhwQOhoW7puOZ48DfDngIgKNgwheWn/O7okWgrjPYl6MUTLzQBAFgk+7EiyamayOpxLvIGXF/Epkjx5tEONSNmngBiOw4PZ6M9NgPI2wsfsCYM7K3Qd7iN/W15B33drSSjF17+sbGN65fvdF55sbV6y0G/JAgkSlx7cqNG99++vpjs0s99kjSclKA+A8Y1lhtrjRa+MvYSt7644gGhV0ydZimOeS2Gw3N3qjPFsfbHSdE6u8z8xmFAjP7cWuLuXUdGONgx3ZB3YXJU0lftqo9C3gUODxkqwKOTqknb3yjs3H1xkYnIdf1J+DJk9eeuLJxNenw826rWpWoteTfy/XGOasG/+rU7+pB347srh3yQzmiER/rgjsJcbztHpFESAdgennnXNK9XWC91CgCZ4cOMGcv4H3gaMd2AcQAfvMiuCGPJmNzvB8Nfe8U2FTG3MEmkFuNFau+auglbBcEDZieB9zlNjUj+F41LfT/N70wgqKMxi3yfVfjD5CFFvtvOtBw0veZPY7MbR6xyRiIxpn5/POFRRwJ3dzHYgwkgLlOd9wLe64TcRMsGQt3ne3pnjFzxMbOOAYw2Wm3YbAalhI58TOB+vteD9jF9Xs3eR8+8D3QrQwhm5KSbPHPSj1/NLI9eL8jGygvZuBAZ/W2XXt/N3C2h1ofTBwFwHkcst4w8EfOZJSl5rdIlWjGNKwwewJAAyfaZ4gA3wOWcLxtpcAYqLOIzyT66GbfCZg5ZnYQOQPglTAfdRrBtGHQlDco7am3PRs4OfNC4gV1Bo7HR8C4BQWG0cgteOXa3vYEBjvMvBcGpzO2Pe7OeAWgo3EWtG8H8BrEGTCyUVywgGk+N/GcaG/ktmPiVHNMAwGyoFjBgEEhF57sONsEmkYKmhqDUDo7HD6COxbgqE3wA3v0xrUZyvs7G48//dS1KxuPtxhY88zIpjltXnObRftRLA9cCHQGpg/9SdDjhDR1dBYzyfaDCfiz1CrLcUkO1ECG0b7LK4TXGMazggPTERxbkSMmix4m43WQx36VAuaqTI0+QbpXwmS7NvCDkQ2GyPx/s49KUX2b8SDwA9RYoOHGEEWQpno+zXgM3SLWFyEMtVZkgCZj1wdxUbIENghNpObEDKCsu2vvh6XyDOskmDRHDvmOA6awx1MKH3zkVqLbqvo7sP5oOcHS9aHNFju30NcDsXkpCFHS3MSeJxxb7fbXGt1Bbjg2d0NaRFY/X6eQbHFxUbcg5o4DyGA9u+u4yoJoagoJj2M3Ae31+MaTT1RBMUHVvWgCystaWF5Y3hhyeLNxjaHHtR2IasTCzN/1gGncvklWgEBLU/WCKNblQ2jKDyy2MXRAqTihj95wPzZd6PbCH2QhFtqjMRAN3agK8/wI7DwoToccnwgdOODSnj9Bvw8xg54iegMwo6zTGUwQ404HnGQUTsAFIBASIZaST58LfS/+4ofxRyFYoJ0lPLIeEjdZ5BFwgJAM10U0+zh01uVBBTomq+LLG1RFAkGuBFdFAbgGX+WbSeDCC2tsByFXr+FZOIYRivuU+A8WOigde+yosnxvzHtRhRyXTlIurgp6ypLRnzWedF2nJ4yfqt/nA3viRlrpRKtZsRpTpR9/+smrnUeuP/1tiCQ6j1+98tjV6zcqrO/3JpIxf/UY8UL5gScGIjvaEncQUymddJBK2R95rezMHpRqI/NTFsJ/jq3o9JKJWMvyzqwSwBsB0BKQrZizShlXw4P8U2RccyCa4l1Kr2h4/YT0BlQtUSV1BACuS/n65UVcF9WavO7L6JdslIaYaiY4kph2Ney+n6lkPjCLZIDCGpCH7lGjYauluGktTQnAYQFs7rEniA9wER6PsBwHziKhcDltgt6NwDty+3ekApgZGUDy1Eh5kQWW6qiIqW6B5qpDTjOt+xAXRf30pEHqHS5JofJkF4QGpkAuY42JJKadaANa0iS6qo7aTx3DFeaRVU0y2NmLE/SD7sLvAQkv4D7yXiUAU7mmEZupSYYMor8EfVGGqy69PQwmkfv1eu3GEKwA3BmQZVBk6WETwFu4KN2J+cEHPGHmcnW0BmpMOFX05w4uib5PbIHEQr9mXVI60Vidhtz9EnDtpLWPcB5Pnou0b7gAJ82lZp8R+weFuost3uSL3J4rKD0GYywKfiHSCKUjiJJiib1eJzz3BGNToIU3e3yLC9FZ4ViWggNn/Qxp51Y3Uu0imyBqScQpuzqcxNwnAf7ZkxA9Dy52eXRLudZE46bcRH/xCayhrLioGyuC/st9iNQSA4u3u4QKhPQCmgX0XEmsxlen9TR8xOHR2zEIxu3qjBw/Un1LMfSP7eaQQMtx7AmnaYppTP9alm815X1Suz8EPHGuOqO7xZwaAwYIzoNeEzzjHMiDYDmobBUkkewdNZDkSYlqzo170c9M+mhEAuNJwbbsfuxVqux+nkyHE35NgSZLufHH6sQgBT6JvfkSk07UOAXqBFDlTjtN2Ww7rn2aFxC3CussbMLv8CMljVXSvQDweR0pTGjK+LxboCcDeMdRfsUSaSH5lTPS0ob97ZBqHH5wj0kMqbrWlQaQBSWDtl4nhZEbImjE2s161wiRk3lU2jDsbbeG2q8fG89asTODpRxbPjrTUbA7b0Wi+zuxAVzAg/CfPii1xU2/QJcSndfevFzIpHbQAJnDlc6Wxs3Q6ghGdgjx91vpZ0vHQcyALq+Sa2RuN+hW0+GbpWGLitLa2ti8BjwLzjm4EainyqNZZ7yFHo8NlsAtG+HQ55rtbKGUBQR30zaS1ue1j6FXSOxI20R6wk9W5JCUirY+6SZEGfwp50ezyT9cnTWVLwq+CmtautrWhQIXxHBOsV/GE/gIjcMJ1bX4Q88GPg+unAQkWGIjzBrTAeacU9UBK7Xs9GcJFZOKNEpI6707VR4WrungE+2O542YTPjjqkpZmD6ABfqyBSE3pTVWA1QJ9OzXTlYoOinhq+5mqU9JVDXYumau01hQjCOAuthCSOyijnB05FYzTWQDexABrdaghubjdslWrxQXGSMJTaHAR8stRdp70JvEoR+0F7cyjDxfdnfLC9NGWQZG6+cXydePr8meHmISlOLoE/PYeeQiMgDniNO73X3sRjEpqAkxZEOuK5L2t1QcxSVfyhVKfqNudJMAizsOf6eld2pUVh9QVeO5ppIkegCbqoYsK6nKOKHp2TuAaZI/YqeZ2E2KXE/h9ZcIZOi+SZEgYyQphi3KeNkHMdzdRrHc/W1yvrZjuMCuzzifcdmJY2H1tfgtUz0P/A8zVR+JeMM42DF+JgP3qDUHfokA3iMvYQCcugakr7LZ9HkjNmSOTCpJZhMp22zeVqQpAKpOC0sPCT2yxnvtWYy3q0A7Ac0m5mEQdWq8WOzVjkP/LhyvnL+bPmRxXOLraEdli6hSnVwbrGcQwo5hUc7AkKu6ufmKBNr2kxakmlAEd6mzI2KkNP8eEpmq7E2o5Ke/MhPbKSq65FoarClc1OUvSfN2NA4e0a8nJKAXD9X12CZwunYQipknbjJyiKcF8R9NrhS+CBO16xqQ67ZhFxQ8eu+q+ZZplPX9UGQ7j9x6rnVSr0JrHquefasykQO0ImHwA+YVV8PGZ4TVtHSP+b0Oy3/k34O/uvQ7gPF4qdkYRRRSRhkXhH0jdg0Y4pTE/FMmp0AF2GDHolwBU2oHkGndVzpM57tw6wGtCK6i22Z/kTGxCwGjtllbcpwNmvrrUwToJHf/0Ze9+VDxXQ5yPx7NYeURor2Ua2LYLPCWgO/B6GSXKeFSj/cQa1hezAkAoFRuG26/rZv0p5T61zIepMubgDkLzg8KFm48b0OFFxZg7/laUEJN/0x97ZSsjNwuIuHIcRLLkDiJqqc/owi43AaobE/hs6tFQ5yYc46C0hMQJh4bFLdOh/KDDP0ZjVkdcy0D+dSfliCZWHjqJpjNHh1qz6FKo0sLn8LL2Rqm31OrgWl562VENQc4bbEZBLfwZ1m8NSqr5ZZVQIGAWmWRbgrhiDyb3IPT2vaxGQmdtEDXLbkC2EYUzjLCmHIRzhHV2/MHmpQ+APcVSNEOQd/gdcYTB4qnOQ1Ok4iC9ACozMJeV8EqefAo6+vgVo7X6usna1au3yT7w8Ce8TDmH2o/3RKzkFqqiDWIlKhoD7+Tgmd8DILcRNVyTq/XpaOh5kBrLET8UTtYRRnxLeWnpAgnqo3Hq6w9cbDOgZ19a6W1NXnaXBepDwFaxqThPkSRGZOEaTT9uk0C2HUWH34AdL/iVZCQ4j/MF+kFzJDfxBLsOz+fWM83VxNn9uSRNP5IsP9xB41GB8aJDUaObwhZbKPs2fy4365DB5tlLxE1ZmakrJW1qWmXFkH8GurhdBrJOsSWq3Pt3UwxCynrduLT30qXLOnl5Dr9VabA97srVtWb2B3m7XVU9frpWAUrtVLlUKZP38ORH0ZfjdXUeJxr67U/51kzWNJPqqwpQodZQeRi1wG21rQj5wga4Hxn9j1K6Kajjh6hraaU+cqFLPgwWRtPG9Bbc1YXFy8zuNwH0vKZbwVcFbpFFtTHYHG+3KeWoDz0FcXh7cyynHSSWcWHWMmlt0GYg+udtrORTWhT2cQtBcFrosMk9Sma3e56/J+d1+9MKkTi5cMHQQGA7J6JsRavHQRAkann6lOeJlOfd2LG7ykT8xfrA4b6SbsbANI4kWGSaD2ojz7AxqjVQcacPVCHPJxsYrv04CpBnV26PSBylAnmPDFS+xff/i2LH+xasP/0ElVcys+foQILzYOi32i6aMVgmT/Q+YciYtj1SEtGb0I8uVy6COxxeKlO5/SsV90co04leb47V8f/583xYE1eH7Ix7+7+8Gto9d/Jk7K+cOL37tYHev9k6t+uQu44pJwwndTnsayVYRrEXYaYT2/IznhKV+e1SLXVOzzyCIs4rZDXtzQxFUtKcbKHH6AqOJOp0LMtUMsONjFMS89P/FBR2HJTXEGxxZImj3gbaN6eelruKWFSuRtqBFRYQwJd+ogHDHC6owuAZlOctqa2vALpBqN8QwekZjp4NyIxEU7gGRrakE8UoXvRUiVzemtL8ZF17l0EU+56wF+GsXURCwQLmfP9MC4OFxJFac+gKDYUnQOkECHi5cO6MWhYPXhyhS0LMLI+zFh4v2tOZvLUuM90DhLj4ahfUlxotSierxVPtRZaX5qaRopE08XEoomsfCwL/wAhEkhlAzd4laKfQ4XmZEHjsjZXtSOBkv6SCyCPcOWitAhvSS7QDnPRaZgjseBv4dOLKd1+yG43idfvnHn88/FKWxfvfIatIXL/OMRWkxOtVlEO14+ZKIs+7+/EzCUasxBhqYGO6hvSyVlsPCgzIPDMgHXBa2spEN9zRscUqUXq5Kb4RPw9ix+y6iM6sSNOSL7SlqH+L20x4ZhPec7eH4JFBe7EcjEaydbUUuo4MgWV8T3pQo5B/UaeQf1psz051VNnVelgeiFwaBDPl3bMCoLGkUFLWXZZXUgvzwxS0JQfgV5ETgCZJErTJ7kFZML3Ec6CUOguw7xS40tNxrwd70I4UIPQDo5pCSUDInMSGxv6cCutBWnJ5duQNwBqhrViJFtZqyXxwDF8TpDBxuStcD1JWZk+/4EFL8HvjOtiBGZOCYPPAot9qgPEV8wIv/IRmnAQ/rxXB82CTHSoPqPPvFNskMaUyxkzy54iF1htM815UjhdQysy5m4MWB3iOfs0sFrE9eFyArETm3JwJ1XkeO6dEStlRxKFo8jlsCh1A1g8jI+aS85H1Cdk4ak2lTHEYLKke6FeCG0rvQqteWsRvlwSkvTAiInJOuPmGT2V9GhJnGrm3pLhg75cMqnSInetIebdLOS4newl1nUBIHyLYZISeS4TWLSOuMx6RymYY/uahrODZwswA0SIXGYqy8KhcCMYQLUD8AtdPeZBsjKuFcZnPKWGGd9umxt/cxB3bsDXw70+g3kNgUWaTUbMY2/WeJ7FVCW1vfW1iorq6AqVpsi1VGkKdJVs8ZKaPRxyl6x1HrdxPoRZy+ioUXzjgYo/UadrKmMf4H8mnOxS35wQ0HJfCGImelp1l9ltAivvWiaXov21IlOSIjQhRTvaWiTbyvEQ7m4nFYEYXydln5Q9OZ9uZjmDKfJzPE5XCfpjtM6IPSgA1PelHQiwZxNQSEjdYrLPOUjmGfrjpkP4o7JQC+DVD4xc5luDgc0t8P/QQ7fNC7LKY9P193zen15MA0VQZvpM4TMeaIK89So4tQAonj4DOGAFqmU2OE0NSxpe3VKSFv3yCC5Efb9R/xTLUj7WuwqTFcRpxKeSexfOMozHPZ81OODlHV39xSLlKv7FaAkJRU/0TuXFHtUfiJLkDFuOakmWVG6tE97YKAVLHRIyamkXdHoX+D6bd63mOb5Ol7PnYALS84rONw7KO4KggVq7dT8anIlSHGGNVVG5lj767V+nzctq3b+/Oo5e3B6jjUNpTjLmi4nNhStn8egZFl8iJOtQqN3YDhL8p6y7Hhr2+/JOQEDmJygaSAR5S2AljyIUvAP5vTHdEweHg5jh3wArnqpvJBzXgmdnYBn+cUOELJiqk1xWCi+Bv//Ju7tzwOkacw2HhmYJ5wpsOiDElTkp5wkigoXph34HGg5dGNqiilzEUP65hh1G4PeAR6C++77kXZGMOa1834UkjYeo5GtVFooOnVI3UtniUMc/UB+4JX4jZpvt+SDziSAGNh1Rk7UXs0HnKPe8CcmMgXLZhFKS0txwUpxqeS0+HRy8vQqKU3cghDUl1MCqVRyuQDQYf5jGcS0k07qyh8kYHOrvNla3So6bu6ar24HSM7DEXMPuPvNphi3iifj4CEP2VC44KS5GKc0hQiNfGZQCaaYj1LMOIONJNd0xnbvJo8qxQU3dV8llWMW2G6JpM9KrVGn1S0rtfp6pX7+3jRVfKcRB8yjoJRlZdBHPs7iwLDQ3A/G3eJcFXhCU0oFfF3QMb43dgLebysdGPb8MRfjTwYcoCrPzQAlEJ+jLeupRotYbmmpdGDoQ2G0UiNzmK8pocmCkzwBYPFBZDObktcZCLWiZO+wGFgGseKCuQe8zqJ7jhGRpoe0S4FmKeUd4JxPvkeu3LiqShQNfPaw5tb0Yc2FNfFKJRPrB75Lh60LrwNPDwK3zw+4sVBE0lxb2/P9m06uaymODmalKatXSjSyFlzkOqwFJ4JBtQSGzCYVlpxGIEftLhSzR4pEdCWVUUTdb4FWpNtByAGpsEcFcYqK/7n5ZLhtPo6H493w7DGeFpfNwZ3qAabNeaETOFVM+oFr3eZKc5VbVr9m19fWTp9rnwZU6ApOF6VMdaOJS6/FH3hg0z2ZqGXncx/k5QHGyPYmtmucLqMaa6qpPj01oWVp5f5GPF1a3bajl6yk7sQxDhfSwiHOi7fEvXfaPY3ykUXJasfLagkjuWe40+eeg4o5UwKscmeAKWtDzk2sNCnZL//OScTDjMmS8yQkOywT56ZRz8S5iorq3kBBttLB0hJSC6ikjkVr0eb6UgwMxbW8SR68sXVYjs/pmkoIyBbA23/Go88iVW/kZAUUMsbxL//u6I0vjl6/JeJpLRyVHU3wiy9pWDLYMujvvY7Tl8kNp0+pjXj8xRRpZioy5pMHpcBU5l5CiimTS4Pp+dapWjnkWH4AciwtySYOVSCggCxoVo4uqUwrrFOvb+2eWiQOWNfrDXvdslbO15vNZi2tqE4FIpTUqcVoWdBKpb7ClsUf6QBqN9PKqwI7ENJ34qVS/Y68WwukDrf5Y55SpsJLylXE6FH6CdJLjzMJRpl80g5uDqcTXgM8Qy2yt0OpZOTpnQeGnHIUNz8hn9KlVvGNVhVUFvLtIV5iIBtNQdFyU/c9KWLIpSV0i4YlrhExLorTAi8Z5c2apleNuWc05oCqNDz+i0dGgxHTveOEHVScMmOOIxJ2XuCBHw+IzCgkUx9lC/dD41Uh+W2bGhEfgHKyf6k7Je6FRqq+PqaAKS0/vFS7WJWfUsQkghWJZs6FzN3Z7+e6jLTGea8xyL+MdDZ0/QbSlTV1A+nX6USF+HrqdNRKgSMKka0HqskYhdLBsuQFn+pCTzScji/vrESBpJQfBKNaYsTGe2HjKuJCa1kjc2W1qkVf+vQCt+1pX/V6MkBMKrkuTpxuY6IwSF272QPH3BLXuA0cVCKyilDWc964mbpMc2E58RlkeEu8W4IgV63poatjwkn3OboWtKPF5wJFLFvRL9KUixTVSgy5tD+uhx3UQkCsrd8BKGymJXZ3Ekr6ChG8WykQNjW5OQ8PvMcHD7GvJzekkRDi9VnX6YJaQCC5+BCjIzwI1dBvZoJ+trGv2omdotNt1fn45iZ5niYlav8U3bp2/ZvfurJxlX39m9dvbLDrEBdix57CU5zJEjxQR6TukMNkqfRlW/VQe2L4Nw1VCdlGXPA7HlvqwBLBeeK65hKdXSpm55D9o73W9PVfMfmiPUs9K6mG1YMQ1C7wveYoaXVIMEvZS6TEUwWgQgqqXfPXarUK28abM2hZb1sVsJJnuNehXHCHmlas8E60tAOlqDrdFfx3WWgRC+2zFSuhRCRJP2D2rSOTbZ2bnI9DudrC8WKFKW+p0jQMdtkDLgC3rhdf/UeSSoUqdA0xHtirKyVtRaVk6qxOUF3F9TFtpTctupNEHWaLwkXJdAJtia/i1fMTHjhcv4BF5u4QFrBRNhGsbjFLKEIbO3g/dZq9UkeAk4Uf9UN35aCIIgIVcStTGqzfpWN5+yWZQwQ/FnfGpO9Vo3vQ8LXljxWzoFDgzZy4mWnfojgAO7mfddBlz9WslYSDWiEAaengTksAV4S4JMc0dqonOqnpGkiikv6OuIiAtVnNqtXkrekak2CGkjKW+khQtEuNg0ZSVIpPPxaXtAgc6Vprcmjx6gaZHWofyCwIXTsYz9kcplWPOItcqJkOBchA1katllNIwt009kzcY0JXCiijLgOlVOIke62YIAaOAer/PkS9pQwuRnp5bNai0AI39KcEPtintAs3ratjF6ygiuINcUXMKt0RU5/Vd+oE7UfG9J3odU4GL4UWjO12BhEJL11uhgdZ1GM6nhu41xKGtbC8kFDgspjk0nBqQ5KszpNr/8ZqboLZeMK/EAvwfMC/VnvKU7ejqvRksq5ymkPpKsp7Y1EEdBqH4mKEGc4GTY9TYOfHZJnbCIjZl1wzYOM9hfZup+9zEevo+TaV/0k0QY55WFg+SwOR1a0C9ZTG1pJOFZm2zdVjoupZa5s/vYSfgfzmLbgobPZerarnZ/hllhmM77/JmML/VGQKMf1eYPCw0ur9Gr17sl3ZLipPxt7N46n/jJvg7KgNr++VvaDKaYoCiyTMIGdx6N7bLXHnYojaoyTaAE/W8TJ3TxoXxdIHOfwIL9Ensc7QNpDJO37FSqmwI9sk1TEGNcv78DAJk2NZpenRdu7d9eJ63ryb6VW2hO6kia9nBHMikygFt8tfdEbbLAx67T083wYTzm3bhc6W6uV0ndTlutUpPOa57Fcrnrr1V136e6g0Hd1pkaSGZlxvTXPLW4enJY9So/g1N7og3n5tO7qQphe+qyYvDXXn0zSEYrrNqJhQO+ainELFvkBSOF5TsTx1I/2KnLENOyJB2cK92zJHmRCbquB6z/R4UV5Trgim2bRkBSe+ildgbG6dMmi0lmPOQZu+4VXoQyO9jNgoqyu90q9V5m7RyAvwIMQH4Ylw92bJSHzUCtsUV7/SPDxuf4klmNbhSdmViUwQ3QFuWKdUs3ILBBYxyFh85bocfZk/zdcnx2DP3AYgryrpTsJ9Y26RENr1HsSgVGDbJJeV6SxmhXCm5uxVk2kYyLkJnKIsqJrMwaP7plKgmZcy1dld44Nz/VXLGqw36yt2r3BSIlt9KgmaLYAZ0GZjFbcg4Z/MTClVoZk+wSFofPE0h44tpv52h9zrkFoWLqHaO0zmujO0wQ4MbS+VQE7uholX7MhwRC+VKUGX7eAcEZaUCQ/x4NRa+gTXnc9u4ZVIH392970fpKarZtRX9kNAOH7jnZO/fclItSr29kr8pPLCdRICWlkswJNlxARvTinKm/8bgKL6MGSZAAA= \ No newline at end of file From e4978a4f20216cb497b9e18e46cb765c5952313f Mon Sep 17 00:00:00 2001 From: LIghtJUNction Date: Fri, 9 Oct 2026 23:21:57 +0800 Subject: [PATCH 2/9] ci(browser): add verified patch segment 1 of 4 --- .ci/home-first-read.part0 | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .ci/home-first-read.part0 diff --git a/.ci/home-first-read.part0 b/.ci/home-first-read.part0 new file mode 100644 index 00000000..cd7247d7 --- /dev/null +++ b/.ci/home-first-read.part0 @@ -0,0 +1,32 @@ +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 From c8f2e9e30b892f2bbdc69a9d2220ef5bed66a116 Mon Sep 17 00:00:00 2001 From: LIghtJUNction Date: Fri, 9 Oct 2026 23:23:10 +0800 Subject: [PATCH 3/9] ci(browser): add verified patch segment 2 of 4 --- .ci/home-first-read.part1 | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .ci/home-first-read.part1 diff --git a/.ci/home-first-read.part1 b/.ci/home-first-read.part1 new file mode 100644 index 00000000..d39d46ac --- /dev/null +++ b/.ci/home-first-read.part1 @@ -0,0 +1,32 @@ +mCWAOqQ8Ng18vQVt9sA2lsrYHQGUNFaiHhMIuR2p5RYQvSgZj4ppIXMDuRe5FluoQsvLeyPXyLRpRCBrVWz5AibJIFCL2pNoYK4bWhsDiEhv8v0K27HdCUdtnRcrWaRe +S+XCoZAIJpDm6sYT3NsGFoYoMApKLvdKkqjlcrZ6wVDSu11MVFgQP0Y8BpAYJjGvVUKPU6EPjMshKMT4HB9bHDEG0S0Zu6Ck/F2rF46vYkYpNMqV04ukgGZKK9VlqQ9X +XZFNDnuB77rfdvrAbxfbQHePB/QtD57r9+zIB8I9JOcFjbLlhB1US2DRdDOMmiAeJhHDWpQ5BwIEOKEorR8l4Dv+TfpaTubveAAMnZMPKJWMeK4IxqsGdFFqQ84KqjqW +qF2KQF8AwwmgIhHRAWbjO5hx6Nt85HuicSuEopEaVT9wwFYDEgMDMxitajWZozrQgQUdjOwPDWUpQnBIQgzC5eRdFOy30sm5bK4BhNQOtRxFhrnlzB/6+RCe8N4kQveh +I5WuHwJP7jiB720aOAWnBObqn1999JmNK488cdXYOkSBTApaQI9SUeGykNmDwzQSKsnU1nMpat7Fcu2J1xuWlpYkrhklh7K9SxwGFC2trNUq7NwaRCH1ZrOWFWVVHhzA +EamBEpgiaAy1TN8Obhp5FfCHOHIvAgxVVs8jA0dPS/l18AftII4gOAyEI9CU/kJ7Q04tt9j5tRpYO3Ab/aAT9hCzNuFXKQaLouLytvHC0Hz0KUA+4P1JD4zyyEcKtQ3x +3SiAUM5/PBbGQXaKOoiPSgXFKRut82IePMv3SgZ+oNKAqWuPun1bVG5JGJY9HoPqK6F+pCcpzTgF0u73wc1yoo5Iv86g/mJWjbU3ty7EygrgXN2BD0+AkuAeauwQ+B9n +78Y+2MT9HQdzoOgSVRZntMHbl2L4Fq4SKXFLVOX9x5yA9yJnh5fLFxbveTCsbT/yS1JXLKvQosJ2beg8uFaO2zY8HuFKCweUlFEASkQ67YyStdAnFEkRZoOmpVIFI01Z +zBKVKFuR3xnaoOaI8ykQ2axtbcpkylYBDrp9yi+RawkIyZAJXFX2pGyRYwmKDVwYtKeERAHYh5gYAy2VXon1Tdfu3aRkOOiEXUy7jiahsJiuD2oKw8Su39+3ZtDV6gGr +3CyUEUG5IvNGxMRvYM8EPecK6437JrLkqg52vHSPbJTbl1wuom51uW65HwxdOTxngvF/NPVBaXHuhUM/Ik+5jUpPpuGr4A4QPQ/IRhyaB2QHDik+AF9kgIv0EIjuz8yl +OO5daZzem3x+kKm/8pw8kC/1CojVBUFHd6vT9fdK5U1j39hiF7OlcUJzB3NxueULZBedAPLd0AuYwdMVBo4YzZiaGLRCG7brlorcBK1LAvoURtLqb7FLbdZs3j+f0ATN +A/NJ0udMJ+9dq1n41aRsiElJkLl54GwERySuHogkWsQkHJOK/Htq8bMLsLI/0uVXLtK0G0n9hO/CZcRUOKCFaR7s6GF5pjsLwwx2LjsuD7ErbujDKPOg54AZ9HB9hWsK +31KzoxeU28nkq9h6Dp0+iA4b2I47CXjGfM52pbOerOfjurYBx0WhU8pqTo/1DHypOaxevsz8aWzQ/dj1M7Pn92ml/zSUOYXTcc1EwMe2EzAnlLkBUywB432xuKHPUfIA +J5w5iJdDUOiBiYB7Ye7n7B1bRi1xnv7ruEj0rPn6wYZ6fve+WHXPpLuiTerlwPHAtOrpDJUIGU4i0JVeOiWm50hScBKjYCjliatJoIeUPBMdwydWfzIahyVZCHS9F+LK +FDvsOY4YlgpzaOTbjTLS9llPEVbMJw2MAww/JIDyYRztV0V+oYdLcdnYnYSFOpSij/8CbHGDuMLsOyGxhTbLzIRGtwyZAHMGrNPBCdBOh3K4nQ6mwzodlUEWybHM4qnp +NXGpJXFy3dTppeTqqAZf7641ViyrD797zZ5aO4WLpOZqTS2gmqMkrp1qnKtV6ufYMv5trDF4JP0VdgD9DYD3zS6uCm6x1cZ47wI7XGDgFlKYaNImApDtyQhK7kMFyrS0 +2I4dlExzNIEwvXwBHEIoGjov8BarrygQ5NKgp8WUXmKXVMstJyzFrid4icJnhQ9qzrrMDhZw0sAJManVYtuB079Av82Ij8ZY3gRUJiOPdhfUxnts5Hgje69Uq7D6IACk +xGtz2x5Dx9YAqwWTZfpbuwD80UcPUz1pNgGSIEPXD0CRoVMGvYKnoe86fdlxcPR4+QKyzKkAEbXmOjYPVDEflCqX2LCBiwoeHA4WGjY0D30okrOS8kRqQULoviQ9cBd8 +AeUQjm2PrV1gtutseyam2FuMcrQXFsSmDofwgGdg1fcvMCJiz3Z7JUE/kac3hT+P6qEBdC/TCMVDPnD5nmqB5hYAAsdlcYAMjmm9hqMk6A88AGSuEZg0O67iA5dHUM8E +rHvQwxYzrVqdj7Txu8/6ZzWirVaXY+L7bEY2gTc9uunGWLyLqMUM4wKRvAXq1uMXhIvcYuew/2Kc5BcpFwHAm8CYrNYeVs+AXNZqRlJAkQD48lnRqgVOBbAtMGmaQxuo +c5bPDH6uWPz7tIjyfK9Mjw1nB3YKCP4GkxhIXKQ+zACnQkJyRQPrRfDvB08lXDUhUy3wCIbg9ETpFjBqFgulYvCOhxrWPK0VQhb0vqkYtEnyq1SwgALIAN9SWbFtrsCA +4VT7eK8KLKxe+p4P79DlMfu854tFwVI+aMFSqgutIW4/zJpIxf/UY8UL5gScGIjvaEncQUymddJBK2R95rezMHpRqI/NTFsJ/jq3o9JKJWMvyzqwSwBsB0BKQrZizShl +Xw4P8U2RccyCa4l1Kr2h4/YT0BlQtUSV1BACuS/n65UVcF9WavO7L6JdslIaYaiY4kph2Ney+n6lkPjCLZIDCGpCH7lGjYauluGktTQnAYQFs7rEniA9wER6PsBwHziK +hcDltgt6NwDty+3ekApgZGUDy1Eh5kQWW6qiIqW6B5qpDTjOt+xAXRf30pEHqHS5JofJkF4QGpkAuY42JJKadaANa0iS6qo7aTx3DFeaRVU0y2NmLE/SD7sLvAQkv4D7 +yXiUAU7mmEZupSYYMor8EfVGGqy69PQwmkfv1eu3GEKwA3BmQZVBk6WETwFu4KN2J+cEHPGHmcnW0BmpMOFX05w4uib5PbIHEQr9mXVI60Vidhtz9EnDtpLWPcB5Pnou +0b7gAJ82lZp8R+weFuost3uSL3J4rKD0GYywKfiHSCKUjiJJiib1eJzz3BGNToIU3e3yLC9FZ4ViWggNn/Qxp51Y3Uu0imyBqScQpuzqcxNwnAf7ZkxA9Dy52eXRLudZ +E46bcRH/xCayhrLioGyuC/st9iNQSA4u3u4QKhPQCmgX0XEmsxlen9TR8xOHR2zEIxu3qjBw/Un1LMfSP7eaQQMtx7AmnaYppTP9alm815X1Suz8EPHGuOqO7xZwaAwY +IzoNeEzzjHMiDYDmobBUkkewdNZDkSYlqzo170c9M+mhEAuNJwbbsfuxVqux+nkyHE35NgSZLufHH6sQgBT6JvfkSk07UOAXqBFDlTjtN2Ww7rn2aFxC3CussbMLv8CM +ljVXSvQDweR0pTGjK+LxboCcDeMdRfsUSaSH5lTPS0ob97ZBqHH5wj0kMqbrWlQaQBSWDtl4nhZEbImjE2s161wiRk3lU2jDsbbeG2q8fG89asTODpRxbPjrTUbA7b0W +i+zuxAVzAg/CfPii1xU2/QJcSndfevFzIpHbQAJnDlc6Wxs3Q6ghGdgjx91vpZ0vHQcyALq+Sa2RuN+hW0+GbpWGLitLa2ti8BjwLzjm4EainyqNZZ7yFHo8NlsAtG+H +Q55rtbKGUBQR30zaS1ue1j6FXSOxI20R6wk9W5JCUirY+6SZEGfwp50ezyT9cnTWVLwq+CmtautrWhQIXxHBOsV/GE/gIjcMJ1bX4Q88GPg+unAQkWGIjzBrTAeacU9U +BK7Xs9GcJFZOKNEpI6707VR4WrungE+2O542YTPjjqkpZmD6ABfqyBSE3pTVWA1QJ9OzXTlYoOinhq+5mqU9JVDXYumau01hQjCOAuthCSOyijnB05FYzTWQDexABrda From 9984a9a84bc779190caceaa50f9d9396668e0ed9 Mon Sep 17 00:00:00 2001 From: LIghtJUNction Date: Fri, 9 Oct 2026 23:24:16 +0800 Subject: [PATCH 4/9] ci(browser): add verified patch segment 3 of 4 --- .ci/home-first-read.part2 | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .ci/home-first-read.part2 diff --git a/.ci/home-first-read.part2 b/.ci/home-first-read.part2 new file mode 100644 index 00000000..e81ebe51 --- /dev/null +++ b/.ci/home-first-read.part2 @@ -0,0 +1,32 @@ +ghubjdslWrxQXGSMJTaHAR8stRdp70JvEoR+0F7cyjDxfdnfLC9NGWQZG6+cXydePr8meHmISlOLoE/PYeeQiMgDniNO73X3sRjEpqAkxZEOuK5L2t1QcxSVfyhVKfqN +udJMAizsOf6eld2pUVh9QVeO5ppIkegCbqoYsK6nKOKHp2TuAaZI/YqeZ2E2KXE/h9ZcIZOi+SZEgYyQphi3KeNkHMdzdRrHc/W1yvrZjuMCuzzifcdmJY2H1tfgtUz0 +P/A8zVR+JeMM42DF+JgP3qDUHfokA3iMvYQCcugakr7LZ9HkjNmSOTCpJZhMp22zeVqQpAKpOC0sPCT2yxnvtWYy3q0A7Ac0m5mEQdWq8WOzVjkP/LhyvnL+bPmRxXOL +raEdli6hSnVwbrGcQwo5hUc7AkKu6ufmKBNr2kxakmlAEd6mzI2KkNP8eEpmq7E2o5Ke/MhPbKSq65FoarClc1OUvSfN2NA4e0a8nJKAXD9X12CZwunYQipknbjJyiKc +F8R9NrhS+CBO16xqQ67ZhFxQ8eu+q+ZZplPX9UGQ7j9x6rnVSr0JrHquefasykQO0ImHwA+YVV8PGZ4TVtHSP+b0Oy3/k34O/uvQ7gPF4qdkYRRRSRhkXhH0jdg0Y4pT +E/FMmp0AF2GDHolwBU2oHkGndVzpM57tw6wGtCK6i22Z/kTGxCwGjtllbcpwNmvrrUwToJHf/0Ze9+VDxXQ5yPx7NYeURor2Ua2LYLPCWgO/B6GSXKeFSj/cQa1hezAk +AoFRuG26/rZv0p5T61zIepMubgDkLzg8KFm48b0OFFxZg7/laUEJN/0x97ZSsjNwuIuHIcRLLkDiJqqc/owi43AaobE/hs6tFQ5yYc46C0hMQJh4bFLdOh/KDDP0ZjVk +dcy0D+dSfliCZWHjqJpjNHh1qz6FKo0sLn8LL2Rqm31OrgWl562VENQc4bbEZBLfwZ1m8NSqr5ZZVQIGAWmWRbgrhiDyb3IPT2vaxGQmdtEDXLbkC2EYUzjLCmHIRzhH +V2/MHmpQ+APcVSNEOQd/gdcYTB4qnOQ1Ok4iC9ACozMJeV8EqefAo6+vgVo7X6usna1au3yT7w8Ce8TDmH2o/3RKzkFqqiDWIlKhoD7+Tgmd8DILcRNVyTq/XpaOh5kB +rLET8UTtYRRnxLeWnpAgnqo3Hq6w9cbDOgZ19a6W1NXnaXBepDwFaxqThPkSRGZOEaTT9uk0C2HUWH34AdL/iVZCQ4j/MF+kFzJDfxBLsOz+fWM83VxNn9uSRNP5IsP9 +xB41GB8aJDUaObwhZbKPs2fy4365DB5tlLxE1ZmakrJW1qWmXFkH8GurhdBrJOsSWq3Pt3UwxCynrduLT30qXLOnl5Dr9VabA97srVtWb2B3m7XVU9frpWAUrtVLlUKZ +P38ORH0ZfjdXUeJxr67U/51kzWNJPqqwpQodZQeRi1wG21rQj5wga4Hxn9j1K6Kajjh6hraaU+cqFLPgwWRtPG9Bbc1YXFy8zuNwH0vKZbwVcFbpFFtTHYHG+3KeWoDz +0FcXh7cyynHSSWcWHWMmlt0GYg+udtrORTWhT2cQtBcFrosMk9Sma3e56/J+d1+9MKkTi5cMHQQGA7J6JsRavHQRAkann6lOeJlOfd2LG7ykT8xfrA4b6SbsbANI4kWG +SaD2ojz7AxqjVQcacPVCHPJxsYrv04CpBnV26PSBylAnmPDFS+xff/i2LH+xasP/0ElVcys+foQILzYOi32i6aMVgmT/Q+YciYtj1SEtGb0I8uVy6COxxeKlO5/SsV90 +co04leb47V8f/583xYE1eH7Ix7+7+8Gto9d/Jk7K+cOL37tYHev9k6t+uQu44pJwwndTnsayVYRrEXYaYT2/IznhKV+e1SLXVOzzyCIs4rZDXtzQxFUtKcbKHH6AqOJO +p0LMtUMsONjFMS89P/FBR2HJTXEGxxZImj3gbaN6eelruKWFSuRtqBFRYQwJd+ogHDHC6owuAZlOctqa2vALpBqN8QwekZjp4NyIxEU7gGRrakE8UoXvRUiVzemtL8ZF +17l0EU+56wF+GsXURCwQLmfP9MC4OFxJFac+gKDYUnQOkECHi5cO6MWhYPXhyhS0LMLI+zFh4v2tOZvLUuM90DhLj4ahfUlxotSierxVPtRZaX5qaRopE08XEoomsfCw +L/wAhEkhlAzd4laKfQ4XmZEHjsjZXtSOBkv6SCyCPcOWitAhvSS7QDnPRaZgjseBv4dOLKd1+yG43idfvnHn88/FKWxfvfIatIXL/OMRWkxOtVlEO14+ZKIs+7+/EzCU +asxBhqYGO6hvSyVlsPCgzIPDMgHXBa2spEN9zRscUqUXq5Kb4RPw9ix+y6iM6sSNOSL7SlqH+L20x4ZhPec7eH4JFBe7EcjEaydbUUuo4MgWV8T3pQo5B/UaeQf1psz0 +51VNnVelgeiFwaBDPl3bMCoLGkUFLWXZZXUgvzwxS0JQfgV5ETgCZJErTJ7kFZML3Ec6CUOguw7xS40tNxrwd70I4UIPQDo5pCSUDInMSGxv6cCutBWnJ5duQNwBqhrV +iJFtZqyXxwDF8TpDBxuStcD1JWZk+/4EFL8HvjOtiBGZOCYPPAot9qgPEV8wIv/IRmnAQ/rxXB82CTHSoPqPPvFNskMaUyxkzy54iF1htM815UjhdQysy5m4MWB3iOfs +0sFrE9eFyArETm3JwJ1XkeO6dEStlRxKFo8jlsCh1A1g8jI+aS85H1Cdk4ak2lTHEYLKke6FeCG0rvQqteWsRvlwSkvTAiInJOuPmGT2V9GhJnGrm3pLhg75cMqnSIne +tIebdLOS4newl1nUBIHyLYZISeS4TWLSOuMx6RymYY/uahrODZwswA0SIXGYqy8KhcCMYQLUD8AtdPeZBsjKuFcZnPKWGGd9umxt/cxB3bsDXw70+g3kNgUWaTUbMY2/ +WeJ7FVCW1vfW1iorq6AqVpsi1VGkKdJVs8ZKaPRxyl6x1HrdxPoRZy+ioUXzjgYo/UadrKmMf4H8mnOxS35wQ0HJfCGImelp1l9ltAivvWiaXov21IlOSIjQhRTvaWiT +byvEQ7m4nFYEYXydln5Q9OZ9uZjmDKfJzPE5XCfpjtM6IPSgA1PelHQiwZxNQSEjdYrLPOUjmGfrjpkP4o7JQC+DVD4xc5luDgc0t8P/QQ7fNC7LKY9P193zen15MA0V +QZvpM4TMeaIK89So4tQAonj4DOGAFqmU2OE0NSxpe3VKSFv3yCC5Efb9R/xTLUj7WuwqTFcRpxKeSexfOMozHPZ81OODlHV39xSLlKv7FaAkJRU/0TuXFHtUfiJLkDFu +OakmWVG6tE97YKAVLHRIyamkXdHoX+D6bd63mOb5Ol7PnYALS84rONw7KO4KggVq7dT8anIlSHGGNVVG5lj767V+nzctq3b+/Oo5e3B6jjUNpTjLmi4nNhStn8egZFl8 +iJOtQqN3YDhL8p6y7Hhr2+/JOQEDmJygaSAR5S2AljyIUvAP5vTHdEweHg5jh3wArnqpvJBzXgmdnYBn+cUOELJiqk1xWCi+Bv//Ju7tzwOkacw2HhmYJ5wpsOiDElTk +p5wkigoXph34HGg5dGNqiilzEUP65hh1G4PeAR6C++77kXZGMOa1834UkjYeo5GtVFooOnVI3UtniUMc/UB+4JX4jZpvt+SDziSAGNh1Rk7UXs0HnKPe8CcmMgXLZhFK +S0txwUpxqeS0+HRy8vQqKU3cghDUl1MCqVRyuQDQYf5jGcS0k07qyh8kYHOrvNla3So6bu6ar24HSM7DEXMPuPvNphi3iifj4CEP2VC44KS5GKc0hQiNfGZQCaaYj1LM +OIONJNd0xnbvJo8qxQU3dV8llWMW2G6JpM9KrVGn1S0rtfp6pX7+3jRVfKcRB8yjoJRlZdBHPs7iwLDQ3A/G3eJcFXhCU0oFfF3QMb43dgLebysdGPb8MRfjTwYcoCrP +zQAlEJ+jLeupRotYbmmpdGDoQ2G0UiNzmK8pocmCkzwBYPFBZDObktcZCLWiZO+wGFgGseKCuQe8zqJ7jhGRpoe0S4FmKeUd4JxPvkeu3LiqShQNfPaw5tb0Yc2FNfFK From c259ec9df9fec7a04d26871d314a269a3b80024b Mon Sep 17 00:00:00 2001 From: LIghtJUNction Date: Fri, 9 Oct 2026 23:25:09 +0800 Subject: [PATCH 5/9] ci(browser): add verified patch segment 4 of 4 --- .ci/home-first-read.part3 | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .ci/home-first-read.part3 diff --git a/.ci/home-first-read.part3 b/.ci/home-first-read.part3 new file mode 100644 index 00000000..2650a961 --- /dev/null +++ b/.ci/home-first-read.part3 @@ -0,0 +1,24 @@ +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 +gKL6MGSZAAA= From 205aa885e7b666cb48d57095512478c3fbb61f42 Mon Sep 17 00:00:00 2001 From: LIghtJUNction Date: Fri, 9 Oct 2026 23:25:57 +0800 Subject: [PATCH 6/9] ci(browser): reproduce cold first-read failure and verify the scoped repair --- .../workflows/bootstrap-home-first-read.yml | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 .github/workflows/bootstrap-home-first-read.yml diff --git a/.github/workflows/bootstrap-home-first-read.yml b/.github/workflows/bootstrap-home-first-read.yml new file mode 100644 index 00000000..434e20ff --- /dev/null +++ b/.github/workflows/bootstrap-home-first-read.yml @@ -0,0 +1,131 @@ +name: First-read repair acceptance +on: + push: + branches: [fix/home-first-read-20261009] + paths: ['.github/workflows/bootstrap-home-first-read.yml', '.ci/home-first-read*'] +permissions: + contents: write +jobs: + accept: + if: github.ref == 'refs/heads/fix/home-first-read-20261009' + runs-on: ubuntu-latest + timeout-minutes: 25 + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: msgtest + POSTGRES_PASSWORD: msgtest + POSTGRES_DB: postgres + ports: ['5432:5432'] + options: >- + --health-cmd "pg_isready -U msgtest -d postgres" + --health-interval 10s --health-timeout 5s --health-retries 5 + env: + MSG_TEST_POSTGRES_URL_TEMPLATE: postgresql://msgtest:msgtest@127.0.0.1:5432/{database} + TARGET_BRANCH: fix/home-first-read-20261009 + BASE_SHA: 02da87322ff134edb031cce837fa683f8999a12a + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - uses: actions/setup-python@v7 + with: + python-version: '3.15' + allow-prereleases: true + - name: Verify exact base and transferred patch before applying anything + run: | + test "$GITHUB_REF" = "refs/heads/$TARGET_BRANCH" + git merge-base --is-ancestor "$BASE_SHA" HEAD + git diff --exit-code "$BASE_SHA" -- src tests scripts + mkdir -p artifacts + python - <<'PY' + import base64, gzip, hashlib + from pathlib import Path + encoded = ''.join(Path(f'.ci/home-first-read.part{i}').read_text() for i in range(4)) + patch = gzip.decompress(base64.b64decode(encoded)) + digest = hashlib.sha256(patch).hexdigest() + assert digest == '6e0b0767ccd7aa4d0b503792c0bebd6180968f7151c35c4b170650ff4148a571', digest + Path('/tmp/home-first-read.patch').write_bytes(patch) + Path('artifacts/input-patch.sha256').write_text(digest + '\n') + PY + git apply --check /tmp/home-first-read.patch + - name: Install project and independent browser tools + run: | + sudo apt-get update -qq + sudo apt-get install -y -qq age libpcsclite-dev swig + python -m pip install uv==0.12.20 + uv sync --locked --extra dev --python "$(command -v python)" + uv run python -m playwright install --with-deps chromium + - name: Demonstrate missing first-response posts on unchanged main source + run: | + git apply --include=tests/test_home_first_read.py /tmp/home-first-read.patch + set +e + uv run pytest tests/test_home_first_read.py::test_cold_summary_keeps_posts_in_first_response --junitxml=artifacts/before.xml > artifacts/before.log 2>&1 + status=$? + set -e + cat artifacts/before.log + test "$status" -eq 1 + python - <<'PY' + from pathlib import Path + from xml.etree import ElementTree as ET + tree = ET.parse('artifacts/before.xml') + cases = tree.findall('.//testcase') + assert len(cases) == 1 + assert not tree.findall('.//error') + assert not tree.findall('.//skipped') + failure = cases[0].find('failure') + assert failure is not None + assert 'First visit post' in failure.text, failure.text + assert 'href=' in failure.text + Path('tests/test_home_first_read.py').unlink() + PY + - name: Apply reviewed changes and normalize with repository formatter + run: | + git apply --check /tmp/home-first-read.patch + git apply /tmp/home-first-read.patch + files=(src/msg/transports/{browser_style,home_page,http_routes,thread_browser}.py tests/{test_home_first_read,test_browser_refinement,test_thread_panel}.py scripts/check_home_first_read_browser.py) + uv run ruff check --fix --no-unsafe-fixes "${files[@]}" + uv run ruff format "${files[@]}" + uv run ruff check "${files[@]}" + uv run ruff format --check "${files[@]}" + uv run python -W error -m compileall -q src + uv pip check + git diff --check + - name: Verify first reads and existing browser authority on real PostgreSQL + run: | + set -o pipefail + uv run pytest tests/test_home_first_read.py tests/test_home_cache.py tests/test_browser_refinement.py tests/test_browser_html.py tests/test_browser_languages.py tests/test_thread_panel.py tests/test_thread_panel_http.py tests/test_board_presentation.py --junitxml=artifacts/after.xml 2>&1 | tee artifacts/after.log + python - <<'PY' + from xml.etree import ElementTree as ET + tree = ET.parse('artifacts/after.xml') + assert tree.findall('.//testcase') + assert not tree.findall('.//error') + assert not tree.findall('.//failure') + assert not tree.findall('.//skipped') + PY + - name: Verify real browser navigation and render desktop/mobile themes under CSP + env: + PYTHONPATH: src + run: | + set -o pipefail + uv run python scripts/check_home_first_read_browser.py 2>&1 | tee artifacts/browser.log + - name: Commit only validated source on the task branch and remove bootstrap files + run: | + git rm .github/workflows/bootstrap-home-first-read.yml .ci/home-first-read.patch.gz.b64 .ci/home-first-read.part{0,1,2,3} + git add .github/workflows/home-first-read.yml scripts/check_home_first_read_browser.py src/msg/transports/{browser_style,home_page,http_routes,thread_browser}.py tests/{test_home_first_read,test_browser_refinement,test_thread_panel}.py + git diff --cached --check + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git commit -m 'fix(browser): show posts on the first visit and refine reading layout' + git diff "$BASE_SHA" HEAD --binary > artifacts/verified-source.patch + git rev-parse HEAD > artifacts/verified-commit.txt + git rev-parse HEAD^{tree} > artifacts/verified-tree.txt + git archive HEAD .github/workflows/home-first-read.yml scripts/check_home_first_read_browser.py src/msg/transports/{browser_style,home_page,http_routes,thread_browser}.py tests/{test_home_first_read,test_browser_refinement,test_thread_panel}.py | gzip > artifacts/verified-files.tar.gz + git push origin "HEAD:refs/heads/$TARGET_BRANCH" + - uses: actions/upload-artifact@v7.0.1 + if: always() + with: + name: first-read-repair-acceptance + path: artifacts/ + retention-days: 7 From 6b4f1e034050a476b1e746baf66399d8c900f7d9 Mon Sep 17 00:00:00 2001 From: LIghtJUNction Date: Fri, 9 Oct 2026 23:29:33 +0800 Subject: [PATCH 7/9] ci(browser): bind each browser error collector before acceptance --- .github/workflows/bootstrap-home-first-read.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/bootstrap-home-first-read.yml b/.github/workflows/bootstrap-home-first-read.yml index 434e20ff..57186035 100644 --- a/.github/workflows/bootstrap-home-first-read.yml +++ b/.github/workflows/bootstrap-home-first-read.yml @@ -84,6 +84,14 @@ jobs: run: | git apply --check /tmp/home-first-read.patch git apply /tmp/home-first-read.patch + python - <<'PY' + from pathlib import Path + path = Path('scripts/check_home_first_read_browser.py') + text = path.read_text() + old = 'lambda error: errors.append(str(error))' + assert text.count(old) == 1 + path.write_text(text.replace(old, 'lambda error, errors=errors: errors.append(str(error))')) + PY files=(src/msg/transports/{browser_style,home_page,http_routes,thread_browser}.py tests/{test_home_first_read,test_browser_refinement,test_thread_panel}.py scripts/check_home_first_read_browser.py) uv run ruff check --fix --no-unsafe-fixes "${files[@]}" uv run ruff format "${files[@]}" From 26077de6418bcf217ed325cdcbe42a4e28c105f0 Mon Sep 17 00:00:00 2001 From: LIghtJUNction Date: Fri, 9 Oct 2026 23:35:19 +0800 Subject: [PATCH 8/9] fix(browser): preserve raw cache contracts and assert rendered empty states --- .../workflows/bootstrap-home-first-read.yml | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/.github/workflows/bootstrap-home-first-read.yml b/.github/workflows/bootstrap-home-first-read.yml index 57186035..28778d4f 100644 --- a/.github/workflows/bootstrap-home-first-read.yml +++ b/.github/workflows/bootstrap-home-first-read.yml @@ -81,16 +81,28 @@ jobs: Path('tests/test_home_first_read.py').unlink() PY - name: Apply reviewed changes and normalize with repository formatter + id: apply run: | git apply --check /tmp/home-first-read.patch git apply /tmp/home-first-read.patch python - <<'PY' from pathlib import Path - path = Path('scripts/check_home_first_read_browser.py') - text = path.read_text() - old = 'lambda error: errors.append(str(error))' - assert text.count(old) == 1 - path.write_text(text.replace(old, 'lambda error, errors=errors: errors.append(str(error))')) + edits = [ + ('scripts/check_home_first_read_browser.py', + 'lambda error: errors.append(str(error))', + 'lambda error, errors=errors: errors.append(str(error))'), + ('tests/test_home_first_read.py', + "assert 'No public posts yet.' not in first.text", + 'assert \'

\' not in first.text'), + ('src/msg/transports/http_routes.py', + "or home_data is None\n or (browser_html and index.get('error'))", + "or (browser_html and (home_data is None or index.get('error')))"), + ] + for filename, old, new in edits: + path = Path(filename) + text = path.read_text() + assert text.count(old) == 1, filename + path.write_text(text.replace(old, new)) PY files=(src/msg/transports/{browser_style,home_page,http_routes,thread_browser}.py tests/{test_home_first_read,test_browser_refinement,test_thread_panel}.py scripts/check_home_first_read_browser.py) uv run ruff check --fix --no-unsafe-fixes "${files[@]}" @@ -113,6 +125,7 @@ jobs: assert not tree.findall('.//skipped') PY - name: Verify real browser navigation and render desktop/mobile themes under CSP + if: ${{ !cancelled() && steps.apply.outcome == 'success' }} env: PYTHONPATH: src run: | From c5249db16a7983bbc0176b2b4b886033a3452258 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 9 Oct 2026 15:38:50 +0000 Subject: [PATCH 9/9] fix(browser): show posts on the first visit and refine reading layout --- .ci/home-first-read.part0 | 32 --- .ci/home-first-read.part1 | 32 --- .ci/home-first-read.part2 | 32 --- .ci/home-first-read.part3 | 24 -- .ci/home-first-read.patch.gz.b64 | 1 - .../workflows/bootstrap-home-first-read.yml | 152 ------------ .github/workflows/home-first-read.yml | 79 ++++++ scripts/check_home_first_read_browser.py | 227 ++++++++++++++++++ src/msg/transports/browser_style.py | 90 +++---- src/msg/transports/home_page.py | 79 +++--- src/msg/transports/http_routes.py | 15 +- src/msg/transports/thread_browser.py | 10 +- tests/test_browser_refinement.py | 6 +- tests/test_home_first_read.py | 140 +++++++++++ tests/test_thread_panel.py | 1 + 15 files changed, 545 insertions(+), 375 deletions(-) delete mode 100644 .ci/home-first-read.part0 delete mode 100644 .ci/home-first-read.part1 delete mode 100644 .ci/home-first-read.part2 delete mode 100644 .ci/home-first-read.part3 delete mode 100644 .ci/home-first-read.patch.gz.b64 delete mode 100644 .github/workflows/bootstrap-home-first-read.yml create mode 100644 .github/workflows/home-first-read.yml create mode 100644 scripts/check_home_first_read_browser.py create mode 100644 tests/test_home_first_read.py diff --git a/.ci/home-first-read.part0 b/.ci/home-first-read.part0 deleted file mode 100644 index cd7247d7..00000000 --- a/.ci/home-first-read.part0 +++ /dev/null @@ -1,32 +0,0 @@ -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 diff --git a/.ci/home-first-read.part1 b/.ci/home-first-read.part1 deleted file mode 100644 index d39d46ac..00000000 --- a/.ci/home-first-read.part1 +++ /dev/null @@ -1,32 +0,0 @@ -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 diff --git a/.ci/home-first-read.part2 b/.ci/home-first-read.part2 deleted file mode 100644 index e81ebe51..00000000 --- a/.ci/home-first-read.part2 +++ /dev/null @@ -1,32 +0,0 @@ -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 diff --git a/.ci/home-first-read.part3 b/.ci/home-first-read.part3 deleted file mode 100644 index 2650a961..00000000 --- a/.ci/home-first-read.part3 +++ /dev/null @@ -1,24 +0,0 @@ -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 -gKL6MGSZAAA= diff --git a/.ci/home-first-read.patch.gz.b64 b/.ci/home-first-read.patch.gz.b64 deleted file mode 100644 index c7122066..00000000 --- a/.ci/home-first-read.patch.gz.b64 +++ /dev/null @@ -1 +0,0 @@ -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 \ No newline at end of file diff --git a/.github/workflows/bootstrap-home-first-read.yml b/.github/workflows/bootstrap-home-first-read.yml deleted file mode 100644 index 28778d4f..00000000 --- a/.github/workflows/bootstrap-home-first-read.yml +++ /dev/null @@ -1,152 +0,0 @@ -name: First-read repair acceptance -on: - push: - branches: [fix/home-first-read-20261009] - paths: ['.github/workflows/bootstrap-home-first-read.yml', '.ci/home-first-read*'] -permissions: - contents: write -jobs: - accept: - if: github.ref == 'refs/heads/fix/home-first-read-20261009' - runs-on: ubuntu-latest - timeout-minutes: 25 - services: - postgres: - image: postgres:16 - env: - POSTGRES_USER: msgtest - POSTGRES_PASSWORD: msgtest - POSTGRES_DB: postgres - ports: ['5432:5432'] - options: >- - --health-cmd "pg_isready -U msgtest -d postgres" - --health-interval 10s --health-timeout 5s --health-retries 5 - env: - MSG_TEST_POSTGRES_URL_TEMPLATE: postgresql://msgtest:msgtest@127.0.0.1:5432/{database} - TARGET_BRANCH: fix/home-first-read-20261009 - BASE_SHA: 02da87322ff134edb031cce837fa683f8999a12a - steps: - - uses: actions/checkout@v7 - with: - fetch-depth: 0 - - uses: actions/setup-python@v7 - with: - python-version: '3.15' - allow-prereleases: true - - name: Verify exact base and transferred patch before applying anything - run: | - test "$GITHUB_REF" = "refs/heads/$TARGET_BRANCH" - git merge-base --is-ancestor "$BASE_SHA" HEAD - git diff --exit-code "$BASE_SHA" -- src tests scripts - mkdir -p artifacts - python - <<'PY' - import base64, gzip, hashlib - from pathlib import Path - encoded = ''.join(Path(f'.ci/home-first-read.part{i}').read_text() for i in range(4)) - patch = gzip.decompress(base64.b64decode(encoded)) - digest = hashlib.sha256(patch).hexdigest() - assert digest == '6e0b0767ccd7aa4d0b503792c0bebd6180968f7151c35c4b170650ff4148a571', digest - Path('/tmp/home-first-read.patch').write_bytes(patch) - Path('artifacts/input-patch.sha256').write_text(digest + '\n') - PY - git apply --check /tmp/home-first-read.patch - - name: Install project and independent browser tools - run: | - sudo apt-get update -qq - sudo apt-get install -y -qq age libpcsclite-dev swig - python -m pip install uv==0.12.20 - uv sync --locked --extra dev --python "$(command -v python)" - uv run python -m playwright install --with-deps chromium - - name: Demonstrate missing first-response posts on unchanged main source - run: | - git apply --include=tests/test_home_first_read.py /tmp/home-first-read.patch - set +e - uv run pytest tests/test_home_first_read.py::test_cold_summary_keeps_posts_in_first_response --junitxml=artifacts/before.xml > artifacts/before.log 2>&1 - status=$? - set -e - cat artifacts/before.log - test "$status" -eq 1 - python - <<'PY' - from pathlib import Path - from xml.etree import ElementTree as ET - tree = ET.parse('artifacts/before.xml') - cases = tree.findall('.//testcase') - assert len(cases) == 1 - assert not tree.findall('.//error') - assert not tree.findall('.//skipped') - failure = cases[0].find('failure') - assert failure is not None - assert 'First visit post' in failure.text, failure.text - assert 'href=' in failure.text - Path('tests/test_home_first_read.py').unlink() - PY - - name: Apply reviewed changes and normalize with repository formatter - id: apply - run: | - git apply --check /tmp/home-first-read.patch - git apply /tmp/home-first-read.patch - python - <<'PY' - from pathlib import Path - edits = [ - ('scripts/check_home_first_read_browser.py', - 'lambda error: errors.append(str(error))', - 'lambda error, errors=errors: errors.append(str(error))'), - ('tests/test_home_first_read.py', - "assert 'No public posts yet.' not in first.text", - 'assert \'

\' not in first.text'), - ('src/msg/transports/http_routes.py', - "or home_data is None\n or (browser_html and index.get('error'))", - "or (browser_html and (home_data is None or index.get('error')))"), - ] - for filename, old, new in edits: - path = Path(filename) - text = path.read_text() - assert text.count(old) == 1, filename - path.write_text(text.replace(old, new)) - PY - files=(src/msg/transports/{browser_style,home_page,http_routes,thread_browser}.py tests/{test_home_first_read,test_browser_refinement,test_thread_panel}.py scripts/check_home_first_read_browser.py) - uv run ruff check --fix --no-unsafe-fixes "${files[@]}" - uv run ruff format "${files[@]}" - uv run ruff check "${files[@]}" - uv run ruff format --check "${files[@]}" - uv run python -W error -m compileall -q src - uv pip check - git diff --check - - name: Verify first reads and existing browser authority on real PostgreSQL - run: | - set -o pipefail - uv run pytest tests/test_home_first_read.py tests/test_home_cache.py tests/test_browser_refinement.py tests/test_browser_html.py tests/test_browser_languages.py tests/test_thread_panel.py tests/test_thread_panel_http.py tests/test_board_presentation.py --junitxml=artifacts/after.xml 2>&1 | tee artifacts/after.log - python - <<'PY' - from xml.etree import ElementTree as ET - tree = ET.parse('artifacts/after.xml') - assert tree.findall('.//testcase') - assert not tree.findall('.//error') - assert not tree.findall('.//failure') - assert not tree.findall('.//skipped') - PY - - name: Verify real browser navigation and render desktop/mobile themes under CSP - if: ${{ !cancelled() && steps.apply.outcome == 'success' }} - env: - PYTHONPATH: src - run: | - set -o pipefail - uv run python scripts/check_home_first_read_browser.py 2>&1 | tee artifacts/browser.log - - name: Commit only validated source on the task branch and remove bootstrap files - run: | - git rm .github/workflows/bootstrap-home-first-read.yml .ci/home-first-read.patch.gz.b64 .ci/home-first-read.part{0,1,2,3} - git add .github/workflows/home-first-read.yml scripts/check_home_first_read_browser.py src/msg/transports/{browser_style,home_page,http_routes,thread_browser}.py tests/{test_home_first_read,test_browser_refinement,test_thread_panel}.py - git diff --cached --check - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git commit -m 'fix(browser): show posts on the first visit and refine reading layout' - git diff "$BASE_SHA" HEAD --binary > artifacts/verified-source.patch - git rev-parse HEAD > artifacts/verified-commit.txt - git rev-parse HEAD^{tree} > artifacts/verified-tree.txt - git archive HEAD .github/workflows/home-first-read.yml scripts/check_home_first_read_browser.py src/msg/transports/{browser_style,home_page,http_routes,thread_browser}.py tests/{test_home_first_read,test_browser_refinement,test_thread_panel}.py | gzip > artifacts/verified-files.tar.gz - git push origin "HEAD:refs/heads/$TARGET_BRANCH" - - uses: actions/upload-artifact@v7.0.1 - if: always() - with: - name: first-read-repair-acceptance - path: artifacts/ - retention-days: 7 diff --git a/.github/workflows/home-first-read.yml b/.github/workflows/home-first-read.yml new file mode 100644 index 00000000..40258d6d --- /dev/null +++ b/.github/workflows/home-first-read.yml @@ -0,0 +1,79 @@ +name: Browser first read +on: + pull_request: + paths: + - 'src/msg/transports/**' + - 'src/msg/plugins/discovery*' + - 'tests/test_home*' + - 'tests/test_browser*' + - 'tests/test_thread*' + - 'scripts/check_home_first_read_browser.py' + - '.github/workflows/home-first-read.yml' + push: + branches: [main] + paths: + - 'src/msg/transports/**' + - 'src/msg/plugins/discovery*' + - 'tests/test_home*' + - 'tests/test_browser*' + - 'tests/test_thread*' + - 'scripts/check_home_first_read_browser.py' + - '.github/workflows/home-first-read.yml' + workflow_dispatch: +permissions: + contents: read +concurrency: + group: browser-first-read-${{ github.ref }} + cancel-in-progress: true +jobs: + verify: + runs-on: ubuntu-latest + timeout-minutes: 20 + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: msgtest + POSTGRES_PASSWORD: msgtest + POSTGRES_DB: postgres + ports: ['5432:5432'] + options: >- + --health-cmd "pg_isready -U msgtest -d postgres" + --health-interval 10s --health-timeout 5s --health-retries 5 + env: + MSG_TEST_POSTGRES_URL_TEMPLATE: postgresql://msgtest:msgtest@127.0.0.1:5432/{database} + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: actions/setup-python@v7 + with: + python-version: '3.15' + allow-prereleases: true + - name: Install test tools + run: | + sudo apt-get update -qq + sudo apt-get install -y -qq age libpcsclite-dev swig + python -m pip install uv==0.12.20 + uv sync --locked --extra dev --python "$(command -v python)" + uv run python -m playwright install --with-deps chromium + - name: Verify first reads, authority and existing browser routes + run: | + mkdir -p artifacts + uv run pytest tests/test_home_first_read.py tests/test_home_cache.py tests/test_browser_refinement.py tests/test_browser_html.py tests/test_browser_languages.py tests/test_thread_panel.py tests/test_thread_panel_http.py tests/test_board_presentation.py --junitxml=artifacts/browser-first-read-tests.xml + - name: Verify real navigation and responsive rendering under CSP + env: + PYTHONPATH: src + run: uv run python scripts/check_home_first_read_browser.py + - name: Check changed source and tests + run: | + uv run ruff check src/msg/transports/{browser_style,home_page,http_routes,thread_browser}.py tests/{test_home_first_read,test_browser_refinement,test_thread_panel}.py scripts/check_home_first_read_browser.py + uv run ruff format --check src/msg/transports/{browser_style,home_page,http_routes,thread_browser}.py tests/{test_home_first_read,test_browser_refinement,test_thread_panel}.py scripts/check_home_first_read_browser.py + uv run python -W error -m compileall -q src + git diff --check + - uses: actions/upload-artifact@v7.0.1 + if: always() + with: + name: browser-first-read-evidence + path: artifacts/ + retention-days: 7 diff --git a/scripts/check_home_first_read_browser.py b/scripts/check_home_first_read_browser.py new file mode 100644 index 00000000..c7fa34c5 --- /dev/null +++ b/scripts/check_home_first_read_browser.py @@ -0,0 +1,227 @@ +"""Verify first-view readability and navigation with actual HTML/CSP fixtures. + +The HTTP integration tests own cold-cache and authorization behavior. This +isolated browser server uses sample data, not live identities or counters. +""" + +from __future__ import annotations + +import json +import os +import threading +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path +from urllib.parse import urlsplit + +from playwright.sync_api import expect, sync_playwright + +from msg.plugins.public_board import default +from msg.transports.home_page import HOME_BROWSER_HEADERS, document_html, home_html + +OUTPUT = Path('artifacts/home-first-read') +POSTS = [ + { + 'name': 'first-read', + 'path': '/main/first-read', + 'title': '让第一次打开,就能读到内容', + 'excerpt': '页面不必等待全站统计。先读帖子,再继续讨论;每次读取仍检查当前权限。', + 'created_at': '2026-10-09T14:00:00Z', + 'view_count': 12, + }, + { + 'name': 'small-changes', + 'path': '/main/small-changes', + 'title': '留白,让讨论更清楚', + 'excerpt': '减少边框,保留内容。把标题、摘要和回复信息放到各自清楚的位置。', + 'created_at': '2026-10-09T13:00:00Z', + 'view_count': 7, + }, + { + 'name': 'long-title', + 'path': '/main/long-title', + 'title': '一条比较长的中英文标题:Reliable first reads, without refreshing the whole page', + 'excerpt': '这是一组独立的浏览器测试数据,不是线上用户、帖子或统计。', + 'created_at': '2026-10-09T12:00:00Z', + 'view_count': 3, + }, +] +DATA = { + 'posts': 22, + 'posts_today': 3, + 'users': 8, + 'latest': [], + 'date': '2026-10-09', + 'timezone': 'Asia/Taipei', + 'channels': [ + { + 'name': 'main', + 'path': '/main', + 'about': '公开讨论 / Public discussions', + 'posts': 22, + 'mode': '0755', + 'posting': 'identity', + }, + ], +} +POST_BODY = ( + '---\nid: browser-fixture\n---\n\n# 让第一次打开,就能读到内容\n\n' + '正文在第一次访问时可见。没有刷新,没有隐藏的加载占位。\n\n' + '## 阅读优先\n\n帖子列表与全站统计独立读取。统计暂时不可用时,帖子仍可阅读。\n\n' + '## 小范围的修改\n\n保留现有登录、主题和权限。\n\n' + '```python\nprint("read on the first visit")\n```\n' +) +BOARD = default() + + +def html(path): + if path in {'/', '/cold'}: + return home_html( + DATA if path == '/' else None, + post_index={'items': POSTS}, + login_enabled=True, + public_board=BOARD, + ) + if path == '/topics': + return document_html( + '# Topics\n\n## [让第一次打开,就能读到内容](/main/first-read)\n\n' + '公开讨论。\n\n## [留白,让讨论更清楚](/main/small-changes)\n\n' + '减少边框,保留内容。', + resource={'type': 'topic', '_post_index_next': None}, + raw_path=path, + ) + return document_html( + POST_BODY, + raw_path=path, + resource={ + 'type': 'post', + 'id': 'r_browser_fixture', + 'revision': 'rev_fixture', + 'created_at': '2026-10-09T14:00:00Z', + 'view_count': 12, + 'links': {'a': {'path': '/@fixture'}, 't': {'path': '/main'}}, + }, + ) + + +class Handler(BaseHTTPRequestHandler): + def log_message(self, *_args): + pass + + def do_GET(self): + path = urlsplit(self.path).path + if path == '/favicon.png': + self.send_response(204) + self.end_headers() + return + image = path == '/_public-board/art.svg' + payload = BOARD['svg'].encode() if image else html(path) + self.send_response(200) + self.send_header('Content-Type', 'image/svg+xml' if image else 'text/html; charset=utf-8') + for key, value in HOME_BROWSER_HEADERS.items(): + self.send_header(key, value) + self.send_header('Content-Length', str(len(payload))) + self.end_headers() + self.wfile.write(payload) + + +def verify(page): + assert not page.evaluate('window.cspErrors'), page.evaluate('window.cspErrors') + assert page.evaluate('document.documentElement.scrollWidth <= innerWidth') + assert page.locator('#content').is_visible() + + +def main(): + OUTPUT.mkdir(parents=True, exist_ok=True) + server = ThreadingHTTPServer(('127.0.0.1', 0), Handler) + threading.Thread(target=server.serve_forever, daemon=True).start() + origin = f'http://127.0.0.1:{server.server_port}' + results = [] + try: + with sync_playwright() as playwright: + options = ( + {'executable_path': os.environ['MSG_BROWSER_EXECUTABLE']} + if os.environ.get('MSG_BROWSER_EXECUTABLE') + else {} + ) + browser = playwright.chromium.launch(**options) + for width in (360, 768, 1440): + for theme in ('light', 'dark'): + context = browser.new_context( + viewport={'width': width, 'height': 960}, + color_scheme=theme, + locale='zh-CN', + reduced_motion='reduce', + ) + page = context.new_page() + errors = [] + page.on('pageerror', lambda error, errors=errors: errors.append(str(error))) + page.add_init_script( + "window.cspErrors=[];document.addEventListener('securitypolicyviolation'," + 'e=>cspErrors.push(e.violatedDirective));' + ) + page.goto(origin + '/cold', wait_until='networkidle') + first = page.locator('.post-title a').first + expect(first).to_have_text(POSTS[0]['title']) + verify(page) + assert page.locator('.posts .post-excerpt').count() == len(POSTS) + # Direct navigation, browser back and forward must not lose the body. + first.click() + expect(page.locator('#content')).to_contain_text('正文在第一次访问时可见。') + verify(page) + page.go_back(wait_until='networkidle') + expect(page.locator('.post-title a').first).to_be_visible() + verify(page) + page.go_forward(wait_until='networkidle') + expect(page.locator('#content')).to_contain_text('正文在第一次访问时可见。') + verify(page) + page.screenshot(path=str(OUTPUT / f'post-{width}-{theme}.png'), full_page=True) + page.goto(origin + '/', wait_until='networkidle') + verify(page) + expect(page.locator('.latest')).to_be_visible() + assert ( + page.locator('.latest').bounding_box()['y'] + < page.locator('.activity').bounding_box()['y'] + ) + for target in page.locator('.post-title a, .section-link').all(): + assert target.bounding_box()['height'] >= 44 + page.screenshot(path=str(OUTPUT / f'home-{width}-{theme}.png'), full_page=True) + page.locator('.section-link').click() + expect(page.locator('.page-topic-index')).to_be_visible() + verify(page) + page.screenshot( + path=str(OUTPUT / f'topics-{width}-{theme}.png'), full_page=True + ) + assert not errors, errors + assert not page.evaluate('window.cspErrors'), page.evaluate('window.cspErrors') + results.append({'width': width, 'theme': theme, 'passed': True}) + context.close() + # Also exercise normal-motion navigation; reduced motion must not hide a failure. + context = browser.new_context(reduced_motion='no-preference') + page = context.new_page() + page.goto(origin + '/cold', wait_until='networkidle') + page.locator('.post-title a').first.click() + expect(page.locator('#content')).to_contain_text('正文在第一次访问时可见。') + page.go_back(wait_until='networkidle') + expect(page.locator('.post-title a').first).to_be_visible() + page.go_forward(wait_until='networkidle') + expect(page.locator('#content')).to_contain_text('正文在第一次访问时可见。') + context.close() + # The repair is server-rendered, not dependent on browser scripting. + context = browser.new_context(java_script_enabled=False) + page = context.new_page() + page.goto(origin + '/cold') + expect(page.locator('.post-title a').first).to_have_text(POSTS[0]['title']) + verify(page) + context.close() + browser.close() + finally: + server.shutdown() + server.server_close() + (OUTPUT / 'results.json').write_text(json.dumps(results, ensure_ascii=False, indent=2) + '\n') + print( + f'{len(results)} viewport/theme cases plus normal-motion navigation and JavaScript-disabled first read passed.' + ) + + +if __name__ == '__main__': + main() diff --git a/src/msg/transports/browser_style.py b/src/msg/transports/browser_style.py index 2e8b6233..d623c4cc 100644 --- a/src/msg/transports/browser_style.py +++ b/src/msg/transports/browser_style.py @@ -270,17 +270,26 @@ .account-groups summary { color: var(--muted); font-size: 13px; } .page-home #content > section:is(.activity, .latest, .channels) { display: grid; grid-template-columns: 200px minmax(0, 1fr); column-gap: 56px; - margin-block: 0; padding-block: 44px 52px; border-top: 1px solid var(--line); + margin-block: 0; padding-block: 40px 48px; } -.page-home #content > section:is(.activity, .latest, .channels) > h2 { +.page-home #content > section:is(.activity, .latest, .channels) > :is(h2, .section-heading) { grid-column: 1; grid-row: 1 / span 6; align-self: start; position: sticky; top: calc(var(--header-height) + 24px); - display: flex; align-items: center; gap: 10px; margin: 2px 0 24px; font-size: 15px; letter-spacing: -.01em; + margin: 2px 0 24px; font-size: 15px; letter-spacing: -.01em; } -.page-home #content > section:is(.activity, .latest, .channels) > h2::before { +.page-home #content > section:is(.activity, .latest, .channels) > h2::before, .section-heading h2::before { content: ''; flex: none; width: 7px; height: 7px; border-radius: 50%; border: 1.5px solid var(--accent); } -.page-home #content > section:is(.activity, .latest, .channels) > :not(h2) { grid-column: 2; } +.page-home #content > section:is(.activity, .latest, .channels) > :not(h2, .section-heading) { grid-column: 2; } +.page-home #content > section:is(.activity, .latest, .channels) > h2 { display: flex; align-items: center; gap: 10px; } +.section-heading { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; } +.section-heading h2 { display: flex; align-items: center; gap: 10px; margin: 0; font: inherit; } +.section-link { + display: inline-flex; align-items: center; gap: 8px; min-height: 44px; padding-inline: 17px 8px; + color: var(--muted); font: 12px/1.5 var(--mono); text-decoration: none; +} +.section-link:hover { color: var(--accent); } +.activity-unavailable { margin: 8px 0 32px; color: var(--muted); font-size: 13px; } .stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); } .stats > div { padding: 0 24px; border-inline-start: 1px solid var(--line); } .stats > div:first-child { padding-inline-start: 0; border: 0; } @@ -291,37 +300,26 @@ .stats span { display: block; margin-top: 6px; font-size: 13px; color: var(--muted); } .activity-note { margin: 20px 0 0; font: 12px/1.6 var(--mono); } -/* Latest posts ride one signal wire; each post is a node on it. */ -.posts { position: relative; list-style: none; margin: 0; padding: 0; } -.posts li { padding-block: 16px 22px; } -.page-home .posts { padding-inline-start: 34px; } -.page-home .posts::before { - content: ''; position: absolute; inset-inline-start: 5px; top: 30px; bottom: 0; width: 1px; - background: linear-gradient(var(--line-strong) calc(100% - 64px), transparent); -} -.page-home .posts::after { - content: ''; position: absolute; inset-inline-start: 5px; top: 30px; width: 1px; height: 72px; - background: linear-gradient(transparent, var(--accent)); opacity: 0; pointer-events: none; -} -.page-home .posts li { position: relative; } -.page-home .posts li::before { - content: ''; position: absolute; inset-inline-start: -34px; top: 24px; width: 11px; height: 11px; - border-radius: 50%; background: var(--bg); border: 1.5px solid var(--line-strong); -} -.page-home .posts li:hover::before { background: var(--accent); border-color: var(--accent); } -.post-title { display: flex; justify-content: space-between; align-items: baseline; gap: 8px 28px; } +/* Reading-first index: whitespace, a strong title and one quiet metadata row. */ +.posts { list-style: none; margin: 0; padding: 0; min-width: 0; } +.posts li { padding: 0; } +.posts li + li { margin-top: 30px; } +.post-preview { position: relative; padding: 4px 0; } +.post-title { margin: 0; font-size: inherit; } .post-title a { - display: inline-block; padding-block: 10px; margin-block: -10px; - color: var(--fg); font: 600 19px/1.4 var(--sans); letter-spacing: -.015em; text-decoration: none; + display: inline-flex; align-items: center; min-height: 44px; max-width: 100%; + color: var(--fg); font: 600 clamp(19px, 2vw, 23px)/1.5 var(--sans); + letter-spacing: -.025em; text-decoration: none; text-wrap: pretty; } .post-title a:hover { color: var(--accent); } -.post-engagement { color: var(--muted); font-size: 13px; } -.post-engagement .muted { font-size: 13px; } -.posts p { color: var(--muted); font-size: 15px; line-height: 1.7; margin: 4px 0 0; max-width: 68ch; } +.post-engagement { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; } +.post-engagement .muted, .post-engagement .reply-link { color: var(--muted); font-size: 12px; } +.post-engagement .reply-link:hover { color: var(--accent); } +.post-engagement time { font-family: var(--mono); font-size: 11.5px; } +.posts .post-excerpt { color: var(--muted); font-size: 15px; line-height: 1.8; margin: 5px 0 4px; max-width: 66ch; } .empty-state { - margin: 0; padding: 28px; border: 1px dashed var(--line-strong); border-radius: var(--radius-panel); - color: var(--muted); font-size: 15px; text-align: center; + margin: 0; padding: 24px 0; color: var(--muted); font-size: 15px; line-height: 1.8; } .notice { display: flex; align-items: flex-start; gap: 12px; margin-block: 16px; padding: 14px 18px; @@ -360,7 +358,7 @@ footer nav a { min-width: 44px; justify-content: center; padding-inline: 10px; color: var(--muted); font-size: 13px; } footer p { margin: 0; font: 12px/1.6 var(--mono); } -.page-topic-index .prose > h2 { font-size: 1.25rem; font-weight: 600; line-height: 1.45; margin-block: 44px 6px; } +.page-topic-index .prose > h2 { font-size: clamp(20px, 2.2vw, 25px); font-weight: 600; line-height: 1.5; letter-spacing: -.02em; margin-block: 40px 6px; } .page-topic-index .prose > h2 > a, .page-topic-index .prose > p > a[href*="post_cursor="] { display: inline-flex; align-items: center; min-height: 44px; min-width: 44px; max-width: 100%; } @@ -398,7 +396,7 @@ /* The byline sits under the title as one quiet row. */ .post-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 0 20px; - margin: -6px 0 36px; padding-bottom: 14px; border-bottom: 1px solid var(--line); + margin: -2px 0 40px; padding-bottom: 4px; color: var(--muted); font-size: 13.5px; } .post-meta p { margin: 0; line-height: 44px; } @@ -718,7 +716,8 @@ @media (max-width: 860px) { .page-home #content > section:is(.activity, .latest, .channels) { display: block; padding-block: 36px 40px; } - .page-home #content > section:is(.activity, .latest, .channels) > h2 { position: static; margin-bottom: 24px; } + .page-home #content > section:is(.activity, .latest, .channels) > :is(h2, .section-heading) { position: static; margin-bottom: 20px; } + .section-heading { flex-direction: row; align-items: center; justify-content: space-between; } } @media (max-width: 640px) { :root { --header-height: 60px; } @@ -740,9 +739,9 @@ .account:has(> .primary) { flex-direction: column-reverse; } .stats > div { padding-inline: 14px; } .stats strong { font-size: 28px; } - .page-home .posts { padding-inline-start: 26px; } - .page-home .posts li::before { inset-inline-start: -26px; } - .post-title { align-items: start; flex-direction: column; gap: 2px; } + .posts li + li { margin-top: 24px; } + .posts .post-excerpt { font-size: 14px; } + .post-engagement { gap: 0 14px; } .preference-row select { width: 150px; } .post-meta { gap: 0 14px; } .post-meta dl { grid-template-columns: 1fr; gap: 2px; } @@ -775,14 +774,9 @@ transition: color .18s ease, background-color .18s ease, border-color .18s ease, box-shadow .18s ease; } .primary::after, .thread-branch-chevron, .settings-chevron { transition: transform .28s var(--ease-out); } - .thread-node::before, .page-home .posts li::before { transition: background-color .2s ease, border-color .2s ease, box-shadow .2s ease; } + .thread-node::before { transition: background-color .2s ease, border-color .2s ease, box-shadow .2s ease; } .brand:is(:hover, :focus-visible) > svg { animation: msg-logo-turn .7s cubic-bezier(.22, 1, .36, 1); } .preferences[open] .preference-fields, .account-menu[open] .account-menu-links { animation: msg-pop .26s var(--ease-out); } - .page-home .posts::after { animation: msg-signal-run 1.9s linear .35s 1 both; } - .page-home .posts li::before { - animation: msg-node-ping 1.1s var(--ease-out) backwards; - animation-delay: calc(.3s + 1.9s * (var(--i, 0) + .15) / var(--n, 4)); - } .token-art[data-running] .token { animation: token-assemble 12s cubic-bezier(.22, 1, .36, 1) infinite; animation-delay: var(--phase); animation-play-state: paused; @@ -796,16 +790,6 @@ @keyframes msg-pop { from { opacity: 0; transform: translateY(-6px) scale(.98); } } -@keyframes msg-signal-run { - 0% { top: 0; opacity: 0; } - 12%, 82% { opacity: 1; } - 100% { top: calc(100% - 72px); opacity: 0; } -} -@keyframes msg-node-ping { - 0% { background: var(--bg); border-color: var(--line-strong); } - 25% { background: var(--accent); border-color: var(--accent); box-shadow: 0 0 0 5px var(--accent-soft); } - 100% { background: var(--bg); border-color: var(--line-strong); box-shadow: 0 0 0 0 transparent; } -} @keyframes token-assemble { 0%, 12%, 100% { transform: translate(var(--dx), var(--dy)) rotate(var(--turn)); opacity: .38; } 38%, 65% { transform: translate(0, 0) rotate(0deg); opacity: 1; } diff --git a/src/msg/transports/home_page.py b/src/msg/transports/home_page.py index 54fe4c82..25c1a98d 100644 --- a/src/msg/transports/home_page.py +++ b/src/msg/transports/home_page.py @@ -97,6 +97,43 @@ def account_navigation(account, *, compact=False): return links +def latest_posts_html(index, statuses=None): + """Render the independent, already-authorized post index, never false empty data.""" + parts = [ + '

' + '

Latest posts

' + 'Topics' + '
' + ] + if index.get('error'): + parts.append('

主帖列表暂时无法读取,请稍后刷新。

') + elif not index['items']: + parts.append('

No public posts yet.

') + else: + parts.append('
    ') + for item in index['items']: + path = escape(quote(item['path'], safe='/@*&'), quote=True) + title = escape(str(item.get('title', item['name']))) + stamp = display_time(item['created_at']) + parts.extend([ + '
  • ', + f'

    {title}

    ', + ]) + if item.get('excerpt'): + parts.append(f'

    {escape(item["excerpt"])}

    ') + parts.extend([ + '
    ', + f'', + f'{int(item.get("view_count", 0))} 浏览 · views', + reply_link((statuses or {}).get(item['path']), item['path']), + '
  • ', + ]) + parts.append('
') + parts.append('
') + return ''.join(parts) + + def home_html( data=None, *, @@ -107,6 +144,7 @@ def home_html( public_board=None, csrf_token='', reply_status=None, + post_index=None, ): def link(label, path): keys = { @@ -186,10 +224,18 @@ def link(label, path): '' ) + # A bounded post index can be ready while the full-site summary is still cold. + if post_index is None: + post_index = ( + {'items': data['latest'], 'error': data.get('latest_unavailable')} + if data is not None + else {'items': [], 'error': 'unavailable'} + ) + parts.append(latest_posts_html(post_index, reply_status)) if data is None: parts.append( - '

' - 'Statistics and latest posts are temporarily unavailable.

' + '

' + '统计暂时无法读取。 / Site activity is temporarily unavailable.

' ) else: parts.append( @@ -206,35 +252,6 @@ def link(label, path): parts.append( f'

{escape(data["date"])} · {escape(data["timezone"])}

' ) - parts.append( - '

Latest posts

' - f'
    ' - ) - for index, item in enumerate(data['latest']): - stamp = display_time(item['created_at']) - parts.extend([ - f'
  • ', - link(item.get('title', item['name']), item['path']), - f'
    ', - ]) - parts.append( - '
    ' - f'{int(item.get("view_count", 0))} 浏览 · views' - + reply_link((reply_status or {}).get(item['path']), item['path']) - + '
    ' - ) - if item.get('excerpt'): - parts.append(f'

    {escape(item["excerpt"])}

    ') - parts.append('
  • ') - parts.append('
') - if not data['latest']: - parts.append( - '

主帖列表暂时无法读取,请稍后刷新。

' - if data.get('latest_unavailable') - else '

No public posts yet.

' - ) - parts.append('
') if data.get('channels'): parts.append( '

Channels

Only channels you can read are listed. Sign in to include your private channels. ' diff --git a/src/msg/transports/http_routes.py b/src/msg/transports/http_routes.py index d80dde40..e9e3f700 100644 --- a/src/msg/transports/http_routes.py +++ b/src/msg/transports/http_routes.py @@ -2989,18 +2989,15 @@ async def decorate(value): browser_html = 'text/html' in request.headers.get('accept', '').casefold() renderer = home_html if browser_html else home_markdown reply_status = {} - if browser_html and home_data: + index = None + if browser_html: from msg.transports.thread_browser import reply_statuses, root_post_index index = await root_post_index( service.executor.execute, service.settings.service_url, limit=5 ) - home_data = { - **home_data, - 'latest': index['items'], - 'latest_unavailable': bool(index.get('error')), - } - latest = home_data.get('latest', [])[:5] + # Post reads must not depend on a cold/failed full-site summary. + latest = index['items'][:5] statuses = await reply_statuses( execute_packet, [item['path'] for item in latest], @@ -3021,14 +3018,14 @@ async def decorate(value): getattr(service.settings, 'oauth', None), 'enabled', False ), expired=request.scope.get('state', {}).get('msg_browser_expired', False), - **({'reply_status': reply_status} if browser_html else {}), + **({'reply_status': reply_status, 'post_index': index} if browser_html else {}), ) headers = { **(HOME_BROWSER_HEADERS if browser_html else BASE_HEADERS), 'Content-Length': str(len(payload)), 'Cache-Control': 'private, no-store' if request.headers.get('cookie') - or (browser_html and (home_data or {}).get('latest_unavailable')) + or (browser_html and (home_data is None or index.get('error'))) else 'private, no-cache', 'Vary': 'Accept, Cookie', 'X-Msg-Home-Snapshot': 'unavailable' diff --git a/src/msg/transports/thread_browser.py b/src/msg/transports/thread_browser.py index 6b4345ed..d0a16652 100644 --- a/src/msg/transports/thread_browser.py +++ b/src/msg/transports/thread_browser.py @@ -124,7 +124,7 @@ async def titled(item): source='manual', ) ) - title = item['name'] + preview = {'title': item['name'], 'excerpt': ''} if response.error and response.error.code in { 'permission_denied', 'not_found', @@ -134,10 +134,10 @@ async def titled(item): }: return None if not response.error: - title = thread_preview({**item, 'content': wire(response.data)['text']})['title'] - if title == 'Untitled post': - title = '未命名主帖' - return {**item, 'path': '/*' + hex_id(item['id']), 'title': title} + preview = thread_preview({**item, 'content': wire(response.data)['text']}) + if preview['title'] == 'Untitled post': + preview['title'] = '未命名主帖' + return {**item, 'path': '/*' + hex_id(item['id']), **preview} return { **page, diff --git a/tests/test_browser_refinement.py b/tests/test_browser_refinement.py index d8812a80..93dc330c 100644 --- a/tests/test_browser_refinement.py +++ b/tests/test_browser_refinement.py @@ -92,14 +92,12 @@ def test_home_counters_are_translated_without_invented_activity(): parsed = Elements(html) markers = {a.get('data-i18n') for _, a in parsed.tags} assert {'public_posts', 'today', 'users', 'no_posts'} <= markers - assert ( - 'Statistics and latest posts are temporarily unavailable.' not in html.split('', + 'excerpt': '', + 'path': '/main/post', + 'created_at': '2026-10-09T14:00:00Z', + 'view_count': 2, + } + page = home_html(None, post_index={'items': [item]}).decode().split('