From 25eb5ce278e3691fa3ed3d381763326bc15fdbb0 Mon Sep 17 00:00:00 2001 From: Sam Bender Date: Sun, 31 Aug 2025 01:59:54 -0700 Subject: [PATCH 1/3] Strip comments when detecting unused snapshots --- .github/workflows/test.yml | 4 ++ .github/workflows/update-snapshots.yml | 5 +- .../custom-deck-notation-to-study-list.png | Bin 34383 -> 0 bytes package.json | 4 +- scripts/check-unused-snapshots.js | 43 ++++++++++++++++++ 5 files changed, 54 insertions(+), 2 deletions(-) delete mode 100644 apps/react/tests/custom-deck-notation-to-study.spec.ts-snapshots/custom-deck-notation-to-study-list.png create mode 100644 scripts/check-unused-snapshots.js diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e05eaf17..15f9524e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -53,6 +53,10 @@ jobs: APP_URL: http://0.0.0.0:8008 BLOCK_REMOTE_FONTS: true + - name: Check for unused snapshots + if: steps.tests.outcome == 'success' + run: yarn test:snapshots:check + - name: Authenticate on GCS if: steps.tests.outcome == 'failure' uses: google-github-actions/auth@v2 diff --git a/.github/workflows/update-snapshots.yml b/.github/workflows/update-snapshots.yml index 97e7dd14..80269cb9 100644 --- a/.github/workflows/update-snapshots.yml +++ b/.github/workflows/update-snapshots.yml @@ -32,6 +32,9 @@ jobs: working-directory: apps/react run: yarn test:screenshots:update + - name: Prune unused snapshots + run: yarn test:snapshots:prune + - name: Commit and push updated snapshots env: # expose the PAT for the push @@ -39,7 +42,7 @@ jobs: run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - git add apps/react/tests/*-snapshots/* || true + git add -A apps/react/tests/*-snapshots || true git commit -m "chore: update Playwright screenshot snapshots" || echo "No changes to commit" BRANCH="${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" echo "Pushing updates to branch: $BRANCH" diff --git a/apps/react/tests/custom-deck-notation-to-study.spec.ts-snapshots/custom-deck-notation-to-study-list.png b/apps/react/tests/custom-deck-notation-to-study.spec.ts-snapshots/custom-deck-notation-to-study-list.png deleted file mode 100644 index ba907dba82d0ea93b3b99a036a656bbbb31ea23c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34383 zcmdRWRajMF*X=?D5d=|6L`6^#lu!{+Qa1w9A>B$zcc%hU0@5Yj-Q5N$Al)D>U7M~m zx8M0L&ds?!=ikqxpsc;uTJJmOm}87N7d}!FLIij>@DKzc5Ptni20^gl+jGBh&%u`$ zR3cdL1ZP0E)f;2!Yq4Z z?&JK&G-3T<(eP-xRE?dOG>zV;?I#P$dHM#4iZoFb*wG)8&goC%nYcys2eaa_*7M~_ z%oMad`GxH5I33?UUgO#ORU5yUJyY|1Aujv3_TmM&1_U9Ejm^dX_g%vc+=hSODBVHM zW4_V;fLz9W{epe&5$5X)vi}hW+%!_dTrE<$zYy9$@bhc<&k_{gAV!2V)s(gE=g!x`PqYC*u0S$R7Kfe%AS7F zFpVx&E(K$5x*hZe)ev8U+Roqh?Udd3lzsK?ULx<`l^c5t_cQRE|5&}B$#l%z^Nz## z^RsMe4nn*|{mqc`FNCjO{^VM;R#n+oVolu=(N4_v#N>ERG1b76>H-~X?yN*ajq&1f zM`lkzbyM}v6{MV-E3NrgJh%SL+{IhHqc}Jf*}v4|?z4#dB@o_zU@Y>>SGYIek`ATy zi;H~LE)3OnC5w*@(>wU+LXEl4aj6{6B2qLs?k}^e7yf4aJ7uy+X8$&W)^})iEoEQM)JUw-j>i0lkF*L zF*b+(E8eZgdC`$CTDvr}o03o$FA;4p6J|sIxK~&193rJYR&C2dGaW&EUxJQ~?iQbU zkHp)-6rcS3eAAG$F6+gyLW{Y!0hY|m9sDhwo$AH9CsC}eWy}|lJCYLWtDO6I)K9J$ ze-`QIbr6dwvei;eBNC~1o6jS=J=>r5(E?ceGW;I=<4t=z zdYWHzv{rDiO)em?T5CU}D=65sHtx94p>qAn`@f(!8I$cU6Ou!(?TsFnM)#HCJ9G>N-|uXQzwapFb@c zE^-gT?g}|;E?XdoMXJS>%kLRIe*g6JqK&-VXb_xKcDwgdB6@kOlV87as;?yDdE$1U z4s7@mx6@Wl*m9j3`rx<1m?sg9{Z5j9>lNm(Ymc=C;>4&!nGU-~iq3v`zNycP85>r^ z4Z8i@P~Og0|7DeyXu7e)rUBpIZTpAh;6-^R!ZpH6(qXx{#2s8#O6?>_+$eFlRtG(`va|=g8>nVwd_- zPpM}+<+$FD#bBDGF2cQSIUjVzEg^sxb?5tC*Z<~|p(<={760JBP|y2NIP-Z-67g-i zbX$V2r8=80^eZ^%D@6So)x3;4hZMc6d&@%FUT*l{K@V(mZ?q3G3Y7MUq$|3F~90_|vK=T_607}*u3Y!2Us?2} zRXoiU4=G@KB8@xMm+0!|h;>OOA};j0Eo0}(*}LFiwyNX2c|O{eA3yyD2`PK?iBK6l z+~fTyTs38jGzna4E9F@DTP8T^S`XE(%z^);9YJ{@7etPe$fV zNFXg=OJ&aDy4#VW1s>T+M)o6l-o18taYI9AWGO(a-tS~$;IJSfdp_O?pw zos0R7m0%>Se$-!cTRiP4%kdXe-Y0=xt2~pF*@+^!)K9uztw}Ie)Q3psyX12Jm}JOY znAsH+TXLq^94+;$?qkrW_?cXLwRm^=K=0(l!DgpS9Q)_y(YlZX-MxWa6}`4uADQ+& zN{&m^bYlDWo?3k-n$FIj@A!H!2mk4ReKU?|x|kt^#&(UqYr@-~%T=^R%Ax$y-~R3X@VtZL;KIB>)A_}UjmhAP?C3X_5}((yZGAM` z-0Y9-; zZr}CVa}w0yp5?N#8hxn3aT1n~Mo>w6gzezGA zR&|A$)qZkGxY6|UQ0O=Q07UlQ8cV>MiiYj`?!Jx%OXnILLCLj2&i&$pn817Y*VH*_ zOM0{Kvfo?S#(%ZL%5+0v;MVAk@K6Si-#16AW^&9-#~#(!7zxf%KC9tpGT-$^Z#DNA znBKd|b+z!_H_FC0?1F;pKA$6ZPk;M3vfjSQX+`s{SMNNk4v))$#$ekwsMe8i*h>9q zLrQFmAQmmp+I9aX=hcW%>Cas|1@TD%vD~rMCqq7O_>3mi(A%WFKE#($xEl3MO=M6H z0Ijy3>nn_ySsdEhO_rPArT)?CaU4EW@M!eHpVpA{)kC(n?r!t;aDo&K&)y^ z$7)QjQ?H-d_=%ss>&jIV8$v{5y_}S4KDTuG2%(S6E81)LeQLeF8nrF=Be__sBvX2u zU5zNNw(O=4&sEB!BSzQs&wme=17^3W72E4A-`qE+esEkO7<09DwOm%Zy4X!^io!`(!Jv)#bF3>lp{^Yt`IT*la&&hmxrMIbKpKqG@)F~})N7wu;2<=7m zt!r-*IdwYjn-+G51{1e9-_C;^NA;-VOR`wlF)x!rg}j%SC>fyd7+fD zyL`0|#qg?WAT!=nj1)D@NS{&o^Q^na;7Ly@3U`WpsPUvGA7WFZl^R?M z)E+(6miY*IkkiI%-u71v<6LNbv#UOiw)axvyr0?eKP6n5Oq0))xF_#W_jf&yDkzM7hm;I z)C1^0_0k3?ZLy z89d{{r)E~Ic^`{sOzf5}9y7LC>b5+XU%x51G4;KWJrfIYbIRmCf5h8xugZx8Q{il>WQ!R)jKQ-7{52a6vu2#AR74|uoq7wZ~5&3l?9{CUXk zks59y8qM-8`6ZH|D&=5B7&}(!V6dOm-+UVTA*a1h-&3mcdb3$Re7tL=%_BTby&7nK&2>+_er6+pORliDB)~AXf0ml-D*A@=InS}zt4y0LMi1sa z?f7dr)9g&~hUgX6`ys(Il5xB?(|xTe+ORUY(FfrxIRe{bmUSra??R{kcD%e3LV>q$ zvg*o4bl$4AUVUP-@^@(Hy{RdSme*Z#A%Eh;+y-{6p~l9yC$`yKbz}+s297&RqBuB6 z=;H^0ZZ23?EK);Ur4|{V&JEbuo$120oBLI97;SwMWjXB2n?(ClIC`Wdp?kU`X4W_;=~_fI%Qcw}zALOWu=fBaVXnrEcCsj~E)z4VCH z1eJ1=i=`vDJ1t)17f`6}`do#@o+LfDh&6P8b#+J>?@OK=WSnP9ov&Qj9&VXV4L`^z z)NyHzwBLPI6gtP+)f`)U|Eob|W*90}_JJq!wl8{ATzu&OJ(kZX!WjN!qc9|WhWNC^ z<&YFrvw?a@{bSvWFowOVveN&?PGiNH)6K(Hp}T^YZ7m(O#arDUU-Tr(RjJp6U}49O zzI+*>Z+oU)umD4^Q>?W>*O%rQDghd$bK7-HV__(5y8ChBgO^ej0`ui1j21TE z^PLH+*f3Gt8XA&|ii);lWM*byd-Ke`bn)!qQ+(Z$%%71y5M-G(4&s-L=TfTmjHEf1 z*)qiE&>K;2BG$b}s)6ewl`epC z^U#k;rJgG2!4f+DSlM}z7FCyn+>O=-K3$7eNXCA%Fb)&{kVuTIB2p3lnYtbT?1 zgfs{2FL?_iHWt=dj87!jhbp-^E2Z$wg-1%9@-iojJL0wr|1H$-sxYGIYJ$l#>tz<| z9~$MSH#l5^<2AN%2yWQCpDg4*3Lv=g6uoW8`}SLUxyi&ely-_sOSN7!WKt@5VG z(#penKG(sc?Na^LRt5^?()9T9Y!O`Z{gFXkfy14zMRf}#)K3{&8I;4kysDLVDndkA z(D63=10|@?i-?YYPyXc4f+NwRi=eIQ&ovxXh z{ETtwsOg!~;hSs%Rht-pyzu>sP|b;aOMkXPD*xJ=wI@+#W<H?dD68vJ=EKHeGlxEgmdk!t-|89TdH>@MPsG1D}#g8hrhR{ zs*YhLiJzYO5AymMZVyk%eVAUc&o$&asjNK8R3vSdDi* z4YJ|k9%~M`b&}*ddf3%Rj#7Q*I@MtM*mC|?t;O8SM|l9AAI?|DyHu(BOukGUvLJo$ z^OW%BP-=*LHUZueIk{~gM!|t%ua;fnoS3_J0tEYex&E+NJsJ8JYuB$R);A+ATdntP zPqwxjK9~wGD&R_=MIEl!Z11j(UzOxNNa@?^|H_gR?&3+bOHAi%Q0h0=+}WAu7-(|o zKCxu{>2|(~>ki)1+ht!k3!?a`QM2XO@n}bCS}uMeftPFE{kS@r{^g%*`8;nr27Z#< zVVVh7Ue(sq`#>$DG1(Lq<=Xu7R;}eirzDq+?qpY;<6hoKtFWVjMdaI~y6m097n4*fs>FvAYFVeaG4^94lHYva}J;kn#VU+gx& zrQ(w_<5DjT=4+fqrJp!a)}Ng|O6|cZZZxjMdx|+rLXw4*6##W;ZnmArDtRNuGDRBj z{^LE0M7Y;t?4jK37j;F&mNywW4=R7r{5v1L+5;V(+}$|LkJb#V;U6jrZwW946c})? z6QyJ`m1$3!m{iC=I_ZoZXN&Yd!1Mez;2FyKzg@r-Df$O;d5o8*4Mqr520wDE*1GWH z3Z%A$%@MgNVc&pHdBGqm+n%$B8)Et7do_y;&M)vouKO%6Ni zffK8#84Qn(wo@b9H~(`1?V68fXJ=tsChJ|-<8~+1Vwt&O*b0q)H8xI2XXfN&sa6*C zOiaZ6$8rsS`{+9Q3dLm{_eRZS-&u)7sn_n?*Pf?nq`km6j|DiMsJEu3rh0lphfG`~ zSB9&ImZ|?OLcz7lpG=`_Ip+xAu>Wb!?f+|g*)v%UwDqe-3)wl_hrXb|H$-qXXAc?0UFm$)z=aqUUi#I28x zmD~>S_Ms6uECFFCsu%_icewRi|CEowc z-Y&y3T|<0jtDI{WJTud(o^d%xvn4l^TS7*n-FS)O1eC{1?WUh6BNME*&B$A0_$P7W zNSVe(a)gDsNeZo+ex&+vUSZ+8azB+Q`fu0d-+hz6Sz5}aA{dzuaeLE|W19boKqH2UOU^fKmVAFFqNnwRsY^uXb01<@C> zv>Nz9dP$wX+$f6d51q&FI3?1a$LN*sA7)c>DMR|)YZ!okp$H6-pl;b#C(RzS6Cbp;l`z*JCfph?t{v=177jAs9O6w*7c$r{ zp{6FbO^HAgfDzw))eQ@wzbuaKnR5UBs^w6QwW@02@%}@w3FoFpzcH~g(=e>Q_1pbb zmicB-FkPRGL0G>{SBs1#>RL}@mpH9erg`Q8Z=b^4)N*>KHzAh+cOc!ICTi(Bk|P{I zI7$|qL~6it_D6WRoMmw??kc%uIM|p7o*>-ZpTeh?L_b}L3ZK2j{q#lD4RYs4oQIlL z=*FQ?)u7&7@sftwmmcqHP_FUe7l4!wTVx zCmEAr@}RS6-M|Xj6=n^qJtu_op52q-{;Kk17yrG^I#akWMNe{_Ip;# zI@BIg>}z~l85aGs6>bF8X-^d{twt4Wo)* zZuyj2E!}Ddy=}whpBf$dgOwrbTWMju?d>k2PJx*4FQGYC4Fa27DtmL;PsC7Jf~zJr zm!Qy4{kKI}aS@s^(9p#HPvLcAB!X>*SJ4YtczCs(!*px@#~$!Ua~b%E{iExb%b2lV zB+`@MsYC$xV}#oux@}HQUvF`CLVNA}izorO-53U$HQIaUbzZ*xmb+>CQ2091b^{Jn zRB4nBPrwh=IXEHcU|kOOz)Hx0y5hDoyVqao_FMEeP8(gZnBk!Uo9$Rwz%_TQhIy!r zngsUqLu3Ad{?)T@8|3j`*XZ8bqTHv&Iq%j1nUr8fN;enm@!`os+Z<;jua6;*9$!a{ zyn#)mwyaQEVpR)tG+fY3V3eXK(RHN|SNRoOpmdq;Je(BsW?7P(@BK%ywg0>udvv*B z+V`oZVYo|&x*?eZ1s)Evd>1Zmj#M|L+>LOz@Tjpvz>B*XdO;il(x`gJsp`%bfL4Vl2WcsL{3hGi>vP9c~Y+l&EHFFhU+?%Z}Rj9 zI!q}MDG|)7Job!Q`nn+LiOR^9%T)Clm2vZ3oH$v@souNIMIwfS8=I;>ZmZDZBbw$mWy}cjAo}=| zlX!3bBJ(2om-CK})k^^))LRuNA5zD$8VoRJ$2#Ca(|27qYOCagL+~+&$jRC>V`;v+ zj^ic+G2j;5W?Gp2u{U2mKR-#vVv^;~!?2qxaAOC~Qm(J<^3Q)WR#S!AzU`>R!VBKO=>=; ziEV_t+$$HpFnOY;G)+x+Ri9p}Bu7L@Ax9bM_O@NbZN(YF!i*K@kkM>ti?0-a{ELss zLn^zyYuh$7Y^)sg{u%$lccC+u%X582(^R2BNN7zEaT9<`ZFq9qaWbF5F}(iVgK&m7 zdUadMfqi`sJy%TA3)-ENFZjTTDF2Yh^`a;6 z-O=8XUj9i>(zP?O^w=qz7+erUL)S;jKXk{Eokts0jJrL)|;x zW6zJ=5yHgm=X0N2&&KVns!V5o_cvy`sZR7x$5=$9p1CzwJM1Xe z|M>z&&M%jP{ZYT3OO_mgn zi{7d~v=Ce-S6<;GN|xtZJ*BOAeTjWf2x}3u_It?r7n^e~pVEiBxxHNdNZiu9BDYcP z^G#^V--SovROL(>m1!Dg-hEr(vqrPqulWsIGHrfBW>}XTUa!f!zp_f@^l5p=q3+Ik`E7&r+uQYVI?*oHoU!uROiT8^ z$hz(>pK~MqmqTLp1@jY~S~nF*sFA@8X|2wc#a=xphSlB0@tG$7n3IqEReKJ_!`tF% zl1H)z@#w{~kCQ9OvJaRJJCWI`kj;3PX9jkDWL@i{RK_=tVt3|_l{7k8nuhUC5iCUU zrk^I(i^rrJ1pSOBzN%M*9~{f^-aJqbO!%|PCt&5uBZYgl)bI#4l8ZB|3dbgx^GDMy z(bvZ_3mf9JF8s)C{^ahJGp6}=rL!0g*VeFX1wWo`rFXkOl8-qu11YfC zFaIXb0$+!38wCV~=9}w;6hX1EO#bIZ7Zvgc>-QQXJ579#YF)UF_@@1#HQ4%!ZOp!*@|mUjEQV8m;v+3fiKE=AY^^jul_zSm zIg`(HqUP#Y{dz^9P{f~cgr1~y1V&6wCReod^agVc)3fBVqiZ5jnt;-MeC!orj;+stLX26qwL0tJAH3T{j56M2Tfl~N(8&i z>mF)rVruFIKoZ~iLad=k7pmi@!u3szsx`>vhEI9wwPkZ&#=4T{$xzR-V<-Oh7H2Ff z;59U%WAt!f7RK!-rU1U*aDk3@I*^UgqMeK0a4K;&2I|UWxG| zHj?UHY7FG1jSz#fLN2ZBd(nB73$IlQ#Sk5}M*NrCK3GUN2KHq(hte;cN8)i1O&!Q( z`k>>u4;K)DZ(f%WMJx>Htpw>L9gyMQp`x{Ce}*K0SAu9#1fJlv^E4oMO$+CdPTwFwXml_V5j8%01GvE|2!Ln-Kd-(7nJ$*}w&hh?c z*~5zn(tuf!J9o931H?r|S&fCo#ba6Z0#+XYiz~paU5kK|)A80^yJdM-Jl~||qoE`k zI8`>taW~Z*)yf{La^ummwY4>rcY6fO+3u)mSvGL?1`PMOgU=beR=r*qQZ_y|X0twV zitcfc~r#E>UOm~MfrhHh|d?B41qIT6vFQrG<{uZf8Xp@5r( z)*s-M|7M+vh=_^r8ao}Wmh64WQ5y{83}-8_6!XKN zOu5N;k+d4UWi-37!EO#HiwBo6w}nzw{Yvz%!40eOitrV|DwmTgrltShv#e88RFv)d z1Stx=Iy$)WrBU+>j@OPut$!AnO9dDPA?s9bItdB$>{*FHf7{CM)03mr!pZbFhc>}} z2q;CLf5ryMu;Y3yhtW{Mw>>_`-5jFnVZC@B^J$OaL?7b&c+`CcJKf1bOmY*kT(+mj z+mZ|xj&^om`CQd;r{?D7zzoY24Q0v9guXi5Bh=vWfl}^?@oHOBh=fMIE2J{--~X+1 zJ~C1R}8tve5d;}-#qaIxViF%MvD`*j!sTB zCF2$5Gj!t7iW?R^Zt2}_Sm*W<#&!-Iq0HD<$(lVEt5cA|W)a*y(F*btY+)CiZu z#rrdf<}*!78z`?URGlp?kJcRQ>=>0x{=x`Du1Y!KrAvi>JmKBGzECfV{;aLVMMpav zY|W*K$DBg^HtnXzyEy!5_&`?kdvDU!bgZnnc(lgevOi1C5&hSEb6P;3iMc^0fQm0q zivHX8@5XAlNWvJ5gt{@>+1tnRIv>I=*IF$G>mPl6ZpmmD=D4?J3`tnQ1(rFvy4_TA zAa5|8K+L!Y43GW;zb~G!#Q4;w2MbiaO}ne3du!vP9~BPZT6%kWQdScZ65w`nm5Q78 zU*7{6(P#(}_V3moJkuZ9ZVkELF%oY#eW`b`jdrh#?@TO~Q*H#(ezVGY`JI-Qc)Y?d zj4xEc6nFkIGS>qkhu_P@%X>N#$T#r*sqU&ss>SZga0H7+ozp>4s)%yQ`|h3P!PIo- zj<3(R`!e4o3kBA}x)*+jFvU}{!M@8wzO=tN1HFlIYo9)0_Ci6YyR*IPos_q{` zl`}2od%a<~ar3U?C)pGgjCxtpO z-OD@Rvw!8bx6sv1E8Of!6$SW$Bwfj`KQ+)(AY7XZ8bAz3N-~R!i*pU_xrKy;^z;^z zPFZw_cXxMD-q76&^)rC6rkBt6CqUq4L0$rKadhz)>vfeGK?f0X@wj}_#nQ}dsLDF~ z@yWb=-&M_8^~rpV`o~&Lk!c#qJdV3JpP3~n?f~vY0ieDRuml`buXUI+iAbX@*Y9mA zifHfbJneEly=#nK8GiHj-P!3$8qbxR&+e2eWXUqXCKl?&aoGatJ(ham1vjKPeG4MH zQWH7_vh;eLUl;o_dtuvtjX)~!+HL#^XHtQw&N9Qn&W{RKW@d^tcAKzY7F5c$4yB7) zUd#R29PfYMS>&C9`y+800cp_{`8fFxZ+NXg1%k-hL8;Do2Bn9Lh{$ZF2>^zr{&))l zsk5!^8tNV$o$_(jd7R5F#my}yMu#C zHfP7<)2G{9HjMSj4z;{&Z2eWxK)b^Qv(Ro9i+{V5)2gSYrqXOj@u=${Ssut`MOwef zltp>NlCtaf{OIV&wOJiWl^z9TXbz-`Ur1c9J6eN`H8C*>m~aAwb$549 zPuFwDAs7Mlgi`YL>&@alxhCOOo9+1yY&2x7l9H0nl=FD4;~oH$PGWF#@O*1Lmu;S^ zEVvQ)m^8eaqg>i%wHG4Et3O9swn9<7mAe6d1GWdn41~BAuh%RhW6wYkaqL zbHQmL)>hE}hFss9ux3i(F)&hB?`bmX&XHKcx}shNA!AM-jnScvB^omg33HVhhpP}d!h$(GM$ zsoS4!y&unbESwV6lV)1HQVq)!`B>|hpu^0}4As$ZEd_-^)4GE+xTsWB%|r+bPA)D0 zv@OW=9-{*bX(l9r5di*0B{>C!{#c$|_5f_2p+gB!gtu?sV#iMbN}isc1_cE{pfcNI zE{)AlsouposK?aaYIFK0yYYx>y-RHdiz@eEh;Md@aVN2{$TDLG)pkoDxi62muNfbJXqljd#T@>MtaH(YxC6Ee)E-(km$04 zFa*GSXRPD$KrR$UFAtBf~$L~>cO`lDs5-dFDu-r_8I z`~4G!<@ko3L+%*spOJ*mvip~AaoNy^wE+WwDCqwFJ$WddX{PS%^ynkk zWh&kpNaTgC_;PsxPYbY8m{iIDH}5dO2ov9FhElQaix&;RV~o|{S%Oqgs5)~Kun#Hz z5gIPTCFAIcKV`*i-WQY7hcThc|j%;-4~sE6Ti=phsNKV7agpZQcs z`|zP5tO1M{WhvyxiHn{`5=tIHU$m`Oxzs?IGA=GIZMBKg0g5FRkl2oTlC+C~k7%x@wdWSxu4SXo%u1F#(#4$E~l z8_ZLqBq7m`cJW_>mg&?tY!wo0pwkak&TgMad>eoM6c~A=B0ThLy2@%%MoSB>jE9^1 z8Zogy(}OSrn5AzT@9XQE4omDeINPf~6CQ;CF4pbv_4jvPuif*tE!)y*`9fVu`A{wk z-7Q2VAS6^>T|JtV;q>$8PXdZ3ukr^M1{1|&*avcP5#-@pC^(^_FhIm+0*A#zq&m#z z=x0e|UU1Wy?AGKsQIvh7&hFDd8NZ{SuYQWOGuPiThH?z z{!RX9iq4hGmap7rVd>!Jvt3iI%pgOg$T0kN%&;e=6{?p95p>s0ds2J~YoQ{gOT<1= zBNLroT%-Vj^7ZS=+?lk4h5zS$0GM1-QJP+Z>!1PXggx6_* z14@EA00Pwdw7XSUSXdBxkkgM>jV@Bx~9Zr=CdtV!RdVAg^f=J4@mU;l9I;P*47dwy>!PpevXBZT*H`H zl?wGPCkY96s67_(e)YtD<#9k0p?<}34VKhGKEv3W$Aw-&{oSIcB&vPq01V2_X9OJ` zA&t-KR6l=giz*>gc1e?rSH~gG!9f~i{-M6p;bMAE#}`Z`S_SH4`a7ZVGX*PbkC9O* z*++K#hNfzIpbr`;qM!@{uRliN28D$5mYZsL`arJ6?2gsD*7vEy`tI-VzpIrF4fXRA z)zs8n+l6E?2DIx*5iX;LPV2wWlDz>(s1gaddX9@hA?74qwVpqrP&`a|&x zUVCB;?9zPlp@_lvPXv&6lTO?IC8I?;?Tzji>vslJbl(hMA((j$MCzqj3_BA$yA3cg zs2TGBEuU8*x1a!xm+1FqzDZ|Rt+>OecAZkzdn^xhKbF(6e|hKt1qN%`!zW`QgiE0ZG$$5Xd~<4Jw{_D#1VdS|(n0kjAW&pX7`>4+Qfdf%cOL4u z>ry5Zm?efV5qQ&W3JL~~eee)Gh^VQlfg^;?qf|WPv(*YT8$rr~WHB0X`QKa$w0M_5 zq#L(0%o25XcVk;^pwT7oo+EBs;)$(2!&Nv4y=01B#tql`v_Wt|YfCmY7lN2-)${iZ z|EdMwAi)z*RUOlUhX}M+CTfpbg6^7|n*+FXj8FO8kK3<5JL2=p7qW(8%`xf3TAvI{ z%=|013A1NaR{C!z=pGaR$F*MUp}l|qemx;j2LQPorQ$T}^kYshlSAo0C^V52a)$sjDws!0=xv#K(?wADR_hdM5nWp)5nh=LC;Cw z$bRhT;BZa#?N!)tLn!{Z-HH4cm(w+$lM#pLDY9_#P# zA0HpTCZMGF>Xipk5@;lNT&qlWL#7{20Msj2t-6f#p@Sw6z=3*tBw<$Z0zRVM*4!+F zYrfRSphA9bHX9P<;Fg7r%|w}Tj5y{AfMbjSBqoft4@?psXnRoyNf@%QWdzXT9T>R& zJJFrV_Z$+c1rYcp6L4Ezxu9*p6FJyIqd$NC9O|N!ER(5RZo&ha1R}=-2nNt1fN0cr-RY9?fV5H88K4}|?`bGWaY;lV@}n}YAO$2CKjsB+>&4OX z^^O>h)NwY$0Y=pdv)PuQH0L;{En(_c1_oLF1{XjCv%}oR3xRFm+I!1`U6C%d@lIQ< zka@ox2C@|}7(S3o2=!Dl9B) zU#3xI)tWO0^cai}c?N*2hTVVf9;rF?3XYLV#1y2Y zO*Hz~xFV>UhK3it;pZ5Y*$U`;}UgK1VDgtfsA9~>MgEi55;U;fWbxkN4I3c=mdH~^0c ziv;=kwS%o_xcmT4)OO)NBVYehpT>ny!Mv3Ie|o04@!vZ8uGH>LP_i2-HabgycLxp` zWG+l2s^Ct|&kt0XYe`B>V%A$}2CV5AHbeOu;y4~h8<3fxy)gNvtE&sK3HlaL*vRMI2O7ifBu_N8 z)HMc$-qn>A5Qd>SbMB z9Z2U(mo9;r@grk;A~^ktfHF^|JP)9hHZxHZo-E+BUJBJ01swwRlb`tme?iu_-#ma@ zF77xKV;muji6tV>Aa(QLaQ7qFn1ux zSFmw=ySoducg_i5d=v{`$3i55W(e*II2u%wGL!LWt`j_Jb(e!V&`%V$Y&XPV#+OCC z=DpE#i`fG2@)@$msC#ah_c z$J_hS`09A|9;h&o`~YIW3S{@UL8qP_Z+8Ku1|tRVO+<t+-o2Y@jGxz4quX&~#s+A*Lawqk=pL|Bfl@=Xev8)w zY{a(R|Kol|QNAh(8U)eV*@jp%XJD_MpQHp=7at#=7zG?D9V{a-B;lcT(AbJ z%23uW5z~=WON5#OO@N<(vVYJNe-lBr_%XY+4ja|el*sP|y}1V--v0i^<5fGrL^~Y7 zM<@ZeJB_L7ISlHS)wrA-Zh~H-;)4c^`pP@q&ae7o>Oe<8$W7QU#A2xRSBzH+wcFNq zveZx^&Xz0r-E}UT94jsdVB2wB=yOQIbxgTh8?Uiv?b`kF<;$2q4D3G|hv|Vspnv|= zw5sP0fe!VUl@Psyk_to&0(t_%W3zL9dOFx~PQ?@T*y`_Z@QEE99Kus%L(QQJ0NsXU zbjvcvff$&BrVH*x6_Drj0dUlfa@eOQtgM!M&n@T0fRsEf9H!sPSj3o83!S(8WOtyY z^_mi#Ldr*)FN}d)+tCsp;y)q;{ z5(y9kqS8o>5F!TCGHy!+ON6rAi-6Jw){DCu8yh={-~e>hU`}96Ow8i2?*gC}J{cns ziu+(T1juSnuMc>Z9aTKgz4fe@2RR?HJp=7?cDh#&OywVxC_Zm{ z0#p$;Uv#uLO&tI7WyUE+XTczIMs$B8US-HZo}#9)VUb*EZxj5^qqVa zzC%8NahMOF_2b8n>+v0ikFNXLvfquB7z8m(D;hrgJeV88I$S7gNbC$(zjOqaMy=A~ zXBzmBZ5$Zpa{=zzX?IvpTm>M96##RL=M35x#bo+wf5Fzy=wAX01-v;{YU;$-3V}Wc zXS-`WyuMq1j?UO(zMJ6EeYk)UxJ)lB^_(Geh;lsz8{0Kw6AK^j^qJjj>DfP6M%XEm z0!G*+0>AOz3)qF?30!zsAFpV4seH^fG38v6+s%7Zn&&QQ^P_AmBkfNkwOONL$Blw{ zbQZQ<4wT_e^DAwJ?`@ASmn;C3T}6F`%Wa}G4ZQ)~!KDBv(D40U&`VkqZH4yYX3;ex zIXO9LX=%SK-`%3OM7vmS4ZP9ngQlH!_3XDa@)v@|_$n z@NsbWIR98@Dj%cq03sL088QG4=-F08{q;|Jk~oAnE9^F>+uB6NU6jJfnN>5!xBWDZ zX)qnSDN&?sbd{EtR-N!~UuH;l>2oJ;z`Hnylx$YVT*boav=|dSlhl#y@(Bmg^80{i zudK0DC&2+D9@?=7Z}Zw{~=kuo*P1 z!_+rVkf>S1sHCK%p5ET}wl>iGM9@Qsi<9ok06aoT83V`iG`naLqg(pqyf46eMxo#X zgrrTZb|-t;#IYlYvR6GV@|zhjJ|k7q9=yI1TTGX@dabHGKg_@io^=_IoBHmxFgI74 z@dZ|K#Hb8bt$yPhK4{&(PVp1ijgELeK6ds2vt9jHpTKD%B_lJLs8PaAq!)R}3!UG; zuXjaEEsf40RwM%56ICHPO5r-z$)^$_OiJN%E*z!$OmBpP`|$SjN=ne5C{3gkpk1b| zqqEp_{wOGk9(cjXWv33d!JE%5engbImY#0&oxu)*eRG?~p>M;jqM`x_1pLjeK=F17 z$}I3DKIg-dDwH)?5E+KD`J{aAYj|B61&WNp_mmjPtiQ)PFb{%@?;i=$R_tx#N9;WG zC=^(*;c(VeG5wr8Y`dSnq(Ei(9MBn#?FF_NU|YdlhYg?pnRML4AHa48$$0+RaM zp2dodqp+|~#CZ&73=bdp6it%@Ca*R4RA2#~ZBm`Vl-kL0gpEUO$Kr|2Zk50_AGaO^n(l~c?n6(*z|c^wf8!@8v0AD}#v@PtJWVa` zLV<{(6N{8mQ(GO`2LD(h^3hwkIsiQC+#5*3t(hvwtEeP;t)|cB1bb>l+N~Jk|C<$j z@|l97>{l$4HLJbO=LbjP2^PUvR-?av;>hhHlJm+s^Y-BpkPy=fZ#Ab~-w-bjLr zroqhc&Ed$V)+0Fy=;|Ec)Dm3Ms@DIgUFjC6`FK8T7|wP&f#ZFV+m?w|0>3i z+G@EAL2~*xIZ#&t3lLeba*Xp>zV$)LEVXsr3W;Y_Dw?G_+iwQV zpDq@)HD=lM{AR%KenRATz+;^2$Vlq(vD5lRMwK$LShl?p!+d}eeL+FNVoCjKz^iOZ zOmhA3AwJv5cU^Q~i05;i*fV$~Ads4#9uXF%R$)dB6DmABwUY6?m6er1@;v{&u?w(dm5Sb~FAc@064jxPCbFWYn~tKpz_mS;dSt(D;9!+W$^du3;h?(N~)3*Rj*tBNz6HyX?4~8mLaY`E-=dKkP=GP9q=@v|jUtix5RcDV(D9MK4JF>B{ z0Zeb!(ZTo?Zxa4fq{I>Y3^dqpO#!!#U?-q!&$>@~9qtJ{T^PNxKGIQAuP{rr>0)u& z^jldmqV{0H8Zs%52o1G+o&aN8c>}tyQUv_Sz*07B`b+_e%K?U3P?)=@76Ngwy9%QU zKq$`u6D?s3$}5WyI)9(E%nc0;tg!6BIEUrlD;$D6aNyG%DbXjxSBs(J1 zS3r>W*idLnOkpelWTIt;;Z>pu$J+lC@@q7578XS|Z|^~w@Mr17LOxh*%?gjlf%AHB zY6We&#YBK$=#B6^Yv%&F=M959$o?cOB$&n4%em!CyZO-R;D7=QTGNRk1)PE4dy6(A z!J!cm5l~!VcEv7`906>Tf6(dRr~$7we{UaQNwV@K7IOh zUww7oJw0R&RMjUS0vj~=>~QD*^ZHspNN@Z`Lc)i2DaKf{n0zkyclm{Tha|`*5<8@U zF#(J?G0a{L4Et#CgK;YukTbA)me<6A2?sv+GVokFTT)dJf!ih0)xFp+CD$KV%6rj- zZby}~4+8Y-*c(7_n>}EOd8O^6y8>D;bs5qfJM@1y7Uvl}%(v;!7R)%fU|->Skd0hk8_Fi7pOco>5QU;(G!VHg5Y9IKV?8|X zmC4FI!2Fq;Gyf3mORX#UA&wK>pVk4?b+_Z;vB4z>562%ZoBKlbCT7;qyB$gmoxGP+Wam`siahlhPilpu|dOZ!;>{SjsGuqxpkJ3w)v ztIL664<~mD9?G&MC!mBcp$Jyuj~_pNQ4@qb7^V9ARcVHY zhJYhc>9~eI&CKtf(R?BxiZ7Pb2jVr4C8T#aiNFcNkj@}kDww3u_HM>tE?t|`@?E?#=t zgMcP=x!x;YGk{7axj=)jpQ>UUAmI-=I5Vntu-L-@VtrI*RClNy!QREs~4zlhEyf?k3#fkbqZ~up@~e0p z95Sw7Ute4MqT~cIDXXfgzSqir5HL&gX?jGAnF`;l4jfS5Q{sMG`{l?G5wcxdDwL&t zHTN7n`YqsZ&hLDFFR`p@jStpknPvIiSU&s{YUm3NY0q15Pea`=e`8Vi-J&s+rDJ#J zR|ZjcTuxRN$uto}?p#{xAVDMDO#^#BnBPMpdRChX`|=bz*8XSUH)C0(^4B$vEJpHN z+jAZSun<2hWcFrx82NaDD>B?^J*J;E&F=mC_itg;US-)iIaTI$wyU<$KUe}k*<4)D zm8eO=gFJAv_rkanDW3UME%vj|VWa8*#khXaV8K=MB0_Z)%0;(6A3ITMw)aZ7<2i zh>wxQ^4jUwd&1YMV!%pF5fz!iKfXU~c4{$xmV^3e&#dtkkI@?F)(jt-CO2QcFaIQe z4K6|y$ni;azf_GQ!1o=cl;Uk$t6$wi;K1WA@@viYN;@pU_k_!YEXDtPa zgZQ#P*U5l`|2jM8(SwOp3bp6Wx`7hL8ot6j6m+D)jI{V(47k~uH@o&LcRhI{jbe0& zLc`)X&+0sUzpL)?_o)OrO9un^suM{)7qLf@P|!Tj#!jwWolcCWwxCoUL~e|;X>?5u z^=M%>?XRDQR@jNgYRE0F4tI0p?NXNEUH&qwtF0Y8WT0WuW7Ooof#ra_ATX1?Je^H0 z58qX;Gbr-+{Z_!y*w4SF`U_6weqqEdf0@Mlt0BJiul<~kssy+o-1PhkbeR;)Jq@W5 zB8_sy$)fBUjqtOWGwFegF^8P^%Yiwc82?mrd>xGPn2K=~b_y9T$A`C%LXapfg@;acbi?D5Pe5sSxC^zR2CUTlIjiy+~)-$1$YVce5Rd09Bv3+B}OnksG7# zJ}e^*3CuR`bKxw%c?tRGAz7j)ZkW91ZL*d41H`P#t8%+3g>980#JVqR0x>rTVwjh!?*X+SCJ&eJA$jIa- zm$Dc>lOC;?a1Y_s#?|=5f7w2A^4d?jP3Qf{d9bE6dfVKyw!1ZYG->8c4S9KjxTKy; zsO!>Zf=5lsRk%VOnp*~+u-1?OA706GSE#dx2f9pwlUx)0>LH~&3DHy8I^UkOm$Za; zxKfU$$~7r{h0|c6PoT@UaeK9PnAX=S_%GJ}uHkTmIXtW{xF#mdH5Kmut)YwbXtC`U z^aaPPJzq%B(Cnk!xSLQ)cI8|GGJb<#DjPf0QMea?_s^_^Lt}&&=Tl^-W)Vac5ah`0RjC1yCkeYMG>qjq8XM*)1nv^Ru4Z|?_a1z6_PIKF?sukN|fA5 zbz(-!|Djeiy7srdQCx7-@D3@!4QO$R*cBeZeZC~(p2b?k*rVvDWSXBSI0JQS(JKN> z?Bc)>8QUZ3F!^!>WuJwrlK)#pKfOKo1dS z=?Lb>5Mq!VV&n&s*HuDBtu0#0XnvKu;YIbEus!pnocZD?G56y49)2DD4g%c=T6zgp z8K6~-{7}(+k64Gv*diz;ex?m8JexXy8@eF!A}*uA&>pt(<1-$F8;&RRTc3wZH(BxF z0TuVZ)gB~&YAHM^U)#+>v>j4b9~f#BNbqG`XSx%tOwf}+_b%ml9s84`_=ftu&zgf) z>X9A}JwrazGe`bcA#d(~8)~RwUPh)M5>(B~+K2Jn7kKnnzvwNs6tf0n6u1>C3B@p^ zKpL_vKfO#o)~Yn|%@o@&vPZy35<^RK53#P}z0=_=eGP`+5v}AbQxT zX7gCSH3!q{e~jY3G@Fl1FDuSQj-~591IXMhn&%MtNp&i0Q~L-q|CyTVP1*+#lx+Uq zOGQun6sbGHA7yZ`=bsb}R^-Ir3e5VASKrBON-70;IVHrnm9DD7BliOjSs(qim5cj8 z_|@`kDJB`H&p$v(T!uslF?XR#KTj&GL{w2R!c1V4*sr*X+!wFD1UHY`h2$I;g|UB& zCmRe6Cb`D5qE;Tn^0W>R=tzJLTy{81{?J*f5sgh`1q6aZpORX!*k<*QEZ-)HpkAB_ zZr<(R8<6QSl2FY57V@#>UWpmCQ~;vYl>uLt_9}D}r;wj^r%%RY02F8MkMPZ(^YR?k zPZ=63TmeO~);Qhs=9X<7ugD3dYknhH^p=>}i$w|u0C`aJenAI%!ihsKzhGSJ#;Y_y z3CBwIwPR&-*)TM>!u}QL7w#4=KdYWRN`Pxeo!y$vO-^n+`kIo$q2qCR(&-Y!{11`_ zL(F@WCP$rLpIs}j!(a=m(4uxraUpG*EP-%8&l_A|5P*f0ZP-pm>0afK_!bagDsdv8 zVb|8JZFPPU&v$Xw9UNa}YHE6>XqGfBdW7B@s`AW8O})54d?yn=Gd3_t(-q8?GNFz0@RMxU(t`fM~e>-8HsScv#)h= zm{`zCbB=trJMqG1KAqDMUg>(|0}}<2-#zcB{Xc`=&EA?;n~@g>4WIEbEp2TWr5F|| zWOI&3HBe=S{%Mj5C(} z>X0^}B1p_MK}7ATElSf;Y3tl#0HDsUTf$C6Ow(>3S4Rj)r+x_@BJ$|)_hQIR&JTBn z!Pz~BJK0JF{R~5#E!x@3jYy_T;R^ctjiMr`{^ZN+=RZM%O0Q@fEXMo`GoFEzB#HIZ zr1XM$7?=zz5LEVWZBtaz@wpeT#UC-mZTlmahd*NpNLMcWT0HmLh{PAz*%6UE_8uxG z16y0v=X%RDi5eUc)U0Y;qL+xT{agKB+tekEY}#0zTl7|rtg3UHIS&o(Ctwo_ z6+HbtVM)KeE5ntIS+1$DZhTc&qLJs}Zs2ci=YgH;yrn>V*KIwrs@Ap?TZ{ZOmLn5U zn*4lA6mVyi^=@!L{$e`$8xE_g6`Y|97WG=^oLSZ=;)s+83op?KwmjB^y1<-HBEXJx1Y{E%y`%N){<0 zFGp`&28P1@)M9ishA=1Xisi|1ljr8eX>ayy}G-q8S88v9xZ_bb>CzmOd}j56&nU4f?eayyLED1}+eNDd4G* z)Zo@lG8~liOJAN5)O+!QFZ`b;gyvziD$wq!U)AIoovRftcd;=*onOJamOilYs6mWn zR{7sXw|TeZ>J43uvv-hJJI>L39V2_QYe-Z!iPKCAA7aLlvzTFs$nzjPUzhC{1Q0i{ zv|cVesX78a06tRwH>*e%hr;{}qSme_7rhPX%HwXrT5APg0C!#!+>HYg@+9-skewB+b*!wY%YW>9>f)Swz#D*?=qD7bb?xGR*vvDbXj_K0< z9fIL_6Z8|a@gIt0@^QO4MNa#&*X3IPj(8UUs1V^LqlVh9K%+^NAV|U=iid21e=+_r z#=8oMQEqBqoM*IciWBznuvz{6Q{u%VI&s9&O|M#}*P&UN^;tjyezTg70n&8^>fm)T zc2(BJY2?Y*r}e?}>_(l_?~3Pt!H{kVmn`p1wb4z7%;zk1(!H38#|)gkJ2>otS&0C^5ojlL zsooJ=pSwm>TSkEM~g7R2fyBwZ zuASd)CElOUmXKOizje)w=&uiXqsy#Xv@&6=HKj)_dS3eqX!twGL?+kOMJ|CUH$OWD zf+7lt&$>y2Wi5aWB=iRlRE==y)Ow8kp3flq8!yh1ez+3-!hUj}Eq&>bJ2SWJ>^y}n zWIb}>!TIv=ff*lqvdZeBLElJU1C?8q&JiVg?$|ZnVbCT5_dJOyg>8tcy-SE1cAEJy z`)fB{C~A|JS5w(p|DsR1?LbB;vC4ICcnW^*vhszy$js0X_0y%!!((G6T}F@q+eHA0 z5dtS&MB*Bhq^(3L0bd99SUqCs`>CK@*(db~%v2af{Ud=|7aq*=9_ckE??O^ni=$!n zyRDF5u)C1O+1sGo!AkV6xEJtaW8X zwT_pEy{KJB?&OAf4p>2-Sz z9Zhgk6q(%Y@J8B+8D=wPZxTUAR#lfV9MorJq4o3yVz&L~-$NF3idd-f;my5eflb3)CpvVALb8QIR9^K%HRnsJXMc@5QVBUw7Kb(9=b>EyW}lpvc{? z*fY9rRewB)YQ5T?TreF~x1`ooQ&MHf8O81T>B2r1d)zP;8_DScUq6y(YA$kG5*F0o z_N&ZljELgdIawyitg#*`n96GvH(PR_-|ETT@)2b03)y=v80=AGDDyeT!<`^?ji`KU zI#>Q@e9Y75Y&m`pkh&e_nJ@c-D_!<(Bl39Mo16X6J@Q-k|0--v6zR<`)vUehZn9dC zV<>dj{-|m8Yge`Vrnnk>A&Bl*tZ-%qKf-K1COnZhx95G~Ngst^+@UQK(r}9JkyCT8 z$hYnROj({r&88ZbVzEkv1LHQiMnFPmNH(M@^7P~A*UFS)U;EOrkS#z-#ww7FTzsV7 ziW8i#iOW+UlfCiMa}N)gA6-bSqR6xM)9mf-OV2AW9|&}Kg)lM5!x*6 z4NU|GJEw+l+RRjB5Cq>8=O^VD1CBnD(n4Qldaod8TcEXq=T1gBmxuW&uPXkE7e&e` zEwQyjK#8K2rP@;mu>s4@##*ygnVj1?+H*hD8LZTLNAcYzkJa~#@;YOW2Q!SkW4M>C zy4Cq=GxR)U=?}+;oeX)xJTICba`Oo}^`zEi!|w2JE;b4n_hry|#>cNbaxL0BBcUmxq8{^n;bX`w*?a{{Q&#$(07+y_`g1h@6 z{hOOYtP9<+=jw?bJ`YBV%;#rMHwP693OsZ~&tGJr1{c{44-4v4tFbj9!M6_g&`(WH z0@UUHmLJ?XTEFtK3W?76jyYJ^xKCAR5Ul>hNUbS<_agz3aE(jWM;P3xmHsr_Kp2bO z?IS_JBCTNMn-A7O^@cdkxfVONrrgx2%c+gDI{owQ^udRMi5Z(pca)l|8lfJl%0jgv zOeC6@$(xg>;lB5HZ-vu(!oW@L)93B=-|ne0WNOp*o*fjS^l*Y!=)K`AsFBsN9`(wrdgU$@EkfST()4?&R*tG{bQpijp?A5Sl37n_T}!NJ8}k}=bpkViMdLCtLQ=T!lR`V$^@u>p?%?kkOJ z4>#veDJfSum~{lT#+0{2M14A-PNIvoig%Q>j#amin$}4zX2VFl~5xPA0 z9_|IV+o7X`mU$!87cdceMQ;I|~$U zVB!hrbW}_=Pzeok$K78A52@j&Z0sTxm{?!)X&z1lI|zx$Z{Upc@!LB;1UZza?9r&g zgF-+4MgBr`*6i3lU&9buSCkzCRi-4BWwfxP@uB~3@U6nxfasJ;jNI)fi{U4)=v-wc zh^~*{Dfr6v)UKIvL;wlr+u%3VI+&}WJG_&FDkG)s;~V9iJdC%>6)~@9E=bYs!A$fF zqNFxrf8FvwUk2h00Z|v>Jt=J4Bnk%ZLi=%w8A_N)f8dqmBaKID{9 zI=<0%gzi99ae0ok^5g{FcDCRhG1klZ5J_s0@8fmHpoKHupM3^N;%jH zZcU*glQ%mO+?SoP@g;rATOEWR?CTASJ)F~yER?GGt6TqAh69VU~0Y7-DiM z5WRU>hdw`6MM#bu9hGjmnKu8CjNIbSHu=CISaIH05*|7hF6g#3StERp)7Gg5Vu&2l(72>r7*zp;}HIqA={CWCCW__f7vy^j_OY`S(B~ zO`{+>!~z08zr01U!t6l#C`fy(_bm2@pr^{ZuR?kF-uB$Lke;biImM<4RY&u-oSSwG z)1$p-X7mDf=PL1{dN10%SD)-(WaWA7s z(m%<;;F%(=>EE@t|Bkjgc3X}qbaq-AOLH8rH%0SJMhQELPumUb-Y4Bcts~>cNQ%@` zr;nOb@#I`*9oI!m6q=OdF)AW)c6O4Qw)ShIM?{URfev&#XPN^T-bq0Ownge|6>D1t zNi{1%)xJNk$>@3hSs3qO_(+>?(upcddWO2sgXv1=$({lkWEp&ElP~>#1~Dz))`0Im zJ77R3BnpVZO1E(?0|hR^C*&?$XV9vho%*&q?%q8&Pe4X50v2ddUW<)@o`{gG6cXh! z59N{k*fG>l&zO3l)v~XHnM&S7**eB{MDg1gm!-zk{x|W_t3f(HMizo**?5*0NfNv6 zW|N@1=eXOh|1Bjjkk{H7TiX$S^{nmvQf9e|LRT^G`@9+Fm%pZahw%nfJ)c~V9{p_J zoCR+FYeJkgI~#p=zu?^MVqCrQmdX)9p2;hteC#vNsbdjK^}oXw$Lm!WCu5<0w*5hg z$2p6dyX7M8RSo;^vhKmA3qQ7pQDEV;Lfo>WN|LqJOrGn>Qm>FmQR6P!gR^n2qY48I zqx9P@cZ0UyB!R7mLkhOxy!K0Ld4dts^Gwm@emJ$HV1ctDV@}}z*XZ)c~q z5n~tfYyhc<&`(8bgCtJkgqxfY;};1(B*0Xh_12?#s*P045~9O~x!Cex!52A{)JTJA z8_#sIHXrH!1AIh2nFA{X_2MFc|1-!W3fDiqM5TXbna|v%_pvwmaPxjI=S%Q%KLs4z zk~DM!PFj&-l*tZAS0O9^YF_^T8X}YYN*geZE-jjd&nu}{4ybQhs}M1n53A+yl-VhX z`|;B)68v_9pvLc?3haH6q!4uRUne(`LIcBr7kdJKnD7(?c6ljQ6P z5p|$MC`({VvSr0habqhmErW0_T&fL!Hc|KaN^bs+eSJ%R2VhYYfbw6%z%;kINHR!l zRG%13lzy!;*iI<=J*m-w0fy=?*=x{_$QzgYU`*D^vvpn@%drC&ZBmpwIp+j^uQjeK z-Rm$JgT-0~E!)bVU92X6L1u%19q@lZDoJ$T$zC9K^_uJn=YQ-?DJkS05Qa^!BOkZN3ki?2Rw}y|+zn%>z@1W|?{_}X@N+h)eg&mp zH*d~yg@+5q4*|vxd86U?QjL#-Y^)%ir05}ryND3(A{05V;y9DkgK;+x`+roGfyi*% z|J@kszvdgW^mirAyurWX5kCEE;vC`uHzH^s3cQ));(;KKro<0`i6KT2@G2`y#q>-m zhJ#XlaOi=9xJ|3{Q{IPfZiK?5K}U#A-1C-v_^t&1U~@Hd+cYg*BWsmW-Gl`6N&k*x z_KL;whppA8!6I`4239kc3?N7rM>i8&|G-}QvLlT&Z5*bZiw8Yn1Ii8Mz0Vo^aHV!r zHAgxtUvce=%Nqz{#x*bWQn@ab_~|xCM;r6}vm+C`blwxwpuEwG-EpJ4*p0fL;o&Xo zgsHQAzm5wXrOn8_;Q#^kpI9YI2r|U+&XRb7z`G0^ZB7QqQZ5d*s5(NZas=Z*!n!-* zQ5KI#^@j`q>qEV_!9;;Hnlx1xR-))aTWAE&d(hX*{q7h!ZfT#alPzDI?s1X6CuGX* zxjR+8H1iXnQklo-8DAa(g$N=hQ?AV}RN?vSqFKWE+by1Y0$O96hjDr-$rkN>WebU9 zbs5Qx*u2Y7Qp-PLdlO%7I2z1H?0o!qM^wgXlWPQ>FI>6QWRx&zS2B6qb7%A~8=v`c zH`8l_^S|_o8+qi(b1N9ZY0VO|2?{@Jm4$+_opzm!!rS5KqMa9C7KrPP#*7RH1*FaB z)40MUQw{*KVc=AJlIZTap#t13IX|LDsT2pNksmY*;ROhvi`;Ikd&HyHhF%pk8g5Jn5Ts6-f_Cf$v(*~ ze310}Guwm`K0SV}+4f$uZ4f5>biMv}6JWt^_y*yD4#T7l^nlc^_77Q3>=QEo=pMq# z&9LE6p}2UEerWL!K@-n5;(6AI@&S*hwYd0LB2vOIaU(3IE>Q@cJulVgK6s2&sO5KB zN)J_LUi2i{pq+~(cZ+x+6W7Ii;q!v4JKk=}VQPL}kdWySdjY7Ll_z|9`1@ff<=Uf5 z&^RtX3gt0flcESHzn3g%w0u^lZl+qhvq>UO$fTdww=>)v=khC|!!p!7$1gQ4|HVm5 z`bx(L$(m^Wj_7&_b{-M%m+tDp*4%7CiNtI3>uzU#QYR+_rw3Ih3t0>}TCQH*9}4k} zo{LjoI@jsm93zmd+iA=2#!+Y;gC6&S`SB)<%=1;=!x!AXhcBFVW9_<4nO;YhBu>4D zugrgB>oD^{Y}7@XYt&|Ciu;e!absA+&gSPs2QM}fTtAK{nRQ!YDmZuqIftyFLh{BN zm7cZAp}%CLWqim{9`H{mu{lHW9T9cz`zuG{{-OQ-VG9fIMkqirgTw~x9avT$X$0)f zW{%Pk2tO-@2f4Pz*p5`se(ga=iL4x5D4-TS`Fm~NsYpvQdGi=-{|Ii3d1kDul*^Ol z=@{xScQW=POijV3^)1E3XLi73lxk;Xdab3U_2EM}5|RCvg2w2kcvzU0wAAVy91nT? zX}gX|MI$IJ#VY<+{#>8PAMr|Nn8lEC4i|O25KX+Rvb2$cMfJ4y$5rx%nxy%g1)Q8B zrJ@)0)ikj2QoC4keGsROL{#@5MTkrdSe8Xnmt~eT?$K~i2eL?kC4I`8&AWFRPzltU zJ$!&|5mWc3p*fcz-zqrux5;l%WzTkQ%7hGM?YP7rRgtX*<*G zGnd+sN{Wx}{oOQ_b3n48TA<>vLawURe~MS{wgDQp1AThU{}_wUCS82_MR2QB5%t5y z3rA20c>?(PfRg=4@)c2G%A;;-P{_Rqnw_33QvQ=`lQ?Ejgr2;3ypPYR5>O=lwBbeM zhk$P+!fjIuH+gg~@*5T*NfN*Yc#b=s_!WQjo@C9TU`?y1~%r-RcS4Q<^PmQYv(Dk5vfz>S$UKYwli}mhO&Zc#b=ARvcvTQiqFg;>Y?GG zO3%i2Al82hk$xP7d&ExOF2(YeU6Tpr=x9?&c+78?pPtU^%mo>L8y`m|6R|D_ay>E~ zS@tC>;#!T=1-a}S^lPBi$tB=ztKPLnIZkCOzEq@?qWz>RK`5p4Si21Nn0;%QCrw5g z*Z1s4Ek}}~w)T_(CR`@6!<$8&B@m$8g4s8BB5A97m)m? zICmu)?yG&X+#5y$RC2fnz(o?kn5Bjkb~7%{3#)C&Lf$OXm)h00}XQ z0OoL5R$(Np&(K_T7O9NSIX5jdVXuX>c2KkKDH$gaB5~pq`k5gNymMTWmOrUo<-Q@N zR#X=6$-k3 zPXRl6&?2z#kGP`*41_PnfV54S#&pEnYOWfcAW!hT<3SRyIi0Vzs;E)>D_Td*nHYup zP}Ciuc+813k8Zo$ zx-kE7+uF0dvSozc>q8GfsJ(`pjk_-YL4i)_q1H3GbCAiU(uiLtY1PYwd_-jef3TfNvo4#|I!K#JwKs9()`ibe7qxT5do zSrHtD7U2P48F+-sy*!ty3W#$9@kj#inn;s}-u~*q4h@_Ud79jZh{_#x3szM%DQ!Sw zEF7E2o7?>Ia4R%&cat1e?ufb%=yp;{-fejGV(Ya3JNH=`$C~?On|L72XA%AwpSC-6 zF@{>EKL|5K22saA928!CuA$qb5|vWH2Lhm@{63Gl!f^Z*pm;bdpoYE&nAvY_={FwU z7-5@kE%jtItQYrkP9%__&AYad;626Qz%@B=7ZPj6aXubaIC(qv)YsnGat&DE#iz!j zwOBZ(3i@AY&(LGssXzT^wC9xX#Q25Fw|>V?CGBxTVz_F7^m&=9yc=A3f(c8o%iAcEV?230RXj@j$_> zoe8-wS9OrM6UtJN_Atytsu9vP1b6i03ReyuMuaH>v7A2sw(-N#=sYa8Zllg30BA*+CT~;Vk^_Rkwu>mIaS7e6XkkpCBqS z@>_1T?C!?H_s`)_FGn4Vo`^RD;XoW+M_+7&dwWeT+*@pK_xG+%J#a~JyjrjAXKt!= zY;UUx^CC#80CNK;dgBFm^%O%vQGAcSxI*;W!yta NNyxv>7uWsr-vB|-yLbQq diff --git a/package.json b/package.json index 6fb0a4a0..1cc716e4 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,9 @@ "scripts": { "dev": "concurrently \"yarn workspace MemoryFlashServer dev\" \"yarn workspace MemoryFlashReact dev\"", "test": "yarn workspace MemoryFlashServer test && yarn workspace MemoryFlashCore test && yarn workspace MemoryFlashReact test:screenshots", - "test:codex": "yarn workspace MemoryFlashServer build && yarn workspace MemoryFlashReact build && yarn workspace MemoryFlashServer test && yarn workspace MemoryFlashCore test" + "test:codex": "yarn workspace MemoryFlashServer build && yarn workspace MemoryFlashReact build && yarn workspace MemoryFlashServer test && yarn workspace MemoryFlashCore test", + "test:snapshots:check": "node scripts/check-unused-snapshots.js", + "test:snapshots:prune": "node scripts/check-unused-snapshots.js --prune" }, "workspaces": [ "apps/*", diff --git a/scripts/check-unused-snapshots.js b/scripts/check-unused-snapshots.js new file mode 100644 index 00000000..12955ca8 --- /dev/null +++ b/scripts/check-unused-snapshots.js @@ -0,0 +1,43 @@ +const fs = require('fs'); +const path = require('path'); + +function gather(dir) { + const entries = fs.readdirSync(dir, { withFileTypes: true }); + const files = []; + for (const e of entries) { + const full = path.join(dir, e.name); + if (e.isDirectory()) files.push(...gather(full)); + else if (e.isFile() && full.endsWith('.png')) files.push(full); + } + return files; +} + +const root = path.join(__dirname, '..', 'apps', 'react', 'tests'); +const snaps = gather(root).filter((f) => f.includes('-snapshots')); +const unused = []; + +for (const file of snaps) { + const spec = file.replace(/-snapshots\/[^\/]+$/, ''); + const name = path.basename(file); + const stem = name.replace(/-\d+\.png$/, '').replace(/\.png$/, ''); + if (!fs.existsSync(spec)) { + unused.push(file); + continue; + } + const stripped = fs.readFileSync(spec, 'utf8').replace(/\/\/.*|\/\*[\s\S]*?\*\//g, ''); + if (!stripped.includes(name) && !stripped.includes(stem)) unused.push(file); +} + +if (unused.length) { + if (process.argv.includes('--prune')) { + for (const f of unused) fs.rmSync(f); + const dirs = [...new Set(unused.map((f) => path.dirname(f)))]; + for (const d of dirs) if (!fs.readdirSync(d).length) fs.rmdirSync(d); + console.log(`Removed unused snapshots:\n${unused.join('\n')}`); + } else { + console.error(`Unused snapshots:\n${unused.join('\n')}`); + process.exit(1); + } +} else { + console.log('No unused snapshots found'); +} From f7dcc333d66636af30e332790f943d7cdcd3c691 Mon Sep 17 00:00:00 2001 From: Sam Bender <2336186+rednebmas@users.noreply.github.com> Date: Sun, 31 Aug 2025 11:02:10 -0700 Subject: [PATCH 2/3] should fail --- apps/react/tests/custom-deck-notation-to-study.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/react/tests/custom-deck-notation-to-study.spec.ts b/apps/react/tests/custom-deck-notation-to-study.spec.ts index 6cdea5bc..852096dc 100644 --- a/apps/react/tests/custom-deck-notation-to-study.spec.ts +++ b/apps/react/tests/custom-deck-notation-to-study.spec.ts @@ -87,7 +87,7 @@ test('Create custom deck, add notation and text cards, then study', async ({ pag await page.goto(`/study/${deckId}`); const output = page.locator('#root'); await output.waitFor(); - await expect(output).toHaveScreenshot('custom-deck-notation-to-study.png', screenshotOpts); + // await expect(output).toHaveScreenshot('custom-deck-notation-to-study.png', screenshotOpts); // Open list view and ensure text card previews correctly await page.click('a[href="list"], a[href$="/list"]'); From d81d4bdaa3a04222a0a465fcd8e40628a8241fbb Mon Sep 17 00:00:00 2001 From: Sam Bender <2336186+rednebmas@users.noreply.github.com> Date: Sun, 31 Aug 2025 11:33:43 -0700 Subject: [PATCH 3/3] shouldn't fail --- apps/react/tests/custom-deck-notation-to-study.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/react/tests/custom-deck-notation-to-study.spec.ts b/apps/react/tests/custom-deck-notation-to-study.spec.ts index 852096dc..6cdea5bc 100644 --- a/apps/react/tests/custom-deck-notation-to-study.spec.ts +++ b/apps/react/tests/custom-deck-notation-to-study.spec.ts @@ -87,7 +87,7 @@ test('Create custom deck, add notation and text cards, then study', async ({ pag await page.goto(`/study/${deckId}`); const output = page.locator('#root'); await output.waitFor(); - // await expect(output).toHaveScreenshot('custom-deck-notation-to-study.png', screenshotOpts); + await expect(output).toHaveScreenshot('custom-deck-notation-to-study.png', screenshotOpts); // Open list view and ensure text card previews correctly await page.click('a[href="list"], a[href$="/list"]');