diff --git a/.github/repair/project_history_payload.b64.00 b/.github/repair/project_history_payload.b64.00 new file mode 100644 index 000000000..872d0f192 --- /dev/null +++ b/.github/repair/project_history_payload.b64.00 @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/.github/repair/project_history_payload.b64.01 b/.github/repair/project_history_payload.b64.01 new file mode 100644 index 000000000..c0d4e5f6f --- /dev/null +++ b/.github/repair/project_history_payload.b64.01 @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/.github/repair/project_history_payload.b64.02 b/.github/repair/project_history_payload.b64.02 new file mode 100644 index 000000000..496ba6419 --- /dev/null +++ b/.github/repair/project_history_payload.b64.02 @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/.github/repair/project_history_payload.b64.03 b/.github/repair/project_history_payload.b64.03 new file mode 100644 index 000000000..4068a4b77 --- /dev/null +++ b/.github/repair/project_history_payload.b64.03 @@ -0,0 +1 @@ +tIt2aWmHs3DWHfVWSnY0NVbLRmtJfrMURZZjbBeuK/M0RXYHz+3Kr67qhhLBnzKGQHSJGHYDeF9//TWD+KJpVPgh4/ZnzKkXlqzIDpjO44Tl7Z9LSlm5dO8F1pV6SBhXw2gedGsVc7eslroY2JEcw+qDxOp9CtuGbxLs03bu08wZcEMa27DQtuvFbL/Yxc4Bj7h3Zs6NHzqY+lVcVkU2asQNJ6dsbSXVrnW0u3O4ezwQibmcPxTQ7KnLg4xUEsW8KgSyCsIS022qk9IFi8xjEi4/bgKIJ6fKMNL6cdpgltRn0YRb8uj1QCWwdcZYvxfAfO/4MdWgu6gCwFSQE900zMLKtwtvFub9BuorLVIZxklsh4F/o8QPd5+ALM4jITaevsSY+oaRGaHnscZVQP3yvcG4UBH1EeVynuXX/Nh0gY9UxVRJptSkCcqxIntwIDSgsiyqE58Hg3Uz8FK5mZYX3A80Ea30X0DYNQW0DY3gO+fUxyGwGSiGz72r5mfI90LHzgQMMF88wpqQ6sNN21DUXjZYl7QMxHHF2+OdngHUwAvME4c14ZUruTAegJcDA0tfhJwvEqw+l/XK4O/6n4CkQW0+YD7bn8KmVMTftLg5NAY2ZeF9WDpiYOkeEjPP/gBixsii1woa3rL5KUTNqcIzeXONJzSmHigU/5Q21QMl5p8StcXi9NLY3NtixRxuBZnIwBNd8DXbWu7ELEOQ1coa3Hrg4ny4a0MeTPiksd8HZ3M5TI1gVtO+AjAbDV8Qb0LYABi2GUkGVcGF1xLQd6pxtDASSGPrGCy83P2690jispBuvAre59Kwj4poyp/+g7mjNVVkmZ3A3hiiBQecop56jKIQvt7erq9Wq64LSrXGTaOWqYIjEyMUEmI369P1nsSKzfI4/3SUPyGh+IfznO+Xi5eeC+xjVCwyB/e1PoVbkom8k0d7ElEf/n3ycOMTl4emtucSsZgHX+Ro+LEEAh7hqFtjVSqn5IWqouUIRl82WaNkzutxua4gP7HGYTQDIKc6UBiLbq2rgniMrAHtJIOTwmHyKdahPI7mVB0qXuvNIItD/VPd/KgS0PyUn8P9Sh2uXLJ7DQIno9OT2ojAKQsCDF88JW7rS3GTqjZ6L+9UaTF25qDa+RAsMcHYoi/NyTiVPCrXY0XVZVmOPqNbptUZ5aQGESv5wJ2QtHJhfinkiE6dIPHGA/jpxAMHmj3Nm0IJqzcr4i+FEemkNdalM7Bl5Zm4FDBzkkvfO08vABzA20cZN3InhWF/iJzZZZ+8AQM8njlj2ieHr75nf440oA+DFF46zv6/Xrc9O/twf/8YzBekZNe2kT1su4e5xqF/Rbs9vE2M3AsyRhb0weH+93uvdwE4H2RNkrcsNxzHayk7r1WuIKXMPEwSX5r3Dn5s3I8n23zp4nStyySZxZtrayBpE/ohmTv+tRe74RR8kuGFl1zOz4deuFaslV9F/q9WTxbl472fkJQVNACL6+vr4fWzYRhdrG2MRl+uofOiAB9W6+eH4AcxTuRKAXrbv5fN3Sonb2WyU/AUr/Uei6QtvE+fUFZ2JBx7otk8bFubq0AQ85I5XKpJXKJgCwofWCkmhPgGjWlXbLY+SvWpk2xZyTxCH6ynMoTQgN2DH4c1ZWm5oBvG8/MdvDC0P+kTeVYeHlOUcY6/C5opuenhqQSbmSa+5bup25n6lcVxYYoMaaDGjEbpjdfVRxo39XA751tjGF86seCMXeH451zBiZTcyDMGQ3ceU9cYnpPQ98FFzXDVIGpxWowZnAmwgTFMhSgSZ73HIaPnkPXHlkQYrIbh0c2UlQAZp1i3i6fuuv84RFlK4x67qFLA9AFbN+1olNu6h9TxBwf846N/vsY7hknkgIWb27x4CVqux1WKZ24lR9RxyTR0qf9opq/BG9DyGOjcgc6eDuOnsvkZnHnko5XN9GUKDD6LZz7eHYZX84C9VpzdfN68RaOJZZhdKD49i2/G4exiQ20xxMMpAeNffVU0FC6SK26dBlfJJaHq3x1fKVfN3n71094bbFmH9brjIQ2uvCgMhhc06aroC+v13pvd7R92/7W7/fOufbx7VOiLlw1lyaoJKxX3v/rgl/jh2PEv4ZO19GNpObySbkym1GznyvHAp/RFUeDzMPSVPIYkulF6jh1wiP0+FA0xu/n6yS6fgMBaO4TzZGujJ9tas4qcuB0tH8cW92E+jOksl4fDfTBfmDoFrwSv0G5q4qUQFm2fxbjYxatK2DqsUDYufu/NvInSdkejrZZt+yrQwLaKw2BrYgUhvh5fIixSsOkwzYZGxJGV2rc570qnBq6qsHCdxGHNe9w46GZvMJLIyiMwwQH/VWJlZinh2WZqJRU2f09tZdm+kHswN8y6HKGhHdGZjyEqtAyBT9ZuSxO/s3ort1ZpgcaJ9wFteJWlrvo+6Zy7WmsM3ssObLKEEodMYPde+jdk6l1ELOE4HUJUGpif+95YVurHIFup22fNhnnyS9aP+jFcJ/xT4g7pwO/EKkZyF1zQW7T7h/jnOZhgl/TDyeb6xumdpeRmu1PwfbfuU7o9ZbhDZ56EvIk2DKGqMxkncHjjeRSHEbCfExP+Wllh8seH9AOWA6JdMPCGIOi71pizZsaHkvIeJMGQx0MZyD3gusSbeDQqS8per/fYZlVRbOMByXIxrmwc5R3M6ziqOKDyEDoUyJip2GndHEct4ayu4lEUF4kYaVkbjUbrrAyU5/h2PL6kU2cI3GWhJ+a4Np4IdXu9VcT72bP03n6cwLZCUXZFAwcl8ccxgRejBfGbd59vfQq64IS6NQHGC1g81guSkKBIDwPbD8P385nNmuqaGAL/dQVQzBm6AGr3SfpB6EqHpcWjLHGzZwJDNlXZkKK1dK4dzCML0fCxKTuTslkdzU6fWFIDAJzpzAlu4MHOjniJjpBlCE2WYMmuV7DTIhyGW2bymB7w59rGLvKugKoK6IkHTeJ3FY7Z7TpEDF7jf1jjUBJOJNFLG422iN5UA72fwKenidm15XnnMVjviBiokvS15MLuzyPCnuxJIlcrB8DAwZw34OQkl1ggoHrEDSVlVBAml15wsSKS/g+jwMpy15j2WhDoKmpLPIqmeJ8UFQPnsVVUZKJE12Bn//BgsI1bNb3cIinhgH6EPT9uQY+VsPxOE8vvSliakCo8VoUFlhfYyHPlQ981nPipSBk2JY9VnL3FeW2SjE7svF1pvyGNERgmAwgxxg7dHN/v9u7+GIJxDi9tZzwO50FiTCiCa0SjAF3X+TlvCiDNza4Xz3wnk4lTLNDquG5EY9kiw/XygctDNDgqKZ8CZRVLkksWkcZG3gJlO30TYiL7Ug6hysz+QT840xkms9I4MS2/imxky+corKrYyWcEgqcoIsKAdnsno9PHCC0X/7EgPbg/RsQJu0En0x/jfnAFtWMOaO7mmYPJI75YyMgczM+NQHEjZ8KRIr+z7F3Mlpa/l1mzE3TObx5BsxnUblUNVygjago8U3K8UmlB6GkbbAgiv9raN4kth3seujd9kvr/Sq5Kvh/F4/lmMouwWDa2I8y5QUXV/x9xn1T/LzdC/qQx5HKVKm5RG9xdxhaoa3KlCxvIJNjUYzgpqKVToyOY36UTK810Z07NLRvhzjI6hoXVSbC6FJYTsdD1YaPIelBYYgpATc0il0sXo2BbklFMNhmD2DMCyYggSjuCJFG3zujtPbbVK0Qh8/6NuBbYrTayNs2Z0gzqPkIlzrXDe+HKHkOWQ2rwvMXPXPAV9qcfjJ4N1p8fj77eHI3gf/82Yi2Z4TorntGxcXoeAVBv4o15jT6scn5hiK780EcQ9tlg9OVgtL6ahHWp77Fi76Koj2kavxLwSVo0SIu63y1S9/lgfX2w/tWKUpdZl22RFoGboeuiNHgBHFtmWukIG6K3Zf2ySuuBtonpZSgXlze8DCA4Ngbro5Xj7j9I9FoyLX3xJjaL2HYLdd/6pFi0zWyo+QA45fkGniLt70tGl48jJ4gxzRBzAUyEblfiUIdXOwctj5adER7rCtOuXy0ln34suf2rJl29IWZG+nW5FSV/Y6k0UYRhc1sJ8TVkQ9WaSaamXbVxNCmQgsPpa5s3tRaMsYkXzQ+Ts9ayPGqtC1NTZrVRa5vKcE0tN0ZBr9drY320DUk58F43Sd4BVHQ3vdXW0xgnYmorb3TRF5EpSVqGY9D5EYuSYFSoIJz/gIcGeoYFo53Rw4aShSIZqit3Wck4xfwZQ7l7iTye5fKhPVPJSov2VzHib0lG/FuI9BtgtlU9KDAplfpZHPAEtsHpCsVLMxHPEJO+/bUycdasvmNbNrmegZuhJyruZBAN29/mDNI6jKvA27OihbGli3ux8wyHaZreq4tnz4R9xo+yQQCyjkWFusgqlZdSNclarJT2659mWkPNmTaX48QzaQLJQeo+0F3LrHUmWbrw/u5aZlFb7Jf12MajZLJI75PPyshNGs6kZlMncuicz8xCL8tXk5AzAzTlt9OP/fhf+2iehjNMIm0ztMFKKoC6M2SNim4hxDq6CZJLmnhjwhp8yJZAZB2iTJlCs2kZowOl4oE/OYFzYRCr3JT8kZfVEL2hnpRUWQes0go+KWK9P6DtVe1BZDbelbcfkjUFCjdIlfJGH4wjsTzR7GJqz9QNj0LmZ9rTaSWtDLYmKxR84RroI4+8FNrdGI69ZNqurmOWwtEQO/3Eo6FRnngijgdfDDZGpc8lofMwkVGNqhpTQBqlb6ZCqWodmlao9WV6wKZOLaORr1zR1zebVDpDXN8YrL+om/qG5tTbCqEpT3cJ0UpnkoXpsis9smOsUEBLKChmnOOU+yRvbyd9oUT0qumTYmSrKg//jG01jG0t7Q20KseRNa0rTQeT6ltMqjEmZ+mMSQ2fl34UJ56rG/ASpl8ujk5TeWRgqWuCPStg9RYr2bE6i+qVjSde4Pi+XnW9Ijp6dZZvPOq70mUzb4s1Bu9+v81ufb2rVCh511Gsl6lLpXaKWdaos6ygpRuFsxUtZ8lcHU4LNW5p+/es6wG7ZW/zfiQ2v+br/YYKi2s9bHNaZLtNeaezX1fwJS+mrNy3qVSP07l2vKyPg7minFqV3vNWtgw5E91olwkkLVltcc2pp6tOahZZ/cCg9xTSiy+TvpTXaAiUtb0sdVNnLVGt/X3ZtmB5Hr2lgdMJO8ASLWnzU0vACt0j9il6MZImfLXRLZvjierS1SfAG4JWyjI3BJNlgajAOtVYydv0sLDQ3ba6nrI5yRwMMkBdZ9s7XNVbbZotphaZA1nMpFGBeqexIFh8K1+ThQ7ALSyITvvCkyi8BsmUn1YIloGPNQTAfQ1+NYVC9cyTn4D1DcBLTyzTM0gTMNNTPQ0sdYTDyYUzO7Hgj+06N7FYWnirsbQgMt3wikY2gMlWc31jOOqT56Ph6FRz56jSPJsomIZezLWrbHwSazf4tMCtduo4K8fHCvReRkEV0D0T5kgWvTkP54HrRDdKVCu4F4VCRbEdBv6N9Yl2wJ14fgISU0TvwTZ2Z6EHwo31wgUrBW/0FIWeElcva2WiVvJLzy0r+ly1rp/0weWy2Q15Exvrd6tP1pXYXMFf9sJhNA+6zT1mA5WyWPzhgc6donFC3qMeG1mOMbpORcF/4ofj94sdO5U61Xcm1MG+O9IdZ+S2oZl+lWmDRMk2QZKottVNsNPmryWFXOce1rO92E4uvci1Awyp+TZ6nkzKse5SN6LvnsxoSmKIbeysywXmFih0ytDqkyGPbgRuDfbUNIFtCusR0HZBoNAIOV0ZcQ4i6zg5wD4wa68yuLoYis5bZtETQIfxpTZ6zntaJZ/1k/hQB7g8Kkwt4B611TJQFz3sSuUJSY9mOTwWQNGGVr4XoQ1uqRerArm3qYaNsNonaZsvtp5Y/g77SdIPHjbsloF7mzPEHauel4kZLfx8GnRzyKkVZ1u9Hvl2i2y0zekijslos5VPKcWjjnYyQwg6L+FaMFTBABBjKfKcwNALXPoBISowmGw159QpTJ3dZdTTm5EuvzbYjGrUylZsgfbKlMSNUI7ys1lnxQgYLQxDri6gOviITmiEAYFFRp/JJ/N3ijk2PcsYEVrH0hxBWSaG6CFXo59s1vXTkozFdTTkdYpYNW+HYGdOxlDB3PdJgcQyIxTCP7qKJUW0gbkrS77cvtVE6/PCJDVgxePImyXxWkzzcEM6VfD85dlDWJrqGLH0i0xsa4jsqrHIbvwKT+YTc5edmcfc4vP5DY3seB5NHNgKTkTtay+i7uP5yI5Xco8tpDUN3DVnNlvDL5GlNB0cmGtpiEkUBgmOEUdjGMcbJrH+ELPlQ2zPZjDEh8d1pdLNwYJXgqhDQHNY4YQ0oCUXd6tJIuHSBVZMGVcpgnT+gZO5oGAO4RquCWzitduCgrxbS5HrvevICyaNuVj1iTb6RLLoB7ZeGFNlmzY9BvyxsAxAEO0BJvOA5zWxmRgYxCouteC62wr2B05AfXJHGNdaMkGS4Ro2CgMhFAAPlIEee1OK3b6tl5wJgHEehUcjUPjUicaX3cj6pmaeJ+/exe/eHZ1+IbgkLUW/JXcdogPzQXFy99ln2wd79vEvB7tHeLzxAKPsspyZ23cBIctqDrCTGy+4eIk/qmTeLPuOdYKt+zJrfZN+Xig0VP49uDXFD8nvzMoTsMqGYGWg4pc1AwZJ6IcXN7YXefUPsr4ImzDc9JxG8NXdO9ioy+jICHiYpjowQj5QXqI06AP1Hkq/XUyUuefruhXQoFoGHNtx8p+mFCquDM8b2SQTzBl5iHSHJd94O3Ngyuy41IOuTqHmhkPp+0K2SGVq9RcXHvzRIonzhPaaj5PQMC/fS9wfxXH9D84spSdAC+fxPRQEYMk9X2OeAqsyt/ApbNaFzzB1o7CJ+IcszWGT3Ju08ODMhMlS4hJQ8+XFLgQdFuUQoFAjBk9OS6ye/6i0x7NfLcsV2nyIvwWEUkLFZs26id8lEehgzAvbJOch8J0TlHkjzYMAst6b1ZDStcMUxPe7xzs/FhTEfYq+i+M5Y/Cs4+MQTMiUoP0Cpf8nXwD2se9NvQTgb4xG8L7H5jf1YlpRgd/yZQSpEmOOR+RM8Q5uQK/J28PXR0xvHrBPu7cc5CY5YoN02bseueuJ5WCtM4SJ+z3OoToSmwUhZ2VD8W+3QETY7W8P93ZS06Gbz6mXGY9//9stx2+YhAKF3t1ZnwMtEId90suJLe5DgFPw9ngXCS5rsgZ5cjzzHBbTtot5F+lGEIgJmsEmgqH/OafwewDkzP1kC1eGXNCt9T7x6daL0aiXT4ZrwB3UzEGyzd8DgFd0BtSNu6iyxvzLtBFX+vAMWHQzy34/gHeV5/AH+GOW2pGXL9kObk43OQjLsg75cmInNsbe1K1JEnAmIBnI4XfbOwQ2TX6PcRs+4dkAHB6okF/n4M9yKYo07KZIiwkzArMbI4jd0Bmz34sbI2EQCMTY+SjH7P48+qJhhs/3yx8Vr/GVvxF4DWsyL+LKT9nKbrG/hW96OGm+xw/sg+03u6+R5YrP1dmgZcgldt66Lby5q/6warPeIomHeeBQrMjf/05OThceFhx0AI/sufzRPXfhV2GwD8yDP9q6DYMj6gNkfFf54dq3fN47P26/+WH39f4P9uHu693tI7bl/vpXcrIx3MBEPzIg/HbwV4ONET7yV/hy2wUOwzcDcnxJefAo7QU2cGnieD75DmMkRMRISBBeEwqqzY2B9zLWGzBmTanCxWDKrYnwP4ZkO5PShGWH90lcrBY+4PndfZJet+6Tn/d32Bojm0f03HOFGiPxpRNRtk0Sir6U45MIkWW8DJLd98Zekmm0DM/+ogbjehYslbjPhhEaepCtYaquUI/HQyIK04QRgmIFtfMNGNEp/DKGCeC2R4uOo5uX4MNBco3JRwS4DBZwffwSvw7HHvz4igIRg/dAZ5hpQBEDMGVieA7oh/uTmZx448KFJYtJd/vVIRmtgzgbip1wuPPj3jGI4LeHu/YR/Hdv/w0yBV98slu7doWFE9saf362JEZ41idntSY86oez+jj6GZ/12YN27hkeiE8xUepdwPNZGOsc8ESAo3++JkLtwyKyW+JiBmk0lrE0qHwg/jyGVcGMK8bM7wLkbwJcdpEyAWKUyZ5CWzCSGhnknMIYlABXsYYc8SXFHfAuYJPLF7i4vn3gEiLyC0tsxFFjSIhNNgtnc+CCCNkm4qgCXUBb4W5MNQFKcDIFne3DD6JwfnFJzmqE2tlLTrP9f70eoPO/Bpr458E+0oclOngBOXPDcbyWeoRr1Z7E4pfDJPHPMBMe58m2SQi+hCMok+fa9dlZBIiD1Azjm45bYkgDwaqAPzgiSP8Sr3Y+OzreP/zlu/39/6lh04N7+ZKtZxrwSL/JRM4ZrOlVSk8MkPg0oTkkxHtQWK53wIXBBez94hLypNFMHAz4Hnf7uXnK+RnEUHKT/QxAJbif+UojVthMHNiQxFmyOWYoskfZto5gvyOaMaZ+cNINgNwBtysDFI8pFVNOS3fOFeUopCw2wBRmUL9J5IB9hbJCJBu68zE/4MmCRbnUeEm+9y6mDjk7+m52ESb/NT+++unDrvPh7fVVOB4dnSGxXErOno2+2tw44+v2Gdpm17ABKcu1i7vsb7U4W57L+pmwcv6FjxAnY+yYJiSckLfH3w++glnBxvDYOjJwaNF8lqbH8MnjhMcsxMzOVPnPACRYy73NLHSEOYtpMmD6XK/0rcgHHE7fu17UFcmBW8fRHKuWoy60w/fsbeUxPmUWyRZowO/RmobF2rLmyWTwldXj1GGJd+CPhMAUXXw498xAPPiueAWGf+U65meZRcgAMF1HP8CCwlKPYZexFZ+g7GBX5Fw8/MFlPo/we9BMHliJbka8IjEyNPi0cB7wFZtZHqJfnA/+FhMDKLtqgj/Kac2tw/Qn+N2QGXRdmGKP/GWLrBd+64A7RI5uYliw3Q9e0p1YYkoMaQR+m6F4t0luAcbJ5lej079EdwKN6iqwAQWpcUhGUEwNWLou4IuArK1flqkTvaeReOO4roebZsnybDM4bHVAps6nXD3H4lIsmiPhHKT8HC0Sh0lzL2lhUTjK965LPc3Qc52Bwf9FNlF4CR6EdR/l/BsbNDANusu2di5r+OYU+pXd6kQuBa0eFqxN/uvUxsx3fFG0/HC4u/vG/n7v9e4Rn3VpZ2UTrj1B6+dfLx4IoW9UNAVEbP5o/+3hzq59sH90bO++3vth77u913vHv9hgdyB1jEO8D2D9mVWtE5bhpkOkzFN/x1318RxgT0GxTB3YtNE7dM4Lvy97+l9IPX8vmnVHlQU0FwNlQPlt72g+nTo8Tlb4dX448IXEkwbQqwSYYKAdrjO6dZElwr3B9H0xfpQ/KQJs9NuaOfL41hePPXxzQqXnwQVK5cdhaAbvoD77bp4kgLQ4C3tXOeCq/OwdxvjMA6yDt/y87l2TQ7giplokqx6DfQMWnw926xvQIFvvLOZPDBjkS9CHuOe+vcX9+CYEMcu5HA37KzAgWOoIv5ICH0UU/CCX3NBkCPv07pu12bclUpSH7d0t/fKbNaEFWYBiyY9u/zIBrRmzQPNroI9zQcl//ifmPX8ak/2C5PGoLxRJcC+fPJS8X6Dizv7BL6R0p+L5iIcGBZHs1GfATC2yxkEPAK3ohl2YY8Dd86G79nxjwEJE4sFB8cHy0j3eoPeN+VBa2v3jPhtUHeTSwxoLVbjGUKDZ70CkF/bnPfLyZc3Hv/N0tsK3946bXWUoQHqXzG7AMA2ekVI6mwuuLbvZB+ZAeRUbPbDkR4tJcvcRzJrdiAeGYDL4rLMGePD8GtE7a2O4/l/D0Ttr8XMMcsLndSAzWToDswe21/B/45DVnnyXwnhnbVaAL3y1HH6B8dIY7HDqFsnHw7AIvBiGXf+6ROTFAO4XDZ58iOfiNceN1g53t1/9tFvB6ndycuZNgR4D4cQOxAXGqXt22h0O15Z82SPw5Gj05ZenXfw7YO46xm9FHAPshkHpUf5MmaMef/Dy2OmOznZyurczBO77BcdifTQCLODv4IHfVlar6HtaxSgtWyC26E2Ds/DzujBvzTiMF9gz52H4HsQP6huOoRjz4J5BFiJ0whlE56Fb6xX7N+Rw9xW4fx/wkmpMxJXXLArH/Dl0ZXyKTjO/wZA7xhEsJpZtii7Y6+F2dMGc6wP2TbdX+NkQPFfbEd93LQyQAsbjy9AD+3ara0W8XyPzWK2eIE10ETP3mgFg/0EQcTfzqPHdEGGxwj8II/eqi34pTLLolVI/pvkPS77yZ58BWJv5+LbNoNo20s+2BWhOzM/+PwpvSSe/VwUA \ No newline at end of file diff --git a/.github/workflows/apply-project-lifecycle-history-pr.yml b/.github/workflows/apply-project-lifecycle-history-pr.yml new file mode 100644 index 000000000..c23b540b0 --- /dev/null +++ b/.github/workflows/apply-project-lifecycle-history-pr.yml @@ -0,0 +1,223 @@ +name: Apply project lifecycle history on PR + +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: + contents: write + +concurrency: + group: apply-project-lifecycle-history-pr-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + red-green-project-history: + if: github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'feat/project-lifecycle-history' + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16-alpine@sha256:57c72fd2a128e416c7fcc499958864df5301e940bca0a56f58fddf30ffc07777 + env: + POSTGRES_PASSWORD: postgres + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U postgres" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + env: + LINEAGEWEAVE_TEST_POSTGRES_ADMIN_DSN: postgresql://postgres:postgres@localhost:5432/postgres + REPAIR_BRANCH: feat/project-lifecycle-history + REPAIR_BASE_SHA: ${{ github.event.pull_request.head.sha }} + steps: + - name: Checkout exact contributor head + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # actions/checkout@v7 + with: + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: true + fetch-depth: 0 + + - name: Materialize bounded patcher + shell: bash + run: | + cat .github/repair/project_history_payload.b64.* | base64 -d | gzip -d > /tmp/apply_project_lifecycle_history.py + python -m py_compile /tmp/apply_project_lifecycle_history.py + + - name: Set up Python + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Set up locked dependency manager + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + with: + version: "0.11.28" + enable-cache: false + + - name: Select pinned Rust toolchain + run: | + rustup toolchain install 1.97.1 --profile minimal + rustup default 1.97.1 + + - name: Install Python dependencies + run: uv sync --frozen --extra dev --extra backend + + - name: Set up Node + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # actions/setup-node@v5 + with: + node-version: "24" + + - name: Install frontend dependencies + working-directory: frontend + run: | + corepack enable + pnpm install --frozen-lockfile + + - name: RED - reproduce absent lifecycle contracts + shell: bash + run: | + python /tmp/apply_project_lifecycle_history.py red + set +e + uv run --frozen python -m pytest -q tests/test_project_history.py > /tmp/project-history-python-red.log 2>&1 + python_rc=$? + (cd frontend && pnpm exec vitest run src/components/ProjectHistoryTimeline.test.tsx) > /tmp/project-history-frontend-red.log 2>&1 + frontend_rc=$? + set -e + cat /tmp/project-history-python-red.log + cat /tmp/project-history-frontend-red.log + if [ "$python_rc" -eq 0 ] || [ "$frontend_rc" -eq 0 ]; then + echo "RED unexpectedly passed; refusing to publish an unproven change" >&2 + exit 1 + fi + grep -Eq "ModuleNotFoundError|No module named.*project_history" /tmp/project-history-python-red.log + grep -Eq "Failed to resolve import|Cannot find module|ProjectHistoryTimeline|ProjectHistory" /tmp/project-history-frontend-red.log + git reset --hard HEAD + git clean -fd + + - name: GREEN - materialize normalized authority, API, and Buyer surface + run: python /tmp/apply_project_lifecycle_history.py green + + - name: Refresh and verify universal lock + run: | + uv lock + uv lock --check + uv sync --frozen --extra dev --extra backend + + - name: Verify focused Python and real PostgreSQL contracts + run: | + uv run --frozen python -m pytest -q \ + tests/test_project_history.py \ + tests/test_project_history_backend.py \ + tests/test_project_history_postgres.py \ + tests/test_project_history_schema.py \ + tests/test_project_history_ontology.py + + - name: Require 100 percent changed-module statement and branch coverage + run: | + uv run coverage erase + uv run coverage run --branch -m pytest -q \ + tests/test_project_history.py \ + tests/test_project_history_backend.py \ + tests/test_project_history_postgres.py + uv run coverage report \ + --include='lineageweave/project_history.py,backend/app/project_history.py' \ + --fail-under=100 + + - name: Verify full Python repository and semantic profile + run: | + uv run --frozen python -m pytest -q + uv run --frozen python -m compileall -q lineageweave backend/app scripts + uv run --frozen python - <<'PY' + import ast + from pathlib import Path + from rdflib import Graph + + for path_name in ('lineageweave/project_history.py', 'backend/app/project_history.py'): + module = ast.parse(Path(path_name).read_text(encoding='utf-8')) + missing = [ + node.name + for node in module.body + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)) + and not node.name.startswith('_') + and ast.get_docstring(node) is None + ] + if missing: + raise SystemExit(f'{path_name} missing public docstrings: {missing}') + Graph().parse('docs/ontology/project-history-profile.ttl', format='turtle') + PY + + - name: Verify Buyer component, accessibility, build, and Storybook + working-directory: frontend + run: | + pnpm exec vitest run src/components/ProjectHistoryTimeline.test.tsx + pnpm run test + pnpm run lint + pnpm run build + pnpm run build-storybook + + - name: Commit only exact-head verified product changes + shell: bash + run: | + rm -rf frontend/dist frontend/storybook-static + rm -f .github/workflows/apply-project-lifecycle-history.yml + rm -f .github/workflows/apply-project-lifecycle-history-pr.yml + rm -f .github/repair/project_history_payload.b64.* + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -- \ + lineageweave/project_history.py \ + backend/app/project_history.py \ + backend/app/main.py \ + migrations/0050_project_history_lifecycle.sql \ + migrations/rollback/0050_project_history_lifecycle.sql \ + docker/postgres-init/Dockerfile \ + docker/postgres-init/migrate.sh \ + scripts/seed_project_history.py \ + Makefile \ + tests/test_project_history.py \ + tests/test_project_history_backend.py \ + tests/test_project_history_postgres.py \ + tests/test_project_history_schema.py \ + tests/test_project_history_ontology.py \ + frontend/src/api.ts \ + frontend/src/App.tsx \ + frontend/src/components/ProjectHistoryTimeline.tsx \ + frontend/src/components/ProjectHistoryTimeline.css \ + frontend/src/components/ProjectHistoryTimeline.test.tsx \ + frontend/src/components/ProjectHistoryTimeline.stories.tsx \ + pyproject.toml \ + uv.lock \ + frontend/package.json \ + CHANGELOG.md \ + CHANGELOG.d/2.20.0-project-lifecycle-history.md \ + ARCHITECTURE.md \ + docs/adr/README.md \ + docs/adr/0100-project-lifecycle-history.md \ + docs/doctoring/PROJECT_LIFECYCLE_HISTORY_REFERENCES.md \ + docs/ontology/project-history-profile.ttl \ + docs/project-lifecycle-history.md \ + docs/storybook-inventory.md + git add -u -- \ + .github/workflows/apply-project-lifecycle-history.yml \ + .github/workflows/apply-project-lifecycle-history-pr.yml \ + .github/repair/project_history_payload.b64.00 \ + .github/repair/project_history_payload.b64.01 \ + .github/repair/project_history_payload.b64.02 \ + .github/repair/project_history_payload.b64.03 + git diff --cached --check + git commit -m "feat(projects): add evidence-bound lifecycle history" + test -z "$(git status --porcelain)" || { + git status --short + exit 1 + } + + git fetch origin "${REPAIR_BRANCH}" + remote_head="$(git rev-parse "origin/${REPAIR_BRANCH}")" + if [ "$remote_head" != "$REPAIR_BASE_SHA" ]; then + echo "branch moved from ${REPAIR_BASE_SHA} to ${remote_head}; refusing unverified publication" >&2 + exit 1 + fi + git push origin "HEAD:${REPAIR_BRANCH}" diff --git a/.github/workflows/apply-project-lifecycle-history.yml b/.github/workflows/apply-project-lifecycle-history.yml new file mode 100644 index 000000000..8fc6c8ed2 --- /dev/null +++ b/.github/workflows/apply-project-lifecycle-history.yml @@ -0,0 +1,223 @@ +name: Apply project lifecycle history + +on: + push: + branches: [feat/project-lifecycle-history] + workflow_dispatch: + +permissions: + contents: write + +concurrency: + group: apply-project-lifecycle-history + cancel-in-progress: true + +jobs: + red-green-project-history: + runs-on: ubuntu-latest + services: + postgres: + image: postgres:16-alpine@sha256:57c72fd2a128e416c7fcc499958864df5301e940bca0a56f58fddf30ffc07777 + env: + POSTGRES_PASSWORD: postgres + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U postgres" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + env: + LINEAGEWEAVE_TEST_POSTGRES_ADMIN_DSN: postgresql://postgres:postgres@localhost:5432/postgres + REPAIR_BRANCH: feat/project-lifecycle-history + REPAIR_BASE_SHA: ${{ github.sha }} + steps: + - name: Checkout exact branch head + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # actions/checkout@v7 + with: + ref: ${{ github.sha }} + persist-credentials: true + fetch-depth: 0 + + - name: Materialize bounded patcher + shell: bash + run: | + cat .github/repair/project_history_payload.b64.* | base64 -d | gzip -d > /tmp/apply_project_lifecycle_history.py + python -m py_compile /tmp/apply_project_lifecycle_history.py + + - name: Set up Python + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Set up locked dependency manager + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + with: + version: "0.11.28" + enable-cache: false + + - name: Select pinned Rust toolchain + run: | + rustup toolchain install 1.97.1 --profile minimal + rustup default 1.97.1 + + - name: Install Python dependencies + run: uv sync --frozen --extra dev --extra backend + + - name: Set up Node + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # actions/setup-node@v5 + with: + node-version: "24" + + - name: Install frontend dependencies + working-directory: frontend + run: | + corepack enable + pnpm install --frozen-lockfile + + - name: RED - reproduce absent lifecycle contracts + shell: bash + run: | + python /tmp/apply_project_lifecycle_history.py red + set +e + uv run --frozen python -m pytest -q tests/test_project_history.py > /tmp/project-history-python-red.log 2>&1 + python_rc=$? + (cd frontend && pnpm exec vitest run src/components/ProjectHistoryTimeline.test.tsx) > /tmp/project-history-frontend-red.log 2>&1 + frontend_rc=$? + set -e + cat /tmp/project-history-python-red.log + cat /tmp/project-history-frontend-red.log + if [ "$python_rc" -eq 0 ] || [ "$frontend_rc" -eq 0 ]; then + echo "RED unexpectedly passed; refusing to publish an unproven change" >&2 + exit 1 + fi + grep -Eq "ModuleNotFoundError|No module named.*project_history" /tmp/project-history-python-red.log + grep -Eq "Failed to resolve import|Cannot find module|ProjectHistoryTimeline|ProjectHistory" /tmp/project-history-frontend-red.log + git reset --hard HEAD + git clean -fd + + - name: GREEN - materialize normalized authority, API, and Buyer surface + run: python /tmp/apply_project_lifecycle_history.py green + + - name: Refresh and verify universal lock + run: | + uv lock + uv lock --check + uv sync --frozen --extra dev --extra backend + + - name: Verify focused Python and real PostgreSQL contracts + run: | + uv run --frozen python -m pytest -q \ + tests/test_project_history.py \ + tests/test_project_history_backend.py \ + tests/test_project_history_postgres.py \ + tests/test_project_history_schema.py \ + tests/test_project_history_ontology.py + + - name: Require 100 percent changed-module statement and branch coverage + run: | + uv run coverage erase + uv run coverage run --branch -m pytest -q \ + tests/test_project_history.py \ + tests/test_project_history_backend.py \ + tests/test_project_history_postgres.py + uv run coverage report \ + --include='lineageweave/project_history.py,backend/app/project_history.py' \ + --fail-under=100 + + - name: Verify full Python repository + run: | + uv run --frozen python -m pytest -q + uv run --frozen python -m compileall -q lineageweave backend/app scripts + uv run --frozen python - <<'PY' + import ast + from pathlib import Path + from rdflib import Graph + + for path_name in ( + 'lineageweave/project_history.py', + 'backend/app/project_history.py', + ): + path = Path(path_name) + module = ast.parse(path.read_text(encoding='utf-8')) + missing = [] + for node in module.body: + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): + if not node.name.startswith('_') and ast.get_docstring(node) is None: + missing.append(node.name) + if missing: + raise SystemExit(f'{path_name} missing public docstrings: {missing}') + Graph().parse('docs/ontology/project-history-profile.ttl', format='turtle') + PY + + - name: Verify Buyer component, accessibility, build, and Storybook + working-directory: frontend + run: | + pnpm exec vitest run src/components/ProjectHistoryTimeline.test.tsx + pnpm run test + pnpm run lint + pnpm run build + pnpm run build-storybook + + - name: Commit only exact-head verified product changes + shell: bash + run: | + rm -rf frontend/dist frontend/storybook-static + rm -f .github/workflows/apply-project-lifecycle-history.yml + rm -f .github/repair/project_history_payload.b64.* + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -- \ + lineageweave/project_history.py \ + backend/app/project_history.py \ + backend/app/main.py \ + migrations/0050_project_history_lifecycle.sql \ + migrations/rollback/0050_project_history_lifecycle.sql \ + docker/postgres-init/Dockerfile \ + docker/postgres-init/migrate.sh \ + scripts/seed_project_history.py \ + Makefile \ + tests/test_project_history.py \ + tests/test_project_history_backend.py \ + tests/test_project_history_postgres.py \ + tests/test_project_history_schema.py \ + tests/test_project_history_ontology.py \ + frontend/src/api.ts \ + frontend/src/App.tsx \ + frontend/src/components/ProjectHistoryTimeline.tsx \ + frontend/src/components/ProjectHistoryTimeline.css \ + frontend/src/components/ProjectHistoryTimeline.test.tsx \ + frontend/src/components/ProjectHistoryTimeline.stories.tsx \ + pyproject.toml \ + uv.lock \ + frontend/package.json \ + CHANGELOG.md \ + CHANGELOG.d/2.20.0-project-lifecycle-history.md \ + ARCHITECTURE.md \ + docs/adr/README.md \ + docs/adr/0100-project-lifecycle-history.md \ + docs/doctoring/PROJECT_LIFECYCLE_HISTORY_REFERENCES.md \ + docs/ontology/project-history-profile.ttl \ + docs/project-lifecycle-history.md \ + docs/storybook-inventory.md + git add -u -- \ + .github/workflows/apply-project-lifecycle-history.yml \ + .github/repair/project_history_payload.b64.00 \ + .github/repair/project_history_payload.b64.01 \ + .github/repair/project_history_payload.b64.02 \ + .github/repair/project_history_payload.b64.03 + git diff --cached --check + git commit -m "feat(projects): add evidence-bound lifecycle history" + test -z "$(git status --porcelain)" || { + git status --short + exit 1 + } + + git fetch origin "${REPAIR_BRANCH}" + remote_head="$(git rev-parse "origin/${REPAIR_BRANCH}")" + if [ "$remote_head" != "$REPAIR_BASE_SHA" ]; then + echo "branch moved from ${REPAIR_BASE_SHA} to ${remote_head}; refusing unverified publication" >&2 + exit 1 + fi + git push origin "HEAD:${REPAIR_BRANCH}" diff --git a/AGENTS.md b/AGENTS.md index aa93be63e..25c482840 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -166,6 +166,19 @@ adjudication does -- never a raw LLM API. Demo TEPP seed goes through envelope is Failed (`tepp_not_available` / `tepp_result_not_persisted`), never a fabricated theta or a local psychometric substitute. +Buyer Board **Weekly VOC** is an ISO-8601 week list filter (ADR 0092). +Opening that filtered post focuses Event Lineage (ADR 0093). Do not +invent a week, a theta, or a cutoff body. +Opening a Calendar commitment uses the same focus path (ADR 0094). Do not +invent a week, a theta, a cutoff body, or a CalDAV event. +Opening a Customer master related post uses the same focus path (ADR 0095). +Do not invent a week, a theta, a cutoff body, a CalDAV event, or a customer. +Opening an Ask Agent cited post uses the same focus path (ADR 0096). Do not +invent a cited post. +A linked Event Lineage node opened from that focused popup keeps the +originating flags (ADR 0097). Do not invent a cited post. + + ## Tests ```bash diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index cac728a18..5757a935c 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -280,7 +280,13 @@ Keycloak (`src/main.tsx`'s `AuthProvider`) -- no mocked auth, no static HTML. `src/api.ts` calls the FastAPI backend directly with the token Keycloak issued; `src/App.tsx` renders a git-branch SVG of `GET /api/lineage` (click a node to open that post; `post_admin` can -rebuild), the post list, and a full detail popup: Korean +rebuild), the post list with a named Weekly VOC ISO-8601 week filter +(ADR 0092; opening that filtered post focuses Event Lineage, ADR 0093), +Calendar commitments use the same Event Lineage focus path (ADR 0094), +Customer master related posts use the same Event Lineage focus path +(ADR 0095). Ask Agent cited posts use the same Event Lineage focus path +(ADR 0096). A linked Event Lineage node opened from a focused popup keeps +those flags (ADR 0097), and the full detail popup includes Korean summary/key-events/R&R, VOC evidence excerpts, an Event Lineage panel (direct vs. indirect links; a link opens that post), the Keyman affiliate tree (resolved ancestors plus unresolved org roots), Keyman + diff --git a/CHANGELOG.d/2.12.0-weekly-voc-iso-week.md b/CHANGELOG.d/2.12.0-weekly-voc-iso-week.md new file mode 100644 index 000000000..5ce801c7c --- /dev/null +++ b/CHANGELOG.d/2.12.0-weekly-voc-iso-week.md @@ -0,0 +1,5 @@ +# 2.12.0 Weekly VOC ISO-week list filter + +Weekly VOC on Board keeps Voice of Customer posts for the latest ISO-8601 +week (UTC Thursday rule). Other VOC types and older weeks drop out. The +Board names Event Lineage as the next read. No TEPP theta is invented. diff --git a/CHANGELOG.d/2.13.0-weekly-voc-open-event-lineage.md b/CHANGELOG.d/2.13.0-weekly-voc-open-event-lineage.md new file mode 100644 index 000000000..7709c44cd --- /dev/null +++ b/CHANGELOG.d/2.13.0-weekly-voc-open-event-lineage.md @@ -0,0 +1,6 @@ +# 2.13.0 Opening Weekly VOC focuses Event Lineage + +Open a Voice of Customer post from an active Weekly VOC filter and the +popup Event Lineage heading takes focus. The popup names that post as +current and to read Keyman and evaluation next. Home-list opens do not. +No TEPP theta is invented. diff --git a/CHANGELOG.d/2.14.0-calendar-open-event-lineage.md b/CHANGELOG.d/2.14.0-calendar-open-event-lineage.md new file mode 100644 index 000000000..c09605d5e --- /dev/null +++ b/CHANGELOG.d/2.14.0-calendar-open-event-lineage.md @@ -0,0 +1,5 @@ +# 2.14.0 Opening a Calendar commitment focuses Event Lineage + +Calendar names authorized commitments as current and to open one to read +Event Lineage. That open focuses the popup Event Lineage heading. Home-list +opens do not. No TEPP theta is invented. diff --git a/CHANGELOG.d/2.15.0-customer-master-open-event-lineage.md b/CHANGELOG.d/2.15.0-customer-master-open-event-lineage.md new file mode 100644 index 000000000..7b67dcfe9 --- /dev/null +++ b/CHANGELOG.d/2.15.0-customer-master-open-event-lineage.md @@ -0,0 +1,5 @@ +# 2.15.0 Opening a Customer master related post focuses Event Lineage + +Customer master names authorized customer entities as current and to open a +related post to read Event Lineage. That open focuses the popup Event Lineage +heading. Home-list opens do not. No TEPP theta is invented. diff --git a/CHANGELOG.d/2.16.0-ask-agent-open-event-lineage.md b/CHANGELOG.d/2.16.0-ask-agent-open-event-lineage.md new file mode 100644 index 000000000..d60ab9945 --- /dev/null +++ b/CHANGELOG.d/2.16.0-ask-agent-open-event-lineage.md @@ -0,0 +1,5 @@ +# 2.16.0 Opening an Ask Agent cited post focuses Event Lineage + +Ask Agent names authorized cited posts as current after an answer and to +open one to read Event Lineage. That open focuses the popup Event Lineage +heading. Home-list opens do not. No TEPP theta is invented. diff --git a/CHANGELOG.d/2.17.0-event-lineage-node-keeps-gnb-focus.md b/CHANGELOG.d/2.17.0-event-lineage-node-keeps-gnb-focus.md new file mode 100644 index 000000000..313ebc036 --- /dev/null +++ b/CHANGELOG.d/2.17.0-event-lineage-node-keeps-gnb-focus.md @@ -0,0 +1,5 @@ +# 2.17.0 A linked Event Lineage node keeps GNB focus + +Opening a linked Event Lineage DAG node from a GNB-focused popup keeps +Event Lineage focused and names Keyman and evaluation next. A home-list +DAG walk does not. No TEPP theta is invented. diff --git a/CHANGELOG.md b/CHANGELOG.md index f5719013a..c187505bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,65 @@ All notable changes to this project are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.17.0] - 2026-08-19 + +### Added + +- Opening a linked Event Lineage DAG node from a GNB-focused popup now + keeps Event Lineage focused and names Keyman and evaluation as the next + read. A home-list DAG walk does not add that focus or copy. No TEPP + theta is invented. No cited post, customer, week, or cutoff body is + invented (ADR 0097 / ADR 0096 / ADR 0016). + +## [2.16.0] - 2026-08-19 + +### Added + +- Opening an Ask Agent cited post now focuses Event Lineage and names Keyman + and evaluation as the next read. After an authorized answer, Ask Agent + names cited posts as current before that open. Home-list opens do not add + that focus or copy. No TEPP theta is invented. No cited post is invented +(ADR 0096 / ADR 0039 / ADR 0016). + +## [2.15.0] - 2026-08-19 + +### Added + +- Opening a Customer master related post now focuses Event Lineage and names + Keyman and evaluation as the next read. Customer master names authorized + customer entities as current before that open. Home-list opens do not add + that focus or copy. No TEPP theta is invented. No customer is invented +(ADR 0095 / ADR 0037 / ADR 0016). + +## [2.14.0] - 2026-08-19 + +### Added + +- Opening a Calendar commitment now focuses Event Lineage and names Keyman + and evaluation as the next read. Calendar names authorized commitments + as current before that open. Home-list opens do not add that focus or + copy. No TEPP theta is invented. No cutoff body is invented + (ADR 0094 / ADR 0016). + +## [2.13.0] - 2026-08-19 + +### Added + +- Opening a Voice of Customer post from an active Weekly VOC filter now + focuses Event Lineage and names Keyman and evaluation as the next read. + Home-list opens do not add that focus or copy. No TEPP theta is + invented. No cutoff body is invented (ADR 0093 / ADR 0016). + +## [2.12.0] - 2026-08-19 + +### Added + +- Board now names Weekly VOC as an ISO-8601 week list filter. The control + keeps Voice of Customer posts for the latest week present in the loaded + list (UTC Thursday rule) and tells the buyer to open a post to read + Event Lineage. Reset filters returns every VOC type and every week. + No TEPP theta is invented (ADR 0092). + ## [2.10.0] - 2026-08-18 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index 1bcf50763..b989aa8b8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -70,3 +70,38 @@ cited source. After that next action, the popup lands the first cited evidence. Changing the week first still focuses the report period field. Mean θ stays on the period-report panel. + +## Weekly VOC (v2.12.0 / v2.13.0) + +On Board, click **Weekly VOC**. Voice of Customer posts for the latest +ISO-8601 week stay; other VOC types and older weeks drop out. The Board +names Event Lineage as the next read (ADR 0092). Open a remaining post: +Event Lineage takes focus and names Keyman and evaluation next +(ADR 0093). A home-list open does not. Do not invent a theta. + +## Calendar open (v2.14.0) + +Open Calendar. Authorized commitments are current. Open a commitment: +Event Lineage takes focus and names Keyman and evaluation next +(ADR 0094). A home-list open does not. Do not invent a theta or a +CalDAV event. + +## Customer master open (v2.15.0) + +Open Customer master. Authorized customer entities are current. Open a +related post: Event Lineage takes focus and names Keyman and evaluation +next (ADR 0095). A home-list open does not. Do not invent a theta or a +customer. + +## Ask Agent open (v2.16.0) + +Open Ask Agent. After an authorized answer, cited posts are current. Open +a cited post: Event Lineage takes focus and names Keyman and evaluation +next (ADR 0096). A home-list open does not. Do not invent a theta or a +cited post. + +## Event Lineage DAG walk (v2.17.0) + +From a GNB-focused popup, open a linked Event Lineage node: Event Lineage +stays focused and names the new post as current (ADR 0097). A home-list +DAG walk does not. Do not invent a theta. diff --git a/docs/adr/0092-weekly-voc-iso-week-filter.md b/docs/adr/0092-weekly-voc-iso-week-filter.md new file mode 100644 index 000000000..d2cb32e82 --- /dev/null +++ b/docs/adr/0092-weekly-voc-iso-week-filter.md @@ -0,0 +1,35 @@ +# ADR 0092: Weekly VOC is an ISO-8601 week list filter + +- Status: Accepted +- Date: 2026-08-19 + +## Context + +Board already exposes checkbox VOC-type filters (ADR 0060). Buyers still need +one named control that shows this week's Voice of Customer posts without +inventing a measurement or collapsing other VOC types into a guessed default. +PR #259 stacked a `` remains available beside the named + control. Reset filters returns both the checkboxes and the week to All. +- The Board names the next action: Voice of Customer posts for that week + are current; open a post to read Event Lineage. +- No TEPP theta is invented. No cutoff body is invented (ADR 0016). + +## Consequences + +- Weekly VOC composes with the existing checkbox VOC vocabulary instead of + replacing it. +- Posts whose `created_at` cannot be parsed contribute no week and cannot + be selected by this filter. +- Home-list and Customer-master opens are unchanged until ADR 0093. diff --git a/docs/adr/0093-weekly-voc-open-focuses-event-lineage.md b/docs/adr/0093-weekly-voc-open-focuses-event-lineage.md new file mode 100644 index 000000000..86bc73b0a --- /dev/null +++ b/docs/adr/0093-weekly-voc-open-focuses-event-lineage.md @@ -0,0 +1,33 @@ +# ADR 0093: Opening a Weekly VOC post focuses Event Lineage + +- Status: Accepted +- Date: 2026-08-19 + +## Context + +ADR 0092 names Weekly VOC as an ISO-week list filter and tells the buyer to +open a post to read Event Lineage. Report-member opens already focus the +popup Event Lineage heading. A home-list open must not steal that focus or +add that next-action copy. + +## Decision + +Opening a Board post while Weekly VOC is active (`voc` only and a concrete +ISO week) is a `fromWeeklyVoc` open. That open reuses the existing Event +Lineage focus path used by report-member opens: + +- The popup Event Lineage heading takes focus. +- The popup names the opened post as current in Event Lineage and tells + the buyer to read Keyman and evaluation next. + +A Board open from the unfiltered home list, a reset filter list, or any +path that did not set `fromWeeklyVoc` does not focus Event Lineage and +does not add that copy. Closing the popup clears the Weekly VOC open flag. + +No TEPP theta is invented. No cutoff body is invented (ADR 0016). + +## Consequences + +- Weekly VOC and report-member opens share one focus contract. +- Changing VOC checkboxes or the ISO week so Weekly VOC is no longer + active makes the next Board open a home-list open. diff --git a/docs/adr/0094-calendar-open-focuses-event-lineage.md b/docs/adr/0094-calendar-open-focuses-event-lineage.md new file mode 100644 index 000000000..0b7b1a8b9 --- /dev/null +++ b/docs/adr/0094-calendar-open-focuses-event-lineage.md @@ -0,0 +1,33 @@ +# ADR 0094: Opening a Calendar commitment focuses Event Lineage + +- Status: Accepted +- Date: 2026-08-19 + +## Context + +Board Weekly VOC opens already focus Event Lineage (ADR 0093). Calendar is +the other buyer destination that opens a source post from an authorized +commitment. That open was a home-list open: the popup body appeared and +Event Lineage did not take focus. + +## Decision + +Opening a commitment on Calendar is a `fromCalendar` open. That open reuses +the Event Lineage focus path used by report-member and Weekly VOC opens: + +- Calendar names the next action: authorized commitments are current; open + a commitment to read Event Lineage. +- The popup Event Lineage heading takes focus. +- The popup names the opened post as current in Event Lineage and tells + the buyer to read Keyman and evaluation next. + +A Board home-list open does not focus Event Lineage and does not add that +copy. A `?post=` deep link is still a home-list open. + +No TEPP theta is invented. No cutoff body is invented (ADR 0016). Calendar +does not invent a CalDAV event (ADR 0038). + +## Consequences + +- Calendar, Weekly VOC, and report-member opens share one focus contract. +- Closing the popup clears the Calendar open flag. diff --git a/docs/adr/0095-customer-master-open-focuses-event-lineage.md b/docs/adr/0095-customer-master-open-focuses-event-lineage.md new file mode 100644 index 000000000..ab9e55f9f --- /dev/null +++ b/docs/adr/0095-customer-master-open-focuses-event-lineage.md @@ -0,0 +1,35 @@ +# ADR 0095: Opening a Customer master related post focuses Event Lineage + +- Status: Accepted +- Date: 2026-08-19 + +## Context + +Board Weekly VOC and Calendar commitment opens already focus Event Lineage +(ADR 0093 / ADR 0094). Customer master is the remaining buyer GNB destination +that opens an authorized related post. That open was a home-list open: the +popup body appeared and Event Lineage did not take focus. + +## Decision + +Opening a related post on Customer master is a `fromCustomerMaster` open. +That open reuses the Event Lineage focus path used by report-member, +Weekly VOC, and Calendar opens: + +- Customer master names the next action: authorized customer entities are + current; open a related post to read Event Lineage. +- The popup Event Lineage heading takes focus. +- The popup names the opened post as current in Event Lineage and tells + the buyer to read Keyman and evaluation next. + +A Board home-list open does not focus Event Lineage and does not add that +copy. A `?post=` deep link is still a home-list open. + +No TEPP theta is invented. No cutoff body is invented (ADR 0016). Customer +master does not invent a customer or a parent (ADR 0037 / ADR 0010). + +## Consequences + +- Customer master, Calendar, Weekly VOC, and report-member opens share one + focus contract. +- Closing the popup clears the Customer master open flag. diff --git a/docs/adr/0096-ask-agent-open-focuses-event-lineage.md b/docs/adr/0096-ask-agent-open-focuses-event-lineage.md new file mode 100644 index 000000000..5a323fc25 --- /dev/null +++ b/docs/adr/0096-ask-agent-open-focuses-event-lineage.md @@ -0,0 +1,36 @@ +# ADR 0096: Opening an Ask Agent cited post focuses Event Lineage + +- Status: Accepted +- Date: 2026-08-19 + +## Context + +Board Weekly VOC, Calendar, and Customer master opens already focus Event +Lineage (ADR 0093 / ADR 0094 / ADR 0095). Ask Agent is the remaining buyer +GNB destination that opens an authorized cited post. That open was a +home-list open: the popup body appeared and Event Lineage did not take +focus. + +## Decision + +Opening a cited post on Ask Agent is a `fromAskAgent` open. That open +reuses the Event Lineage focus path used by report-member, Weekly VOC, +Calendar, and Customer master opens: + +- After an authorized answer, Ask Agent names the next action: cited posts + are current; open a cited post to read Event Lineage. +- The popup Event Lineage heading takes focus. +- The popup names the opened post as current in Event Lineage and tells + the buyer to read Keyman and evaluation next. + +A Board home-list open does not focus Event Lineage and does not add that +copy. A `?post=` deep link is still a home-list open. + +No TEPP theta is invented. No cutoff body is invented (ADR 0016). Ask Agent +does not invent a cited post (ADR 0039). + +## Consequences + +- Ask Agent, Customer master, Calendar, Weekly VOC, and report-member + opens share one focus contract. +- Closing the popup clears the Ask Agent open flag. diff --git a/docs/adr/0097-event-lineage-node-keeps-gnb-focus.md b/docs/adr/0097-event-lineage-node-keeps-gnb-focus.md new file mode 100644 index 000000000..a31084961 --- /dev/null +++ b/docs/adr/0097-event-lineage-node-keeps-gnb-focus.md @@ -0,0 +1,35 @@ +# ADR 0097: A linked Event Lineage node keeps GNB focus + +- Status: Accepted +- Date: 2026-08-19 + +## Context + +Opening a Board Weekly VOC post, Calendar commitment, Customer master +related post, Ask Agent cited post, or report member already focuses +Event Lineage (ADR 0093 / ADR 0094 / ADR 0095 / ADR 0096). Clicking a +linked Event Lineage DAG node then called `selectPost` without those +flags. The popup switched records and dropped the GNB focus contract: +Keyman and evaluation were no longer named next. + +## Decision + +A popup-internal Event Lineage DAG open reuses the originating GNB +flags (`fromReportMember`, `fromWeeklyVoc`, `fromCalendar`, +`fromCustomerMaster`, `fromAskAgent`): + +- The popup Event Lineage heading stays focused. +- The popup names the newly opened post as current in Event Lineage + and tells the buyer to read Keyman and evaluation next. + +A Board home-list DAG walk does not focus Event Lineage and does not +add that copy. Closing the popup still clears the originating flags. + +No TEPP theta is invented. No cutoff body is invented (ADR 0016). No +cited post, customer, week, or CalDAV event is invented. + +## Consequences + +- GNB destinations share one Event Lineage focus contract across the + first open and a linked DAG walk from that popup. +- A home-list DAG walk stays a home-list open. diff --git a/frontend/package.json b/frontend/package.json index e2e996bbe..7a697d0c9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "2.12.6", + "version": "2.17.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/App.css b/frontend/src/App.css index b37ed6661..6b63bcb7f 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -145,6 +145,15 @@ min-height: 0; } +.board-weekly-voc[aria-pressed="true"] { + border-color: var(--text); + font-weight: 700; +} + +.board-next-action { + margin: 0 0 1rem; +} + .board-controls input, .board-controls select { min-height: var(--size-control-min); diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index 16abc86b0..91cedc656 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -83,11 +83,24 @@ describe("App, authenticated", () => { pendingTeppRun?: boolean; pluralAffiliations?: boolean; deferMe?: boolean; + deferPostOneSummary?: boolean; meFailed?: boolean; postBody?: string; manyCustomerHints?: number; customerEntityHierarchy?: boolean; - }): ReturnType & { releaseMe: () => void } { + boardPosts?: { + post_id: string; + post_title: string; + voc_type_code: string; + voc_type_label?: string; + visibility_code?: string; + visibility_label?: string; + created_at: string; + }[]; + }): ReturnType & { + releaseMe: () => void; + releasePostOneSummary: () => void; + } { const statusLabel: Record = { open: "Open", in_progress: "In progress", @@ -118,6 +131,12 @@ describe("App, authenticated", () => { releaseMe = resolve; }) : Promise.resolve(); + let releasePostOneSummary = () => {}; + const postOneSummaryReady = options?.deferPostOneSummary + ? new Promise((resolve) => { + releasePostOneSummary = resolve; + }) + : Promise.resolve(); const fetchMock = vi.fn((input: RequestInfo | URL, init?: RequestInit) => { const url = String(input); @@ -195,6 +214,9 @@ describe("App, authenticated", () => { if (url.endsWith("/api/posts/post-1/activity") && method === "GET") { return Promise.resolve(jsonResponse({ events })); } + if (url.endsWith("/api/posts/post-2/activity") && method === "GET") { + return Promise.resolve(jsonResponse({ events: [] })); + } if (url.endsWith("/api/posts/post-1/derive-commitment") && method === "POST") { if (options?.chatUnavailable) { return Promise.resolve( @@ -1043,6 +1065,7 @@ describe("App, authenticated", () => { visibility_label: "Public", created_at: "2026-01-01T00:00:00Z", }, + ...(options?.boardPosts ?? []), ], total_count: 1, limit: 50, @@ -1133,7 +1156,7 @@ describe("App, authenticated", () => { ); } if (url.endsWith("/api/posts/post-1/summary")) { - return Promise.resolve( + return postOneSummaryReady.then(() => jsonResponse({ post_id: "post-1", korean_summary: "이것은 요약입니다.", @@ -1181,6 +1204,17 @@ describe("App, authenticated", () => { }), ); } + if (url.endsWith("/api/posts/post-2/summary")) { + return Promise.resolve( + jsonResponse({ + post_id: "post-2", + korean_summary: "연결된 글입니다.", + key_events: [], + roles_and_responsibilities: [], + project_mentions: [], + }), + ); + } if (url.endsWith("/api/posts/post-1/keymen")) { return Promise.resolve( jsonResponse({ @@ -1345,6 +1379,26 @@ describe("App, authenticated", () => { }), ); } + if (url.endsWith("/api/corporate-entities/corp-demo/related")) { + return Promise.resolve( + jsonResponse({ + corporate_entity_id: "corp-demo", + entity_name: "Demo Corp", + related: [ + { + node_id: "post-1", + node_type_code: "node_post", + ontology_iri: "https://contextualwisdomlab.github.io/lineageweave/ontology#Post", + ontology_label: "Post", + label: "Public post", + relevance: 0.8, + post_body_excerpt: "The full body text.", + post_body_truncated: false, + }, + ], + }), + ); + } if (url.endsWith("/api/posts/post-1/affiliate-tree")) { return Promise.resolve( jsonResponse({ @@ -1462,6 +1516,15 @@ describe("App, authenticated", () => { }), ); } + if (url.endsWith("/api/posts/post-2/lineage")) { + return Promise.resolve( + jsonResponse({ + post_id: "post-2", + direct: [{ post_id: "post-1", post_title: "Public post" }], + indirect: [], + }), + ); + } if (url.endsWith("/api/posts/post-1/chat") && method === "GET") { return Promise.resolve( jsonResponse({ @@ -1623,7 +1686,7 @@ describe("App, authenticated", () => { return Promise.reject(new Error(`unexpected fetch: ${method} ${url}`)); }); vi.stubGlobal("fetch", fetchMock); - return Object.assign(fetchMock, { releaseMe }); + return Object.assign(fetchMock, { releaseMe, releasePostOneSummary }); } it("renders safe Ask Agent evidence under each cited post", async () => { @@ -1762,6 +1825,249 @@ describe("App, authenticated", () => { expect(screen.queryByRole("button", { name: "Close" })).not.toBeInTheDocument(); }); + it("clicking Weekly VOC keeps the 2026-W01 Voice of Customer post and names Event Lineage as the next action", async () => { + stubBackend({ + boardPosts: [ + { + post_id: "post-vom-w01", + post_title: "Internal memo", + voc_type_code: "vom", + voc_type_label: "Voice of Market", + visibility_code: "internal", + visibility_label: "Internal", + created_at: "2026-01-02T00:00:00Z", + }, + { + post_id: "post-voc-w52", + post_title: "Older Voice of Customer", + voc_type_code: "voc", + voc_type_label: "Voice of Customer", + visibility_code: "public", + visibility_label: "Public", + created_at: "2025-12-22T00:00:00Z", + }, + ], + }); + render(); + + const board = await screen.findByRole("region", { name: "Board" }); + expect(within(board).getByRole("button", { name: "View post: Internal memo" })).toBeInTheDocument(); + expect(within(board).getByRole("button", { name: "View post: Older Voice of Customer" })).toBeInTheDocument(); + + const weeklyVoc = within(board).getByRole("button", { name: "Weekly VOC" }); + expect(weeklyVoc).toHaveAttribute("aria-pressed", "false"); + await userEvent.click(weeklyVoc); + + expect(weeklyVoc).toHaveAttribute("aria-pressed", "true"); + expect(within(board).getByLabelText("Filter by ISO week")).toHaveValue("2026-W01"); + expect(within(board).getByRole("button", { name: "View post: Public post" })).toBeInTheDocument(); + expect(within(board).queryByRole("button", { name: "View post: Internal memo" })).not.toBeInTheDocument(); + expect( + within(board).queryByRole("button", { name: "View post: Older Voice of Customer" }), + ).not.toBeInTheDocument(); + expect(within(board).getByLabelText("Next action")).toHaveTextContent( + "Voice of Customer posts for 2026-W01 are current. Open a post to read Event Lineage.", + ); + + await userEvent.click(within(board).getByRole("button", { name: "Reset filters" })); + expect(weeklyVoc).toHaveAttribute("aria-pressed", "false"); + expect(within(board).getByRole("button", { name: "View post: Internal memo" })).toBeInTheDocument(); + expect(within(board).getByRole("button", { name: "View post: Older Voice of Customer" })).toBeInTheDocument(); + }); + + it("opening a Weekly VOC post focuses Event Lineage; a home list open does not", async () => { + stubBackend(); + render(); + + const board = await screen.findByRole("region", { name: "Board" }); + await userEvent.click(within(board).getByRole("button", { name: "Weekly VOC" })); + await userEvent.click(within(board).getByRole("button", { name: "View post: Public post" })); + + await waitFor(() => expect(screen.getByText("The full body text.")).toBeInTheDocument()); + expect(document.getElementById("post-event-lineage")).toHaveFocus(); + expect(screen.getByRole("status", { name: "Event Lineage next action" })).toHaveTextContent( + "Public post is current in Event Lineage. Read Keyman and evaluation next.", + ); + + await userEvent.click(screen.getByRole("button", { name: "Close" })); + await userEvent.click(within(board).getByRole("button", { name: "Reset filters" })); + await userEvent.click(within(board).getByRole("button", { name: "View post: Public post" })); + await waitFor(() => expect(screen.getByText("The full body text.")).toBeInTheDocument()); + expect(document.getElementById("post-event-lineage")).not.toHaveFocus(); + expect(screen.queryByRole("status", { name: "Event Lineage next action" })).not.toBeInTheDocument(); + }); + + it("does not scroll Calendar users away from Event Lineage when related evidence lands", async () => { + const scrolledIds: string[] = []; + const originalScrollIntoView = HTMLElement.prototype.scrollIntoView; + HTMLElement.prototype.scrollIntoView = function () { + scrolledIds.push(this.id); + }; + try { + stubBackend(); + render(); + + await userEvent.click(await screen.findByRole("button", { name: "Calendar" })); + const calendar = await screen.findByRole("region", { name: "Calendar" }); + await userEvent.click( + within(calendar).getByRole("button", { name: "Open commitment for: Public post" }), + ); + + await screen.findByRole("status", { name: "Ask next action" }); + expect(document.getElementById("post-event-lineage")).toHaveFocus(); + expect(scrolledIds).not.toContain("post-ask"); + } finally { + HTMLElement.prototype.scrollIntoView = originalScrollIntoView; + } + }); + + it("opening a Calendar commitment focuses Event Lineage; a home list open does not", async () => { + stubBackend(); + render(); + + await userEvent.click(await screen.findByRole("button", { name: "Calendar" })); + const calendar = await screen.findByRole("region", { name: "Calendar" }); + expect(within(calendar).getByLabelText("Next action")).toHaveTextContent( + "Authorized commitments are current. Open a commitment to read Event Lineage.", + ); + await userEvent.click( + within(calendar).getByRole("button", { name: "Open commitment for: Public post" }), + ); + + await waitFor(() => expect(screen.getByText("The full body text.")).toBeInTheDocument()); + expect(document.getElementById("post-event-lineage")).toHaveFocus(); + expect(screen.getByRole("status", { name: "Event Lineage next action" })).toHaveTextContent( + "Public post is current in Event Lineage. Read Keyman and evaluation next.", + ); + + await userEvent.click(screen.getByRole("button", { name: "Close" })); + const board = screen.getByRole("region", { name: "Board" }); + await userEvent.click(within(board).getByRole("button", { name: "View post: Public post" })); + await waitFor(() => expect(screen.getByText("The full body text.")).toBeInTheDocument()); + expect(document.getElementById("post-event-lineage")).not.toHaveFocus(); + expect(screen.queryByRole("status", { name: "Event Lineage next action" })).not.toBeInTheDocument(); + }); + + it("opening a Customer master related post focuses Event Lineage; a home list open does not", async () => { + stubBackend(); + render(); + + await userEvent.click(await screen.findByRole("button", { name: "Customer master" })); + const customers = await screen.findByRole("region", { name: "Customer master" }); + expect(within(customers).getByLabelText("Next action")).toHaveTextContent( + "Authorized customer entities are current. Open a related post to read Event Lineage.", + ); + await userEvent.click(within(customers).getByRole("button", { name: /Demo Corp/ })); + await userEvent.click( + await within(customers).findByRole("button", { name: "Open related post: Public post" }), + ); + + await waitFor(() => expect(screen.getByText("The full body text.")).toBeInTheDocument()); + expect(document.getElementById("post-event-lineage")).toHaveFocus(); + expect(screen.getByRole("status", { name: "Event Lineage next action" })).toHaveTextContent( + "Public post is current in Event Lineage. Read Keyman and evaluation next.", + ); + + await userEvent.click(screen.getByRole("button", { name: "Close" })); + const boardAfterCustomer = screen.getByRole("region", { name: "Board" }); + await userEvent.click( + within(boardAfterCustomer).getByRole("button", { name: "View post: Public post" }), + ); + await waitFor(() => expect(screen.getByText("The full body text.")).toBeInTheDocument()); + expect(document.getElementById("post-event-lineage")).not.toHaveFocus(); + expect(screen.queryByRole("status", { name: "Event Lineage next action" })).not.toBeInTheDocument(); + }); + + it("opening an Ask Agent cited post focuses Event Lineage; a home list open does not", async () => { + stubBackend(); + render(); + + await userEvent.click(await screen.findByRole("button", { name: "Ask Agent" })); + const ask = await screen.findByRole("region", { name: "Ask Agent" }); + await userEvent.type(within(ask).getByRole("textbox", { name: "Ask a question" }), "Which project?"); + await userEvent.click(within(ask).getByRole("button", { name: "Ask" })); + expect(await within(ask).findByLabelText("Next action")).toHaveTextContent( + "Authorized cited posts are current. Open a cited post to read Event Lineage.", + ); + await userEvent.click(within(ask).getByRole("button", { name: "Open cited post: Linked post" })); + + await waitFor(() => + expect(screen.getByText("The evidence panel should show exactly this text.")).toBeInTheDocument(), + ); + expect(document.getElementById("post-event-lineage")).toHaveFocus(); + expect(screen.getByRole("status", { name: "Event Lineage next action" })).toHaveTextContent( + "Linked post is current in Event Lineage. Read Keyman and evaluation next.", + ); + + await userEvent.click(screen.getByRole("button", { name: "Close" })); + const boardAfterAsk = screen.getByRole("region", { name: "Board" }); + await userEvent.click(within(boardAfterAsk).getByRole("button", { name: "View post: Public post" })); + await waitFor(() => expect(screen.getByText("The full body text.")).toBeInTheDocument()); + expect(document.getElementById("post-event-lineage")).not.toHaveFocus(); + expect(screen.queryByRole("status", { name: "Event Lineage next action" })).not.toBeInTheDocument(); + }); + + it("ignores a stale summary after Event Lineage navigation changes the selected post", async () => { + const fetchMock = stubBackend({ deferPostOneSummary: true }); + render(); + + const board = await screen.findByRole("region", { name: "Board" }); + await userEvent.click( + within(board).getByRole("button", { name: "View post: Public post" }), + ); + await waitFor(() => expect(screen.getByText("The full body text.")).toBeInTheDocument()); + + const linkedPosts = screen.getAllByLabelText("Open post: Linked post"); + await userEvent.click(linkedPosts[linkedPosts.length - 1]); + await waitFor(() => + expect(screen.getByText("The evidence panel should show exactly this text.")).toBeInTheDocument(), + ); + expect(await screen.findByText("연결된 글입니다.")).toBeInTheDocument(); + + fetchMock.releasePostOneSummary(); + + await waitFor(() => { + expect(screen.getByText("연결된 글입니다.")).toBeInTheDocument(); + expect(screen.queryByText("이것은 요약입니다.")).not.toBeInTheDocument(); + }); + }); + + it("opening a linked Event Lineage node from Ask Agent keeps GNB focus; a home-list DAG walk does not", async () => { + stubBackend(); + render(); + + await userEvent.click(await screen.findByRole("button", { name: "Ask Agent" })); + const ask = await screen.findByRole("region", { name: "Ask Agent" }); + await userEvent.type(within(ask).getByRole("textbox", { name: "Ask a question" }), "Which project?"); + await userEvent.click(within(ask).getByRole("button", { name: "Ask" })); + await userEvent.click(within(ask).getByRole("button", { name: "Open cited post: Linked post" })); + + await waitFor(() => + expect(screen.getByText("The evidence panel should show exactly this text.")).toBeInTheDocument(), + ); + expect(screen.getByRole("status", { name: "Event Lineage next action" })).toHaveTextContent( + "Linked post is current in Event Lineage. Read Keyman and evaluation next.", + ); + + await userEvent.click(screen.getByLabelText("Open post: Public post")); + await waitFor(() => expect(screen.getByText("The full body text.")).toBeInTheDocument()); + expect(document.getElementById("post-event-lineage")).toHaveFocus(); + expect(screen.getByRole("status", { name: "Event Lineage next action" })).toHaveTextContent( + "Public post is current in Event Lineage. Read Keyman and evaluation next.", + ); + + await userEvent.click(screen.getByRole("button", { name: "Close" })); + const boardAfterAsk = screen.getByRole("region", { name: "Board" }); + await userEvent.click(within(boardAfterAsk).getByRole("button", { name: "View post: Public post" })); + await waitFor(() => expect(screen.getByText("The full body text.")).toBeInTheDocument()); + await userEvent.click(screen.getByLabelText("Open post: Linked post")); + await waitFor(() => + expect(screen.getByText("The evidence panel should show exactly this text.")).toBeInTheDocument(), + ); + expect(document.getElementById("post-event-lineage")).not.toHaveFocus(); + expect(screen.queryByRole("status", { name: "Event Lineage next action" })).not.toBeInTheDocument(); + }); + it("renders the A-100 fork as a git-style DAG, not a flat edge list", async () => { stubBackend(); render(); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index af10f9e0b..c14cdfd3d 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -98,6 +98,11 @@ import { tf, useLocale, } from "./i18n"; +import { isoWeekFromCreatedAt, latestIsoWeek } from "./isoWeek"; +import { + analysisRunTargetClock, + type AnalysisRunNavigationContext, +} from "./analysisRunNavigation"; import "./App.css"; function orchestratorUnavailableMessage(err: unknown, action: string): string { @@ -825,6 +830,7 @@ function KeymanPanel({ focusTeam, landFirstKeyman, landFirstRelated, + landOnAsk, afterList, }: { postId: string; @@ -839,6 +845,7 @@ function KeymanPanel({ focusTeam?: { teamId: string; teamName: string } | null; landFirstKeyman?: boolean; landFirstRelated?: boolean; + landOnAsk?: boolean; afterList?: ReactNode; }) { const [related, setRelated] = useState(null); @@ -946,13 +953,13 @@ function KeymanPanel({ }, [accessToken, landFirstRelated, related]); useEffect(() => { - if (!landFirstRelated || !landedRelatedName || landedRelated === null) { + if (!landFirstRelated || !landOnAsk || !landedRelatedName || landedRelated === null) { return; } const heading = document.getElementById("post-ask"); heading?.focus(); heading?.scrollIntoView?.({ block: "nearest" }); - }, [landFirstRelated, landedRelatedName, landedRelated]); + }, [landFirstRelated, landedRelatedName, landedRelated, landOnAsk]); useEffect(() => { if (!focusPerson) return; @@ -1646,6 +1653,7 @@ function PostDetailPopup({ liveBodyWarning, knowledgeCutoff, focusEventLineage, + focusAskOnLand, onClose, onSelectPost, onSearch, @@ -1657,6 +1665,7 @@ function PostDetailPopup({ liveBodyWarning?: string | null; knowledgeCutoff?: string | null; focusEventLineage?: boolean; + focusAskOnLand?: boolean; onClose: () => void; onSelectPost?: (postId: string) => void; onSearch?: (query: string) => void; @@ -1682,31 +1691,58 @@ function PostDetailPopup({ const [focusEntity, setFocusEntity] = useState<{ entityId: string; entityName: string } | null>(null); const [focusTeam, setFocusTeam] = useState<{ teamId: string; teamName: string } | null>(null); + const detailRequestGeneration = useRef(0); + function reloadKeymen() { + const generation = detailRequestGeneration.current; fetchPostKeymen(accessToken, postId) .then((r) => { + if (detailRequestGeneration.current !== generation) return; setKeymen(r.keymen); setSourceAuthorContext(r.source_author_context ?? null); }) .catch(() => { + if (detailRequestGeneration.current !== generation) return; setKeymen([]); setSourceAuthorContext(null); }); fetchPostAffiliateTree(accessToken, postId) - .then((r) => setAffiliateTrees(r.trees)) - .catch(() => setAffiliateTrees([])); - fetchPostVocEvidence(accessToken, postId).then(setVocEvidence).catch(() => setVocEvidence(null)); + .then((r) => { + if (detailRequestGeneration.current === generation) setAffiliateTrees(r.trees); + }) + .catch(() => { + if (detailRequestGeneration.current === generation) setAffiliateTrees([]); + }); + fetchPostVocEvidence(accessToken, postId) + .then((value) => { + if (detailRequestGeneration.current === generation) setVocEvidence(value); + }) + .catch(() => { + if (detailRequestGeneration.current === generation) setVocEvidence(null); + }); reloadCounterparties(); } function reloadCounterparties() { + const generation = detailRequestGeneration.current; fetchPostCounterparties(accessToken, postId) - .then((r) => setCounterparties(r.counterparties)) - .catch(() => setCounterparties([])); + .then((r) => { + if (detailRequestGeneration.current === generation) { + setCounterparties(r.counterparties); + } + }) + .catch(() => { + if (detailRequestGeneration.current === generation) setCounterparties([]); + }); } useEffect(() => { + const generation = detailRequestGeneration.current + 1; + detailRequestGeneration.current = generation; + const isCurrent = () => detailRequestGeneration.current === generation; + setPost(null); + setImageContent([]); setStructureUnits([]); setBookmarked(null); setBookmarkSaving(false); @@ -1726,55 +1762,101 @@ function PostDetailPopup({ setFocusEntity(null); setFocusTeam(null); const asOf = liveBodyWarning && knowledgeCutoff ? knowledgeCutoff : undefined; - fetchPost(accessToken, postId, asOf).then(setPost).catch((err) => setError(String(err))); + fetchPost(accessToken, postId, asOf) + .then((value) => { + if (isCurrent()) setPost(value); + }) + .catch((err) => { + if (isCurrent()) setError(String(err)); + }); const reloadContent = () => fetchPostContent(accessToken, postId) .then((content) => { + if (!isCurrent()) return; setImageContent(content.images); setStructureUnits(content.units); }) .catch(() => { + if (!isCurrent()) return; setImageContent([]); setStructureUnits([]); }); - reloadContent(); + void reloadContent(); fetchPostBookmark(accessToken, postId) - .then((r) => setBookmarked(r.bookmarked)) + .then((r) => { + if (isCurrent()) setBookmarked(r.bookmarked); + }) .catch(() => { - setBookmarked(null); + if (isCurrent()) setBookmarked(null); }); fetchPostEvaluation(accessToken, postId) - .then((r) => setEvaluation(r.responses)) - .catch(() => setEvaluation([])); + .then((r) => { + if (isCurrent()) setEvaluation(r.responses); + }) + .catch(() => { + if (isCurrent()) setEvaluation([]); + }); fetchPostSummary(accessToken, postId) .then((value) => { + if (!isCurrent()) return; setSummary(value); - reloadContent(); + void reloadContent(); }) .catch((err) => { + if (!isCurrent()) return; setSummary(null); setSummaryError(summaryFetchError(err)); }); fetchPostFiveW1H(accessToken, postId) - .then(setFiveW1H) - .catch(() => setFiveW1H(null)); + .then((value) => { + if (isCurrent()) setFiveW1H(value); + }) + .catch(() => { + if (isCurrent()) setFiveW1H(null); + }); fetchPostKeymen(accessToken, postId) .then((r) => { + if (!isCurrent()) return; setKeymen(r.keymen); setSourceAuthorContext(r.source_author_context ?? null); }) .catch(() => { + if (!isCurrent()) return; setKeymen([]); setSourceAuthorContext(null); }); fetchPostCounterparties(accessToken, postId) - .then((r) => setCounterparties(r.counterparties)) - .catch(() => setCounterparties([])); - fetchPostLineage(accessToken, postId).then(setLineage).catch(() => setLineage(null)); + .then((r) => { + if (isCurrent()) setCounterparties(r.counterparties); + }) + .catch(() => { + if (isCurrent()) setCounterparties([]); + }); + fetchPostLineage(accessToken, postId) + .then((value) => { + if (isCurrent()) setLineage(value); + }) + .catch(() => { + if (isCurrent()) setLineage(null); + }); fetchPostAffiliateTree(accessToken, postId) - .then((r) => setAffiliateTrees(r.trees)) - .catch(() => setAffiliateTrees([])); - fetchPostVocEvidence(accessToken, postId).then(setVocEvidence).catch(() => setVocEvidence(null)); + .then((r) => { + if (isCurrent()) setAffiliateTrees(r.trees); + }) + .catch(() => { + if (isCurrent()) setAffiliateTrees([]); + }); + fetchPostVocEvidence(accessToken, postId) + .then((value) => { + if (isCurrent()) setVocEvidence(value); + }) + .catch(() => { + if (isCurrent()) setVocEvidence(null); + }); + + return () => { + if (isCurrent()) detailRequestGeneration.current = generation + 1; + }; }, [postId, accessToken, liveBodyWarning, knowledgeCutoff]); const permanentLink = (() => { @@ -2228,6 +2310,7 @@ function PostDetailPopup({ focusTeam={focusTeam} landFirstKeyman landFirstRelated + landOnAsk={focusAskOnLand} afterList={ <> item.post_id === postId); - return { - liveAfterCutoff: Boolean(post?.live_after_cutoff), + const analysisRunContext: AnalysisRunNavigationContext = { knowledgeCutoff: run.knowledge_cutoff, + visiblePosts: run.visible_posts ?? [], + }; + return { + ...analysisRunTargetClock(analysisRunContext, postId), + analysisRunContext, }; } @@ -3026,9 +3117,11 @@ function RankingsPanel({ function CalendarPanel({ accessToken, onSelectPost, + namedNextAction = false, }: { accessToken: string; - onSelectPost: (postId: string) => void; + onSelectPost: (postId: string, options?: SelectPostOptions) => void; + namedNextAction?: boolean; }) { const [calendar, setCalendar] = useState(null); const [error, setError] = useState(null); @@ -3048,8 +3141,13 @@ function CalendarPanel({ const caldavNextAction = calendar.calendar_sources?.caldav_next_action; return ( -
-

{t("Calendar")}

+
+

{t("Calendar")}

+ {namedNextAction && commitments.length > 0 ? ( +

+ {t("Authorized commitments are current. Open a commitment to read Event Lineage.")} +

+ ) : null}

{t("CalDAV events")}

{events.length === 0 ? ( @@ -3084,7 +3182,7 @@ function CalendarPanel({ +