From fc456c042f0ed01414b30c1d8b24047a5aed6abf Mon Sep 17 00:00:00 2001 From: Daniel Workman Date: Thu, 23 Jul 2026 07:19:15 +1000 Subject: [PATCH 01/11] Add Train Alerts project writeup with app screenshots New top project entry covering the TfNSW train alerts build: Durable Object alarm timing, the Stop Finder type_sf gotcha, the DST window fix and the from-scratch web push layer. Screenshots captured from the redesigned frontend running locally against the live TfNSW API. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01DYxU2GnruStUUdoMh1PiiM --- .../projects/tfnsw-train-alerts/homepage.webp | Bin 0 -> 112140 bytes .../station-autocomplete.webp | Bin 0 -> 109304 bytes .../timing-and-schedule.webp | Bin 0 -> 129046 bytes src/data/projects.json | 21 +++++++++++++----- 4 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 public/projects/tfnsw-train-alerts/homepage.webp create mode 100644 public/projects/tfnsw-train-alerts/station-autocomplete.webp create mode 100644 public/projects/tfnsw-train-alerts/timing-and-schedule.webp diff --git a/public/projects/tfnsw-train-alerts/homepage.webp b/public/projects/tfnsw-train-alerts/homepage.webp new file mode 100644 index 0000000000000000000000000000000000000000..62fcf725f7ba4940a2f323c22bacb27277caba3c GIT binary patch literal 112140 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
>uCMZ;6K5>I$g= zE@6JCg3Cn@Ufft=6ZU!{AW-!@&Gvp)$_mq{MlTbLvuMLq=eweLsm;Ii3v!SV*%)QL zQ1@~vfP*rdrMXA@v9pj0D1wh}S(+I?j$EPsD>YL(#bL_Jb&OsAoh&rz=c~M%Z%RTZ>=8Qt6dBLFM zLZdLPqJbudy0jTdK=hIN`JHR=7u;sxByTwm*$ql$0Fv;#8^lkES}eLaTPeJah@7Zh z-17N!a%zHJI0)YAFJug)FU29ZD-2mDt2lQb&x`FZ;OsSD( zSInz>p55`Yk=mAtCwDNnFy~?TaUIKUme_Ccrer+x<*}%elgWNcGe$mtM|`C(-88^; zHgQhW@T_@|tzzOheR(ttFuvoIcMYyZ;^gyc0%J0^XRZ1G;x(ya12jelmV~J7!D~vt(TL*m=#F8Px{&;`#gK#L(Jds!wpPUSc)Q zjHyX^)}>DTY=K`x#k+&vN~l=Mltk6+Th3N$bCc@dA2W5he6+`r7$M(UiMSk&lksZI zv1>Qmlp2g$<_0%9WR>@nL|i!qr;P8_-A7trwTw zlAS-34uAsgq)A^*w3yJXr6SvIR@7atx}2@HZX6+J%n@-w2ay#Q*USyZYBaAovhzl2 zs^KW+6`^iaeHzK9?#cYg{UTu(FWUf5)H~C~;NY(bE}-^~IP-LmB#|fYjBrzoOvk3% z<#;wW07b!4j_4i}{#9r=x{ zKu>TV#X?#LiRUxiwJ2sbaxQC!OUG272)ft>e9e+NF@?60S2ufthGKL3`o&Gi=a|r0 zRasfV#f(3F@+lYO@NU#{TlGAyMEy#i`CZmH-l*!`!aT0}KDXLj(=E$KYsrF)9-jE1nFsKia8xxvzG>h@}R-Th3v} zv*xQp!y?xxl@SChsK&&)pwXXG{4PU44D;)6Ux{^Z<#4q{e=YXr{Q-TtA#b~~f%m?C78IK3t(N-Q?xtJo@DqoM#Phy`V%*%yrg=Z0LB;0*S zUWJEnsPC{+^Wx@?y`sTv#lM8=T0tV_mZlM?`SL3(->KJ*pdH-QojLXq!Ia!xdq^-> zP$5=YE7H)0Ii^&}=NT|Q;P}SEW0fO3d84fvH}!=W+V4XBhAi7=&pb`?NQlt_O~4#V zhfW0lD9FE;Hp`0VsXryDi!_i0)qsypbEWJ|SYkfPl>^}9w@FzY`Tm^NL{^3LC4^Tw z?{L@>0Z&MDcpA$zzj^q!93-)+RS8Y!zW1T-jl@HN^_tgh8r<`3l9u`PbarpW&q6`{$E98 z=wpzm!w!jls!`u*Y24)*D*T&>D)Q(cSkD;y_;=6_+(i?*4Z@I$7sf)8Q8m8ta0Mo< zjx5Y5y@0U198`;ZfymWo9EdP7+q{qWwe`pGo*BKs8n6K0)tojM>y4zZ@>V7466q+N zN_h*PLBP6K@3ZhByp|+Viv>X5juarVD5euuOud?TqH#wfo;@^|G$1c=eHhR4!DDm7 zSWlp8BZE9o?pvQhE6A^JeMSMdHT|_4r_d(SQ@Dtx2<3E>{;N@Olnj0c!;9^H2O082 zWXB}R-MM6l)$nX)+_2j4mPDCjVDki`9^4T|-` z-5ibMoqa8#3H+Gv)KWpxGDh&bReB01l^aLqRP z7AZP@nUKI(m_?CHg2^6hFpAModU_)21X^CA1aE<`Px@tGpt^3W6g7H4Va($wf0Q2-#Qal^jE!#Jp5-q!RLa*Pf0F8az{+3Ft_z>B z1?^|b?5wALa`**BXD(z7*(9{K&whp|?&`dp6NC8JkS^f{_~DnbR9Mp$9ZY+HE@}xWqB^bqA|%5N6jvX-2)j0BJ{~ zDBukhP^IS4MY(>OT}ufF^BxighdG>=w~vh8V^sYkE{O1p1eC9ou>>Fhe1xBl#-_HA zsEr9t$sUV#GG0Xqhhh@8CBuzZ`Vcv=nO{anczZ=;Li7a$aqvJdm zOS!_v`Ha%zJ?z3Mm2kTPYw(Wn(GQ7ks23iUC^IKMwR>l3%xiVH7uuguIz^-~V*tbj z=Yp9h8}vWdYyi5I3AjUL>n9ANq#j4K$SWG-ZM?oULe(e07($K-%j#Gxm)cUYB}|Qa z(t7}-m6kiem^@#q6jwy4ba(F(b~x~XTfh1#>odcg9H!0_ep^=5_t3LG3$QE>77;Le z(T_I{zg9df)CC8sLJ8uQ4YcC)4gTP|S15;!3aRfQure5tiukApas|KV*`@|umJ1DQ zx!pkkq4FFg=3*4kNQ6pX(7co|E4XT0Awv;hpEb3gULvffy4;(*>{vI- zuV+Qx=yu*ZWF+TJn+(qIuJk50wT-4_mIIVjW>LZDf3t=D?BUs){4UV;8wn#mUZC`Z zoQXXj5oewX%`Q@i*_6-H{e(U&otr+;ZegJYW*ma>T#)xt4bak%dh1Gg9C}7~wJDj& zm!#$O2kg~g7q^m$54^8+E8$P8+)IDk@g3}O5V(ZuAotc>v||T^#2&AItLpq3t9oAO z^siv5QU++E|7eIQdhn!qQ<6vieY6KP&9QNJ=(8KQ9|7e1gWDDz#KDMCD&L6>$TqTb zbCq7kd5{=!!dQlZEb}f!j;qWjifsw7wf)IW{Dt^>B&Uj=*Y{b;i$8S2?1fT0>#FYib{i#;#kxn(v?s)lGJ*3y+-JxQd8I z%KBxdDKFUe&W6LHEqF~c#B7GDD zxMi?mdRp5{J-J}SQjIS}gTyheT@xTp@V=v{be-5Bs9|wU^&m*G<_byN2 z4d5F_WZ3el@AKZ|Y1s;{omasMeW0&y#LA<&rMJm*Q&w3MI1nLn((yig#GlHdZaEQR zcY2zf%h~4mU$83GDl1t(@ZqRBdSuo5;l{XYHu!#nlaHre`g`;{<$1i*YP=pwI$}0N z7YcdAaR6U$=hwQ6H)C-<-By8BRyJ8!-S9yPmT&6j6^vKc2chFlNpKRsh?x14Dhp_t z(&&yOR#CkL59upcLnNTv>!+Fgw=(Kd5qUi!IC9W^z1Xv5aDpZC@>mSkH&ht!C?r4k z4zV%m-6Fc;on|M4@R@Dwq6!q-Ai>3SpNBg%fGSX6z`2+W#Po< zZ$T11hkitQukI>#$J4Ax_*wIN^~%O$r~vTSUvq5R4}AfWlB>QR48V9) z{N2_sH3UWq4q0Z6(4t6BYf+LdF-zRrzbBPU@c1z~{sq9@*0-JJL&II%W4v}#r^VOT zBGVIOts1fi$aAr(DblIP;)pNAJ7{V7SBmI|R{b`Te*7SYqBP{Sw;Yi$s2%s5aW6>>gga9ce z%~5ki3VCn4e?W_T?7E+XR8X@ntcUF7lEOPDPZke_zpx4~;_>j3hKD=-W4f(hLw2c} z9H!MSFZprmF!~M}cI}xk;^KZ`Zu8(!x5+TQ+KbjpWsuaaLqBHV++(pobK+Vq_so|e zs7&8B;9b*$)&;eQ4rIFy;skKF_fFQPg^Sy7EI7-NwO` zNf$1cnro{@@q~oaRD9@X&uz}(E@@kq-E>k$V*d!f{4${DSr@=YTjuAh55bikkim2+&Q;U`MDwUq1{CrHc5Sh2SY_cj3medr5p6?jX z$XXn@O8H9=nUYe9DStv>uz%$|wDy2(zZ;fr2jmvV%4GUp5VyC5Mdph&cf8LIWYeN3 z^GLj#e@`ZpOChB)-y^8GDroEkZmDZ`vkNoAAWA;vbfR56uxl87*GM2Z3igPNI|w{w zJZd&(uP^}mT8{v2^2v^vZJ4HT+7)IC!>K%=9<#EtCQ08MIgshY+B*lkv(pf4q^yx1 zG=U4WpDPWxa4Hs&Oa;Fr78g&&*wL~-dmD{1m4iZs2aCD_OE>JRVJJ)@0G>J%eW8BR zdMFIz4FG^xYJ3JsO~z{57ZJLhe|(7BJcP&NMyg<}5)`@~DOG%dxUDJYFFP&B)nR1< z(x%Q`NopiS9!vQb2GmZE5Y6a!m|!wpXV`zj3coO+(r_ckefl*g_Fr=3k>i^9((-9~ zb28K0ShEXgtSCG8c%~K;E}?_Rx9mrsLQ}`$0;wI-LJkno-Bo>Nh!N=`OB_j+wy=p5 z7tR=?F)Ve5t;6#3Oaq5fHGvV75=|~%e1js6>k01)!foznTP=r03oyG|rFo!sQ))|5 zc{r==%9Xs%ehT{zZdTjRf)A$~7iCq1`buF8pP7B<63&U-1q>c8M?eec9Jy8dX} zU~^{d(qAKqaC_sAubugJMOmbOAraa%|5!V~7)7HBJ&Ufs{E&J~&fv{NM!&v8qndD% zE)zCst`Ww`bjqP!Jc-B%bAZhRr`@VkH|10OWVYB9L^#WYdF48e{&7o4p{#>`Ob7{W z|7f0L>;>5?(0ewBhf<|?dUa=_E5su}${KwwzL}m2x+!C`3nVlTv`Q+Bm4RhO@2^@%% zs2bD7iYO=Q>l^eHFPvGZ`%~3RXzqwHoO#>Hxh3aHoE&8If|3I23jKr_V3b%l@Qp;I z#4WNbA>P+BgV!h;OfjRj^^cd$tsK-1=@q6E@J3|wQukW;7&_HTZqh#cHn?Dy?~yG4 z5$>_#jhr+2+cb}8LArDJ1VcqP>TYvL3sRquBpn}_2j)CBnWN9wLp7m#e&RS{=(O|P zgFq&(*KpTT9>0UBgpnzisX@fBA*Eu`z;;udptz2eA}@VW696_#?>TTQpWE190Me_I z6+wW?yV|@I%$=S;{#o%-5kYctCmCw@lvMycZraJ^F4LUhlp%7~HUyZNVQ9wNqTwf% z#kfA{J@X-6h~fsAqS2>hN6TJgpNwfXun--J6QlNKC>gY&4ZVf-HJ5=idSGGJ9nH!LTz3E9E15Qx*24 zuEAEqft1E<8Dx8iPY&Y#!t4a0z>twbBqyM3@GoT`pU3QVkS$d^+t>6tMKqqQ=JIXx zW6#7KeA`3W5>pTc;~xYnI|&o~$AtX9V*5poXF8tA0Vj5E5X>&Cj-$%%#r=1L0WkL{ zjGxhk80M&5#|fc%CoSPum#>}=#xW%cOP>n)b?U`9$vbN!nF88aJo2MY&!IGAIN!tz z(<*m4w`loWt?CD6;1}DWW2QIII`y=KHE^>GlaU!kdjVFh!92`COcA+JeZsnYd%R}> zUSyz#=sPXVB0d|Wl8>;~n1yct8!ug~{3D7d2>X)jnDuKUf|k4Hq1?|(1i2$ah(wa* z;tmumnJBCq-51H*YiDZ%)CYi+Z=q=D63;UN@?l!D!2@%TGPb-sKzmIC+7MFv3!J)UNc;Hzmv*K z0^Mi<;iBvct(jyv>2_&P-YP95^bj&W!hFy$4&w>3j)*~RVg2`9$>jqdlD-wVA1%>n zIin`J>?p*0oUoZW#oF#ELia+?NN%)d&}<+QOQ3CVchT<`tJI$$nVDbqaR}yW-xNAu zqN+vC3_Io#bjP!(DsCPBOE;=1KmzJSc(+B=;3%aO8TVSWUX6pXEX#G?-6aa+>Ns#E zi){krffdkpGHAf!ytgd#B|e4%OFvrnKzc?UqCCBFrf>Z1XZlnn1NUA5WtJq~4B25c zyyY(o8)X1dS+mhU?$3aQu_F0i0gR9G+Fqic_$eTDnlTeWB8&!e>XKXS{eqeX`|I8n z2$-_aL7*9s_rtNCWJ|r~#0LGF3PLiZ+BwpaG)ptyf62TFy`DG_;Q24|ecO!+C&W6# z8#C~r-k|M^4A{ibV44KF)QQ)|=FAnq}s!zTd)HZ{{`8~Nv}($b4O@oh511J?&;=LnXln*pb2QW(3sl%pLe zjl-uLwFsr0InH@R%Qex(40vf68kRMji4Gx=jZFGy;1V0rO9p&1l+{TPiM;^mCg5~_ zl=kG0M&e5`zatC@1FsqoI^m!LE&PxGgk868z>B~FwbUqnXwdbJrpQ91e z^yg?tS|=F?Cb@S+bsDxr+!}dW&=8o;kWhe5_cHBDBzM-|7o0{zyrz$>9L zHK(C}-YkO?OPh$FyQ9Or;)TVsqqEys^A~S=G*cC9nqKi5ynJV%yh*lh{88mro|srh zMlK@4*`UUYaReCBkKLvT@5!NS+ZDnuAGTPiUxJhj!;CL(@^e2r9|1QBX7F3F3yro7 zGC~~YS>NmG6eKQtJU`=52zJdVb?QPaUV;Jf;Iyy%jO2&+0P&j|DZ3ns70F?!MM=|EMXB& zlc_d?ig2Sq(%e6UFxo^^2vY}?M}1Ms1VHliLiT_@Gi3S!{ka`?2e(Uwylxtk4#{Nj z@rb*!hDC7vG;w8Di78$Q+k1T30Vz$g|44-C_b2mP6eEgaB8*aOz?V3BrBs?05DLFs!Kt;`bJ^JQ4kLRc=OiW7NF77tWitsnjY-Kb{6VEk99^L(J$YyoC>fwxn`(53g z9J)hDDyX9K)i&T(IE-WTJcKQrsp~c}FsV=n&zYnzkxnY*;^Zl2ADW)M8u;?ulz6c^ z;l3V*cS&PH&`ps0$t{gw7{d()ZeODZgIA@$Q1h2P&^J$`OeSPMlCqibvb;>%O6 zNP0RH^2@A6qU;aDQEub_pg~B>n4)|pb zqGkrkK4EkrXzu9rD?6L2W(*`&IhAHOL@y$RE6=^(p&GSjPziX(GRqVl< zWs+qDGP^{2BM1ORe*4XT9KFB)j?m++wvP0{vY2`Ge&aH_K;bF{ zL%r>}<@$xnk#(go<_Hd1RsAsXM9zfwQ@ph98ZpkiyH=q)YEP|@e@i2A5b?}7eXu?E zov_%RgBOSPsnOCfB5L)8UOp{K9i`ppA?YPB{<`y1;YRZu;VhS zc$@PE_d=8Zwp$?w4p0BkybuZSKVagXrGDcXZifKWnmN)qjSq04mf zo($d^ozJNn$Gv_{=u3wkcM4EV%i?62;}`8n-kuivkXKD_*Ogl~ZA&;)`BNZ4C$qps zlWfod824tQ%EhpSC(~Ixhyg38k`s9LsDQKWc2ipMv(LA!1Z@+BL_+IYjVGEF%eC3IPK@LM;LP-q->cg)Jx`55S ziZt)VaeS~kc=K&+%#si4Ib%{0&JM9A?X*M=iw_&MN1_ABS3exO zt}^%!HYFP1$$n`_cuL#)oK8-U^Bf923au6Z8Nb_NeRo~y)JkG3cbrdk)q}oWHBITv zecn-&H69}loXP-@sUf{OLy){~>EPvsb$b47Uvs4a7x-~_(j-1mH+aCUqJ;fF`B-!IiS5KnsDzkR;(eDA(kA@oq={3;L|1g7`V{CU01LH zucOegoc~B--)ZNqy=Ig6l=@>#d+{sS9#8uhY5$RHoe&koT^!UTZ$D&3WCZ%qmv1-v zqOp&r^lSn- zf;~T5C0cJZ0gfQzuy^axoE3-kkg=mV-`ev~bk&AuIiccd-u5(Dy4uz(^IPYo0oBMPLKRJ+zXWmX9;b8^CxQQPyBQABQSl^X za!JZw@_ztFK)ApAFU0LW#YU#1eFqbfQw|7mL}l*KlmG*uOcwlrWDn9?YeV7icffG| zA&6C)2eSXYW^j8F{-IDVN!E=?ld8x>NPa7AL-SW^b{~jc*jwW5JeYo>!Fz(Yy zJYBKcm~{EgVQZv-io_xDO9repca=bGSqF#$cJ579gbeG9a z-+OR$f$vfTOrnp>7_S_bJvcGv5Eb>oLqOqzk*km`5eZ1Aw;};0{K*vq5*g9n+i$@S zj!G}Ky}*=O5}aPm#d{r@sUXix59IM^Abs7kAf5og2F^V4s4uBp_ZB!C>dI@-nWF5T ze;R2u=nq_?qFS1ujp_6uwkc}@Rqf-qn@AjC$LIQv)$;a=Le>+^evgd2v$4XB`2Z; zJ<*@?&M9EcQNmO(8%s-}Oy+$|hR-BqBf?$S>e|#)sD$oRAOtrRrt%RrvrX{|0PV(71ws5pSaW2{K$)+0Typ@V%ZoB$@1GBDI*(YA9*PbFo9d9^=pya^sye*5B@ z!9z0t6V>jn#WJ43q7dy;tg~3b$wC(wF@Gc0EPU^bC7n zb5LLGeLbWO*iFFpNI4D2^D{Y^6%t_mZVR$PPTNa(2j!H9^U;6CrD|==ojVk8K+XJ8 zn@{7vT_#|=xe4pBmJb%BjNX)K|A*NA;p@&-aLyc>c>9SUh;z!_#4vVvOC6{xBVZA` zNtGZo*-d^}qt$K@BoKKTuerf7+P(YpgnYn#ADod_UM3}S@;JmzEt#cXx)sO1d&o9R z3fsEDRLh@96auT`Btx5^ZU>`Uq{%g}io^k-MuT*0JOHU5=pM>&&csjg)88lUFO_3x zj$|P}+Kg02Yo`UVs!7uH7IM%iHg5e3KG+OwzwuLdFH0ty>pPXBCN~i+jO3IJ18R_y zTKKF$A1OTisE|hXO1G%$90$UynzuT#(Zw)s`~!t0(?8tQ0)FiuKyw@NR@vWGG}qfP zGO-RZOE4;C+8bTTL(I37*f$iNkTDj@Ai=~W_hEQy#0iuumF1^}LUdp8Wil$KpQvC= zl(PeIDXD=slG%BZ;$gnCR~vuh9PqJHq3rAbxj8VPn<>NcCu1%L5Y6m3$L(3Y{$7b; zSWsjznl~$WxfVP6;gp-}+$<DR*EMHjd)3z`3zmwn!!cWJMHB%bk49=O}Tyj&Pe8g%OF0JixNLrf>vu zYLDchn6HaIQ>U>2PkiR%Kq2!0qKKoM!Qe}$wDBneCe$av^_6x7xcUJtMQ1EQJa!5! zA#;Y_?$ZahJmg9ojE&d~XVuN13*Ik3#0w_Bnj3vJY9jy#L?9slr<#QfsHwlxra+@o zcmy}R1Mto$%o%R1#~a|FO^p&~TdDb%) zw{<`!I(|T2=xBiRAUXU>A$V@^WeTE@V54m&CD}F?mS)RkM*#hzS+Q%!_Xy=B_&91H zxX=3PVO1!M`4GOM?*olA&obo+($BNA$^Y*5z{z=;BjCK=g zS48ZIzC|FR>=>URL8n~AgUGg;+5S#|+At_a3d3;WX29)vTY5XONse>!#bjYGCzLNB z6w8ZIAt6$qG*F!W69A-Z@JLP=j8B7eNLF2OC`D%*nxL^%lGlru%>)WvOSwYBw*_Nb zJ>6RwzA~!jQ?zV*GFXsW(TlWugt1@wq#8jxQMLp`Tu- zbn^Vs*c=}mwIJtclSe;>Fb z8sCFkWP3L5azb1ybpg@rreE^Q(ED>5wQL;nv$XTs4fPJc6qd{FYggf?QNvsz$Mlb%6;tM>;K} zB#je;4jVz>{C3D3+34_tmiS*$=^a7$39q(uu7062H=5mPS#gOL|^@kXny_n zG-*3)mDK4qj4>#APo>6_^ie~)N^7DXwZS4F5y3QYZ8ez>X}*u&m#z$! zMR2%RHdwsxB_X*YL!%knsvcefmJw&Z^UE6d?F-}GcuDg}cHpgfuj47Qw+6bsz3A$} z(tLZk>$U;bV{pst*Z#NiIq$ply6MRWJOGe+S5iF9$-|u$dC8e86`~&Ukq5HkLeMyGYby(!KgeDE1w* zYqRN;<^H@mr9ewuj6{&a+w`bWL5Z16#{V(rn9cPQ_=7lEzYY8Pk8xuLq>5Tth%^cv1es5|DxCg%IL_n!B_&-qpyr$sq%5DLBCG2DP z8sFPhNBO7d7n_20x!g}@xAw-0o4O{r0xl~aJ5Eq-I0zSLF2?49YONXXBhL7+=N+w7 zW7;Gf0}=H$qv*|CyIPAmuID>}4A`J7mE%HqqrFV`c!YY78#UKtKGsGzC4k8dVT)K7 zCNi=9)0e~%x%e>+XL5Re(E7!L@5+_`Ux`l0mfW9;);8bcggW#8cpqFX6q7?oATDCE zfn4)P;U3)9E9c?zc2nfdPu>}dYr2#tSLjuh8E`ljb?j5s<7+W(h+o@yHZsYq4`0ar z$U#{hL)W~YYBjSm=Fyx_O@Fbv`jL`n$zk3#YC7BqRys6=Xr11|aJ zPd)Qbg+ve{%wl~h>f>Yuody76l(;OS)ORB!BJaHezcmlLYkR(j265XAU=r*psT$D4 zdUUN7(^WqzSEV#VW8`*|uwnr=PA0%gg7#IP6HiE` zw$YBDG{@hxt4(;oo;)rdZc{QonTM#F5oVG#-yaz|TcEtFuEadNWslj(2skMD3Q{-? zFNcW**lxseY`l2_+OePb7~tfzOXzcatpx10XzneWMZe)P4V*MIn#^L;F(z%{fRLIrfDXkEHMI|-^b?`@_ zVUjs^q?~9RNYS@m&1*QanKC>NOV*$#?;UEFRU8Q+PYU#hb>B0x@j6`}6D|_+xX~eR z`v)hm3Db3wsot^P*7OqBOBk18Ep`*-#DA^IwdiOYp1Nj zb@xtAc*Y?5cI46^y?Se85-uH`9yIphTMsu`6!dhH=x6HQxLR&JIXO`=8X|(h08Z|1^WR#?;rIBN+z?vKz9l$nt>K7D^`=rho{;70t8yRVi6OAq z`x5nB90w@wd8zq29;e<05^3kB^ak4pKh8wwiWP z3(?6zdEayaGC{bdfcy|aF)i0sA36Fmc%4QT6PLr`yL&K6i*={+M1T^hm6_2=Vb{0&DmZg0(?1(qbY}o&~Mc76y{Di+REnT1@nz+P% zR5Lr$2FbMvdnlhrB21XL+9U#wx8Vhf3T`}iD7uB821A|CTHmFDqUd<1+++w>PdrG^ zs(h@+-2$Lw21_-}bhJ>hm%DOzy4ozPA;^&&2N!@Enz;UyGr1M!{X?!7#@(skx{H@$#=wNp1!UIsR{;6v3~yp58plwf7C zoeFKW!@l%iU!ASWZPUFA0tAcm-$F$dv_Ak-I6ROq7|ft1Kit^z)aCOSLmJ~E*&$Yw z*|DXR9#YjL5dT_#`8q~b!CbsOD^g71)F(c-v@5_*qyujAT9}m0)w(Go27rgCIb%9 ze?+F?|M0yw@UWm5k_6SPUyCDhK~Tw({7zRFUpBRc@5T=(oB6tQd!4t+gs+mI3TF7e zrDaK#^XT{4$j&?4pKU7coH;-ura(+%TR5bOm0@jPqn@sqlDHP<{Gc&A;0_$m6n1SC z6h8AE(^E14Y8_3!IS2Gq8;S~cjV-6%^c*HMf@#_04PR6*11fD%?A#}j{BYB#sso-2 z1LPLx$e#P($Np)+f!;htmrMJX*SQjFe-S=cS$Dx>Y~qCx-6mbAz8swo;r!nbRCPqj zD(?!_L;$ZD+}DxW-4UHpQvM2M0{+Cd7rw#l761p{#qBGKb2!N_`WwCBQ!=!Tn`DCVDAeA(;+!b5V#c-kz|< zz#Z3;Ws8 z?)U0onO&jK~|F)WC9+rE*va0f^<9(w+}cV^(~3Dg=nRjarZM)iUHe%i$MP|V8NzVp>! zR}TWL<>liK=k?=x<%lL8DfI2HP^OR1>i}%KYwi4f4LZ|GBT;5kD;hJgy4>0wa%LIE zeEc0_ZvZ*lNUM7eOfj$1LwULz%$Cj9YoM$AVGJ;HO@#el&}R41$UoQSm-a%E=Ll{k zsjz5dd?H6r+H_gj`6x{xTx*mV1@O~h=(b5`rVSZ;rHqTn1`=T96{qn}Ydz*aN<^;I_>vqgDeOKG|*63YBQE;{L?67N?f z^^s0hi{l(-y6J{OH5QRcjpy*PozSSyg40ZA+u?%-UL{}GUIIA1=_{NpaDFS2f z16tHU+qsj34q{#~mR)JpsJQrjJ1f-? zj^6Rv-fDri&lm`X_YIGmdUi_%Zd0xPCwJ>ucT2 zlj^{A7iS**+3yd=1>F)tZs!x!d(d%IlpGi)cm`~WVubDaqsG$%RZk>OOL&P?2wzJM zR<-5eLF87=_OZcp7zSMcSVlM2XUcWrAY#947cik$RIIr)i$SXhEk~YSap2wOdHM;1 zO8+|hAp{AE+^6xdfxcq3;{#^YP8-X}hmYGjbR`yO`ceyy+LixoV;hK_5m^ji z4dINKI=!ZzZUJciuqv@MWlhoA@-g-2SC`rvH##1WX`Fyd+sec=0y496PIF_cwAh}W z8DE0Q$k$z7)sR37M#0C*+oIMVc=-6;55uJoII= zTTwiip9o>#3#5&hH{D**?>(l31gT1g`|v91ADs~#(~))J6xnIUtR-q)yMDfnZeg3Y zvHG_*E7f|q@@jfRK5V$=43y*+@q7mx`A4Gf*$LwV%6)fMefN45`Xwkvo|0Gs1{u&hbYKJF5jzGE)2M}q(ukr zE@+Z;B5KuzpHc=|?fhcloRiImP8jvO^>=XC11gssNcU&-E0vepilx#`??8T;zod>w zGE|Z^5+@HQ7lf!}!3)4Cn9awFRhApog&oFs_a2$_I!r^oFgyH*k^&(p5X~Ekx8eVW z(=nmD;-`oK$uwl9n8uxIp;3BR_%;Om<{BbrjgGvWbd~E<0ddnm-B_T5*sNw*A9>s| zy1&@9L>@HZdF=)U!2xHEigK``fbXG#_XW(0sBR94wU!R#@}7&pB@>Ra82_SRz1n^7 z%k^4^QoSPi%z8ye;!U@su!-W*7&xWvd_y+h5L~(kUxfWOZ)VI_=(766Z?@+|b$RWt zK!?O~1kO2}o~{_P%c67(Hyp6*H*>zKglwfBAFQQ*Wh7#rGoEtNtDa?A@*)a z-{d{3r#w#8WEkX7rkCDNYM!sI zZOa(9?=|8KUIlP(K-sC3__jk22?|@!QP;@t3*K;d=}@nJ9`JF8?wqjW%`rrMiTV|UCFt}Cxv};5!q)P0!65SgGK^FMeIlH zm~0XaBbvZpK&@X4?Sxtv%C{@jR`GX(kWn*1*Jg<^AD%DZaiM|7Zsdg?l6(-v$36yR zV2PBMK4?^&rky-V5)5LtKVw|QfF-8e>Q=tnXBu~ZuJcOYP*2}CN!Ji15&{ReKE19& zuBdX%rpScm@()@#gVfX6N9ZS~a$$5sZHAD!323UqI9U;6%-Q;C$a1P!Z;fna-nUv_6urScqCqiD*kmx-4`h|dIZKBLs|QT=M#$49A#po3`hD-0 zPV)1(k}K>`6|O$fYK3Q@u!lbP(*sZ^i}DEUokk9A#PX*+)vb5%T27%>mSqD(iZ&O< zjT>+{lex1~uNTjT@1Ln@QI1~BnDX@f(TR`@$VVY4cqJW&UI|@Oo0by%20JluIW+-n z*mZQH@xN^jn%BNl_i+;URwXkA!Vc>g*}@vx=?{mb6)_FF1-rN;7pm=uDF~0bcPHtW zsf}79-kVHJyPFJ3(o0)TEaE=NbFXK95RtoLzG`fSc`)~Ys<+uJbFQAk+sbOj`IHhC zdT&sSIK-QS3J$apGbs+PjkwRk)eQ{`0m#fJWi-z8H8^!>L@D((U6(Md`?u}EG+}+2 zpOr4+kk#M-{6`cXuPQ%L-~tg)enVc#+!(JA`gsJgY@*7~InIe8E42F?E5Y<(lXV4H zU~*oBk)imU9a*Mr<~p>t9yh!_>`H+^Ch2L@8v+(?+6!11YM(q}9)iqA$F<~`1g+1m zl^y@Cp0WfquACN|b7-(B_IlHYJ2pjx?fPJ7}7K?zJzQ zK?f{Dgvg0Tt%~6Tt$+hPa-hpwH7MbSB0T@1j^WGDu#+b^TGJ|GL|D{1WqslS~UX+Lm#vERP%yb6Drq zQkXWZ81-A2sk8(Ah$|u-nC@vg$!=iVv;~jWh6K)cwMj(^q zxCxCxzsDau&&gYe<3eGQ>#k7h{QccGFj>a=%qTGCr=;|rkxdTec4qhSL30|C-9)Vj z<-Q2~(~Wv7UCh3gYc6FKSthk@dwE7m!<<``#+Yl_yXhi`n#iV=6{B_LoXT&n~L+jFDv!BQ6?9Q7~q<*olQ`JnNDz9CHgRjJgq1r>@i zv0q_5VI(a4;4d8Mbt>L`2%hftw3`t=u6crzqRCg1g*8T%FjW_iniX(m}}`JFx?|Zr$flOrTyYiOhfWTX@~p5Qv8C3l5TJz zw7Mk!T{Zz}JL1K8A6WDHvY~N%UYNP$FOb-c9y9fWECI7-zL_tum`jTE!NKhH77PE` zFn%2^ME_iP|8-1@iI^wtOC~{WyNyQpW0Kk9T;o`(->0%m@wrVNDZ6*+#j|)!0?o7b z8BWhXL^!rsEAp%PK^_Br*@?fcd>m4Kw<22CaFGpN2hsERU*2s(yr>&$0Gv=phnT>d z`LQlwn5kWlW0r9`e}U2X!XVHBYH_APJ-FT zcgCfTRo@l-(TqIvZLiSZN1fqIQL&%AjXCR#+f4PDe1>M=h@gi{^Ea7Hms?9rBt3uh z71h#@Q=r>TT5qsB_2$V zcntVv-rB-ab=@mzS;u&|U?c-7S7=TAn@MQY5VmggZ*yAI>*0Mj|A|05*8>-fcj8btaYX{*wy3dAQAY@14y#9xGD>~*b{*uvkg{s`>QjmES{n&!Vdn1;g~XmmZu8Lx71(eo-Q!wME^Limy@IwvWo9z!=&;t>pb&-=^Ot9L=Ojm&hMHAM^hRH@ohYxriOGA!--_IdS3jwF`>)!vSCCb$Y+!6 zcty&mul(}7?!gAu{ZdPb6qH_T>xhU68_r!{grjpWSya%Y<+Od*TNSG{p=qW{kcoyT;lRWT!5r>w zBmt*Ha|fz&`6mxWtW7?5C9pFxFwUNqj+MXe8akbT?lssLs@owJOZ;{ z!pIGq!uz`q0paTx^3VES_U29LNPEIrU^7;E+3csHC#y7#2Xs_zI#v)Mt! zd7@t}8K9=&!c`f&743;d z89nVRW9)7$A`KM8YeD770)oR>ewz%A>~}>90a+2HCttOU&xpn3R)i3TUF{_SYo#$> zR`wX0pEU|NnJOFWLri5`NvF*ny$GrPz33D5U)Y4*z{^sHBGO4iJ#M8tzsb_!f1_Lece{A zas6q;8tAc@rOtr$5mY;(iQ!$x^jqzfb}np;gH_=|H&EbKhD&N-I8aCy%6XsIM( zhIyI?mX>->685!wuPU~EdJOYnY`^n7s43Yr!3iX3s2)e{v4>fnN_rG27ExYTu=_|L z0)_(y6K^)+{3_E7q6fpKNjL-GGrqF^ z>!yfdhthiP?f-;fQ)@x^)`~^rgJ3aA;}N+^87V4=QlT>@&h71Eeg+1bj&J9R)m86C zXx?JS@7~s{bDit@WLPHhlvKKhS@RWDu)+okD&TscG#aGsmRdK~T7~84u8*^X6Q`YO_^gp zf~!+2(PJ@qkiXw3rhW7V?Fi}b9=z#BX!@&)5xkD54^o({&fSn`l@t#1skuLB@x zl^M0n_4v64y2%9@7k5}i&LPS$kZceX!JK%=gAjM{N$|Ly%)qS1em<3)ZJu*nA#VR% z{1N;v!*g&9zMccXm(i&CM^>}&m1UPPJ7g=At04&InbH^u&KG{*x6m_?9=+vv70Fx} z)s+HZQiSrt*dx08D2gfXuNeFq7}G$&`dX(C1$RgkQ$ei!(`M4MSUGbhnt^+S07CNT zEtz=+xWFGUBtS;J{I*%YI82P;da^N{8ICY^P%V|=58};WGL)Vff@uW`3^{&Rd?$By zQPi|$1YyKCZ5+{G17R!HqRk~2DuR)lX;0m@DVgTyz)O0}4AC*3g6B&MX(|bEq~b{~ zDUQ!%<7dg~Gu7A&$$5#+lH1oJWe(osZRVYZF{p)^pzwGqlYmrp>_-cM?~c`qci5(osX z_AAT_C*xZ1*1t?cDEhuqUjO_k5pU=io`w8DGc=T)z8i4N*9bvRzskfaZ0S<$y1vC> zM09bnHP^BfHA>Fb&nTaCegY~YNBbJ{{10)8`bM5m?CD)1+ypiz!_$ z172&d4~NCDX~HWt#omk)n2Lq;Gdf<@k@Rx4VKbUp_T3_`--T8E`f!dCA-SJe;Rt^4 zZ9@U$DiG=dx)b4~tNs|W0o?#Ew`Dk-VhC$`p(k(;*Z9f64x@RbJGm4lQ;;O3;Wv{( z*z7X5)Q4mRHENM4Op&HkX*txA4LY^q>!V^~)o_^fD@GJ<2e*W|=V@;ZL-kG3J|5-R zW-^&y)bfr9ne{q1cPS7zZlJo4vBtF%=?6)?%!AEWx1QrcvARDkwYYN{caa~YwPV5c znDMyO0h3}LK#88C(ux|tIr{6nmrWwM%ss}h6GSv+Sk;dKkY@Usp5q7xXoD->O zk6>wO&gRpo&@#WxDJ_T*fF!Iz8U4w`zPR4$AY@%%mpKd1kId?901BHyEAe}BSo%x{ zr(otAgTzt{*+=FiOC{pbxIHr*WYxIB0C__`*i>4w}6KjP4%1T*6mKvYEO3OOy2 zNJdMEFog?GVE!*Lm5A#q0BEP}M$bG}^-{5kWMpBZ-z)TLdAj^PkQ%T(IB7=z2yPRe z3HRn1yRcT47T3@P*b#<+0002`_(YRz3S&VgvU)S*EUd zPkYyXF!M{quxTZkZvb@551NpwD}L%aC0pcj#k~&ef2stfU$94De@{;n;l46K_6|DG z39^w3qz*6<=rl-sJ{<~~+0r3qO0J4PS$=3!J^iet#V%3>v=YMViyAI|!3D^{Ze8ue zQISNe>wMXr6i)=)%lwu}S5`;TvL0t7=asGs5C8xG0014II(hx9+9MA5RJfW)Ga5q( z#zakO1r6!1Wp79eaf1;KsP2L=sfzw3DbuO4+_4RyNxL{z17y40Jq>9NdtWnsR6YuF zGX>3jT@x)?=1N}4J7d{??XLmda-|LNH0_BJF=;YfzuMzK^AZLxrtig40dBO2!@#md z-0)PTXkyfwWLL;zz<6Mt4)tCcIVz(JCLsM^Wq~P<4&wH?$V!N%-i%|eVLdD%1>&TG zjpJ%ZCCPxTKC>0n3q18QuS;??BhWrK3p^xJv#169u(Y&>(H^S>@Lal_zk*xRugD0} zejr@eRC=gY8<6S#)f*7kF2qT|3$8vM!rU?1gV^ddPnE50gFcSSOD|}FP|97d)(YBh zAOBgJl3Hjw+m#Q zU-si#)omG?h0lZjK78yplg|eO1uW7-PM;JqETpaA;+${*_{)sy4%p zeE)XTxSlzm>EW6cDs^|Io(t$AK$)})O^_c>C7ffGPi4&EyNottt_Lju+3DTVHTP33 zcEYitH^~7>#X4-9qaNY;>AY-Md@`*Zc5P6&q3pF;BHqZshBkhIf$dSanHvw6L|o4z z>w;l&rZ2?iz}tzq{@7`|UEu%ypHv})9l}g1np1N+*wJvRSBUrog8jjl&syF89=dLb zbBr9)bc>y9H7}7N>QAk=R#`n#~ z)I;?u-UU{o?>N5{;B{)n?nKK3AR6kJJ@_xOS-{}*iQ)`RyzhR&kV~Y3tb|j3cc)yu zx@rpE0~q`3<$({Q#FECjJlYh_&e&!FyYXni9cn4w~UIQZ58d#o-~Y^IWLbhzB|ZTL;J}9_fnr{(j4Vy^8$eSdd^BbZ2=Y z{I!u6`<>?pJ*-2r>dZRcH@KtFJFJ_svf0>tGMTKEtRX5Sn72(DF+YTPqkhzEGP`lB z4O_sqK;dp1B;y7y(#s*$ge=mr6q$Hs?iob=NRp-4wdugGGAm1`>P+8wW22V!TgfR= z;KzQip1EeQFNM-8;CD?=&?YYF(UwYw(IY;#jG@izVOuR#I0)WpqpsX7cN^s2zSTM& zZHdXBTq}DFt1}H^+YT&T5tLn9XWfgCN*1D+%@z%y!x35n>sI+fbX@;?mQ7eE0p)j~ zJ}E$Tm|s=bw9eXxafHF3?i|TA(^^#&sGv8#)^eRhTRqf^+|HUFZA+Akpbg7au8<&S zd+uGFbpGu_(?D^^nJmXGeD{1a$HJCGK4@&o+Y1F$Fk^lHy>qr4;y}n!vj@lVA=dXT zhU`(;HO(nI{?1FL|ajCgVgjDExs+f7@R zwa2`zMDm+B?j68l+8e9nC7XxVj~(RyuXBg*wxQJh|E~^xF6NTu{7#mKfLIwgpYri_ zqb8nDhpT#Lc>DRXJ+p0tt)5&R7!emMfNDZ-zJlvCH zw-j-6bbNufeHF8uDpV-^J69dN^i#nf0zJS0VN3#uPEY}nIUC#@If?>&@GaHJptETT zBbb@?8k^{N_O6sxE>1xB?$9p;D@8e;BAGijZ3B$3A2Mc2OWsLb+u}>Hp#$cDVi7{t zD$-V-ENhW<5U`wSilPhQ@uo6usIuQAB8$2Uv0h8Q8muES(b4k+9=7Pj!=d9HXV^v? zL#ftT3~t#&Sd$vy=Y2~IvADU19HyK->a&H2E`{L$)LbYRf^yXm={ep>ckXnv!B&b} zT}3Y9@&}_G;7+!=IXOOm(x~lt9SGlPFCcK%g;5L0%m549kU1yTn3e9^IQNK`5blxC z{1Pr0fY%sgKO9vcs>IuR_a8ZkKFPr_KP zR8#~LGQ<@`+XdP<1G@5mpk&9BG7}vzfMFz^HQie~6k9=(^-^qpjsr?yrt7&B=mX(Y zZ6p9j-_zO7RGmld8uTA~V`4;Y8<@X8s`K30;LMTR?L}VD55URM;Y33*XD^O*Pr~@b zhPao=c#p6FTKfqOF(awUo)>KY50ijZThXV&KI1N0K1rVHc_xOK z?O9V}MY56=W|GO^8zcYuh`4n_M}xmnkY~@OW(1QFRF$c5qhAev2rbte&oU73eT>9V zWNU3w+68#}O_0c|*Z`N@T}QR=t|DM0Kpv0)3nEyzbg3FDqBy5f2Z}W`iBbo(BqPt| z9~APKf(ENJsvS!iAdj2ga{trQPAEjmwP9V>AFv@s)o%VAMD_z+YBo?EN0oI`{A16; z5bf>^oMr_sm}hB5zE9&pQ1@;kV@wtVA~EJ|dmgd{3z|yiRThANaoGq?9HSTCmh)=> z>{1p3c>n-x(X}SF${HdMXnhr1^B5LuS4HW@ae(??1_2Wr>yD?Nb~{`Ff%K$NkI;z(rPt%5_4t_sIC(lY^Tx$B8pcIdQhpKn#DB z*R!@p{3tb3ujZE(?j!sKfEicKN+u_s3W4*^gxpEBXde20Z(ehA$G;VRs^;Lhq+81p zS7aro309WQFgf5oy-BQX6hQcW8w?@#-7204vUML z^W-AxNBkm%Ma+?1@$xtPkCmv!H|%b$I1@xoGYhp#98=EP(VQ-(3a$Vr&xfI*H66$I zaj6pv+=z|%1t&RaH%7!t6jW2wf#B_kpsQ2yI-j&Ywqe3@T@vm$cvy;6@*S?pK$#;0 zr9lez#i#hK14)E-FUv8p_w{~Y2+`r~6qf6@dO)=$NE;Z1Ms!cK6gEzfqdQM;4CgL( zl<-x>6Lu0SJ+tggVIu197_*!_R=mkEpdXRQvJP+kcxBgLStp5>4aK1{sYf$aTg11xa(Cb%(ykZX118Z)O*Jwr8SYZ1U*I{c%amjN`zy|=P`Wyyw8X@k+5ophWdTVU0U=YqTp7~D6+LtbcjnVnF$tRuU9o&%Oypy>7e#_-eR(v=5Af_#bvX*-=3Ke4$fMj++4TQrs;JrAJ?EHN;vh)Sm@(>Wtt}0_vPP z=(~eP+tC8>sNmF$eU{6$7X2}pcPXs-3}oJt`Lpio&hb~%0TVTbC`D4Y5GtRwxH^Pk z`AJ7zCGW&1JVXcQF0~@}uX?1T*D4?e(8soYqHGDCnWey`#ulALxuwv z$wE`%kp@`oaeLPcxqzDJ zP1owX-1u5tn|t@nhz`z9aU_Dnyc~A^Ix#B8b4_1QLJwnYwvvlBJe8zDX5KEFN@k3L0Q>Q@*7o12-G&LM&Q%_C-@7Eg!(M;p%rWHoF_-;m?C&k!X@vTTfz>+^ zOn!YzXGt8Z6PmoSj;RhFdpP6n+sg3V_j7Vd$nN@?1(f=CGe~A)mG~65FCgATj1RGB zE38b`f7VaxYGLwb8{A=-R|!2Cw31pRMfE_O z!|Y`2jMJP>(1&|zP(_Yze^Y-WnK-h}w*oQ)(0r5g4Wc`1;KJ|#jDAHB&Vt(LORO9T zw5X=UIytphxrZB~-Jq{B1jg!Ou}BegW0}}l48(wO=-_-&G-g)=<7Q>MXpMM8Jt#aZ z2l+S*TiGFR_!F1RlK?D73v(t}8%#2;=<9BXBccZ0x^;i4z)hH&K6m62j25zA;-o(f zq=B90LLN%U?AvhN^9q_R4x7U1u*ZV5W8Zv1+Gn^|K(-eiWJ$nnD4#aS#Mm=L$fD~gfe*@Ua6nB>lg-*v?v zd7(L9+5fz0IV#(Pu#!==K|2@Zr!ZY^WlFLBxq;76nrS_WX|ixtLpZr7i7IMb#hv`N zCeA2nHENqHE=Ya_lns7QV9e%x)BSloA-X}x_)oHNjWBs*-(xdLZr*czUseHmkZJ_6 zFrW)dn1o_z(j|JS1p7Bvs2_zC1{NU2vkOz8H+^;?P-mvT+VRzw<)_IFE_>7sD#`d- zVx(x6YNk#su?$M(^7%22P{C{TL|=19_Uh^PPobCob(;<1`B*f;afwMNOx@1AdwFB4 zLQ&K@m02sKh$ZSk+)oZJAsMJTRTSZ~`J_tsHzxN7w2r1w(*iR(bt%oh$-!7vHX&kP zx4q_aWpNfd?yBWDEs8OXPi}j0YCu=5q<$urD;=(*!?s#X?l|}?f`ExBdrc{TbmtBl zevLl}YSn`FK$<+%g@0kAqX-fvS z>;+~?;e9cGbGC224zv(% z?_2?Oj9*+HBYXPioYwj*sUkF4{wC)oQ+tb5^;_X#L}&nbK!?A|E{w-rQft|vkOOg4 z){j7+_k5KJ=NU)y@(?{1ZuZhkod<4)yavBE$jOlm8?T+bJ#K0j_IrxGAJ&WG7=sk8 z<;sU>kRFwGW3bnOyS9ZDj+;o${g0Ym_BsK<1`=o3EwF*{R50AWMKChqO6GK`DZW;z z976m*$H+)396zau2p4;g)|ySl0p!is=qWDBKv8yQ_-c9wJixnrz?gEV?Fa)+=t6(pX^Td=pGKKw6=kWAg{s{RFWr7k;wJhW84)8saf zEgSNJ-X*V`DGxF8du|z712|7x{=t?_#qe&#rQS3mY)2%MGXA^3djk8zEqTG{ba^)Q z1K=7k*$z#ZY!hN}GKkxl@9})Y#UPo`$G~;M}K9<0^H|zFa zue>!wq!vGov>28aYZbCbE>!U-^(8oRASO{M%5Jesoj9+Iq6B@Q8iuO!EQ=^z^Uq4DX^bTBzHCGs8wO7Z! ztj?xP6cTPHbHIK<|3^1G0=(unUXH*#LsPdLrPFvQ-f|4{!`PmbthUv42tat^Zh$zhaGPiL9#$P3Y)wsu-W~Q{^H*`qBtY zH(Tk#Vd@u1ygqaojAeo(^cw*XT-?qdk2QF`ia!%Jy-!-o3l>}0ZqO8m7AX!)X)=KR zn;Hh06_ywQ^=95^s>{UuPTU>E$VA*~qi|ehbLUYmtIUrhw5E7tXPl(jW0k$YiMaX5 z06p{r0iUst1>3gEyyKZEiRWhkSzSI~f`gjkYA$L@ zp%PeLx``FL9o^A4c1+N(HCzG6LH`#S$968>;mDGLJA4h@95J_b+IsuPh%I@DfqL_0 zU`w-({I;teEd;Xn8}qaS@c!K*=%eYQmib_RWw2c@cF+-!;gZO@oi&FI`j1_Tt5Vso zLl_bt0{iBg54D5^0LG>Schlbi9r!ohk6m%OZ_4$OO$M8pPUkaJmw$=k=`^*G>r?8g zrh=f7##mC4N_UOaxNvhEx5X_T~D3-}DjJt}>G;)Zyf+ucdM?VU2Vnx7|VY z=Sp!$kP&?bK8KNH8{=?G_lfTBwoPl+8?XcAJrqdCQU z#Sbqe6cCvPmG~7ra7E|!M<{VQUG_ycSHS12dR))e;^sl1HG)K3!H1l~hTix6(_MAU zdd^*jRqYUl)3xKd4|dB1^dfml>$Xo_tu6ucHX=@In3<0WPw%fhFAch zglc%Vi;osdTwl!SdHmBR40P243gYa~2iQUT!wN`(6;k*Q|Ky;xsQ4SmxUHW4IGE1J zi_ma9A(Ct^P}x!zgMim!I-4_yRWqFsxzq0X6gDpBwd^`=Prb0vbDg$bUF20RefdU% zq*AeL9#+DXaOJBTRZp1wj&t{%58#vGW?k+*uQkVOo0$(&S6`x|Z^MJB?qh7+^UPC- zNr=-v3MI$BLDG6hA&`BrT=3PKsn$Tw9(+c22lz|`<7%wbT-l)}rK1iVca9R(CSOBk z?n6(*p%UHCT`=5ge+)5V)^Hd8{7ImoJs81s7SW5C)XhZc)~QGm^>}B1l@f+(qPm)7 zBgJDVHNFFYv>!#u>S6Y{wKoeZLjJ=7xu866{^7<0kXT1vQjXEqD}mg2=rks3l1^7m z$?onZ$gyu%3B9JXf_m2A%tfhQvif3vA#{u`vN z10-RY8c)j7Z_P9qR{paQ^rckrMX&wHnF``}o)C0c20B~m?p>YV5mzkDwe@HzWWlwB zuG($5wI_~fJ2Fpc5Lth;270QPngDdQ_xMEb$~bXK z??QQKph2h7{5*08V#Q*aed#{ z9_-fgU8?Rs-Of_Z1-mM8>S11W`waCY0h2PRVerMpC z`xuQg>3~8XjsYae9ug!cU<{}b)Yz{<>kv^9L{#gk)izBq%Y{)85e^Si6>^M`CA>i4 zn+gc38D*wm#)wRxRE$#oFzUai4 z1n&EAR5W5J^FqnUo(|?`#J!q4oh2JE#~}8TlC; zbW6c(es%A#{+%zPUZ6-O$+xa%wep@w3ICd7`abw9jc#sImyg2ahfvtth zsO?MlGWzbU8bMb=BdE>VP%;GhR!?(JJ89UuDk9heRq%^d4}IpGCcmeFQD;Ci+71x& zz4%qC<@Gowpdj@4 zQR(`pPnAlfpxUUU`qs3JP~W11p19rVw+eG>Ei3-r?qC;DRj0$N8E$Nq5Q}`Qoe$fxlRl{6W|F1l!Mc|e05=2q&Jt)5c|ow zB4)pTH)%RPeBDSV<3AW=LFxhHrVCcU6zax;NaBD6y`strrk#o)U0n+ykT#Uotfp~G z-L}(S(M~>P`9tln&6aYdtTjHZs;C-ii2>6=+m$3*m${z-TJRl8GcGrnuZwl!6nc=W zMlQg%zYiboitVOE?MSUSDz)0&u%;e>5(soHcOjcz^rcI5mE@pV9SiS-nHOq7&B8Lf zu)v?U0>rka)=sj`G8(Qi%v4YSk>M$;lD=YG_-L1*z3xG|1WDQL9?DL{i_WdT=2q4# zg@}4m^ulVv+M3%AOau=2VP{8lig+s{mrG+`8#|;r7M59<7h8S(;e@gXk09sqCp zrjL!ZEeeG|g#n^xpNavSyA7u3uV#|1&8$jApy6F3AfNhLzCt-1Lliq;G&nJ^~G`Ir^iz&&JWZ8Fq#3_~IWAh;> zV*uBVp`WKH#mY?ETrZANH44%&LE~SMFz_DoG{yn(g7anc;Z}CoVNW?I<%y-{trlV4 zMayHXoV##~yAV-@+4n0T00iu{SR{K0mdZ&qs()#>!EY}?ji~~KiIPZp9JF9)TFS5+ zg-ctfGUY1M=AuJ&&ufIjTW5fCAIg}!y( z#>^-ecd1sDr~d#&Y|hAmB^PJ>I|DU>4e5#~flVBoC>QF7G81l~1`44y z0H^}k^2nCO$^bZ%UU#mgXdK;y(_sM`=ox;NVA?M{Uv&+0DpiU$Uzk*vrrzku^?lnw zm%Wi%jY@C=JTLqXv|Cr|Ao-dwt$+`0G01J#+Fw-+`>iGVF#PF1A8c|S&+jxw3{N0s zFUx1a^Fe-o!VC(h=$}f;5Mnj0V~`Wu|rs((%pgeXETmHlVX`^|&OGH=s*iKxl=sGJn`?ty!8KQtLr||2W>O(0Mq6 zjNMf#vwksRs@k{HnX0TDO<`q#00000Y5aXsA@X7<=pOX>QEXL{+#jhV#Ig9baBuzf z;>D^YeH%r`8s^!@j;KTxHbtICynIK~agUCg0QTB6l2mFG#ubU6Soy$oK}I)7LI?mU z{D>@l&4)Vrv#+1pKOT;cq5yXN@m~EqUM=68X=#&cZe(u*P~b@a#d(o4eQ2fvmt@}D zktImNXOOEXzAc%Zpj|L=zKxvf(p-1mtKZC^S39L!@XMG+B+-nCX8tk5ZL#2@b6@;j zR=}WHCWiP5T0Z^gD;*+5cW~tn{DoB;D74c3rB2H|xX}c_6pQrd(&gjVj8d%yd9Ca5 zqad)&6+15S#stPplXacGZn?*rP*~)EL>JYtxR3mZ)P5e0fq_a)K#E4`bZcO(KiQ0LcP^qN5}DZawqj(--4d8t(LMTf)r-+d@g_3`Eev2T zoR*}+l}wYx08k`uU=&vBn-X6BahHU4#0a__5Hs%+hF*;0@SyRk%d^IQJ&Ym;Sq3kFAPNH;zIHX04 z_74h9DWokSw67j)CJPfo<^qu4b~?v4?+n}4ef4m4vyB0zb;j5?2~nOQB&kZ;RU5j= ztPs?4y$A$1SySwSoog^{8zWz4fKdF2`a`QDy@oG_@jtD6dl%iASB9Q;aB8Md@>V^b zH>--6daR_U0wLxm`d2+z?6R5?T!W`^(0I%=O5eh=5(vvQE$RG$x|*0sS!}7lUo#erAcNMhL;p3 z(E0kS`4NG1q$^O1dyAAL)g2DmWf&X*(JD$2a&nao@B_s=vBc!hQ1Zg`jdp!6b09dz zp4K+)OVoHMfK*(px;jPvJ;TdwRksyGMDjzJVPKp-`^(&&qY-i`b@;?WB-sc*=Y326wPyH7zsD)cs!OKr3jTZJNTlA=u*cC>`FEe3dtAP3y= zD>`s9Npd8jJ&oG~1GWnwMVLm9M||?F;-b<#(b7*?^4ccq0Rrw~& zB@9PcaJp~9b`q%wrfRD}8-<-w{moN5e#I)n4%1rgg(@DB^qOx_#?RdSQjJDSYJ%Lu z^_ODP|BDF-`&kT2lISsi#KiC$Lmf0F{9IFP$P{(W3epxwtj!!ou2KAlOHq59fZSxj z9d7gS*RuYo9!H+62XLH7?@YRi&@xy2000003aM(Xjs5^w*HpxU`Bc<8&UXJ*V>E&)Hua6SzHywkk>EL!(DJ&c-Y0j zNpo&qkeqB>lr<^ijsRBNtsSPST-Ai0;z|bZhaV4S@ZsB8l)2b88XieerQv;P2#cpQ z=ES*3QG1|yXT|3TOnXN|sQ->@=0EiSjX_wc5>PYzf&mFzQHqzDzb>yxPWud>DgQd| ze*Pb9PTJt}bPNUX+$>$OIk9xY_iykP?~@&?nTQ}vND8h--kRREd0}2WhR-PJY`R6z zZ+4<$$k#3+^J-?O5*b#YF85=og`Bdt;skd&`;jQZs2-lCM0oo^l|bnfy+;*FXZuJG zdHAFI(JBO#XVJ^iaUFF1<`Q4r&_*0wwwA5r`yPlTg*;hdDE+PD36pM;JMB1_eR& zdGC)0m7w-ms$4Fi1Eh{eqG4*;(k$_6F_a1!$+pt(%OU<o^4{7QJiA(+ zSO{H-*4)Ps7DARQOW!Os?Vf1mQhQnHz}sIp{xPz9}E>yDAC^IP0<#7jXyrO(b?3HF8 zNqLC+&$PlJ8S1xFVN|0)`b4D}8tBO@E0QikfieZj+ zeNoMY22Ye18k4WlJ%+H#ONVu`Rr=+{?y}GjZMx(aI{U_cIeDJCNTZ3WsRop=XT$k> zxuADpzFG5uF2W*QX#ZJo-|#*b%kUY{1g!RDJrgiLL5QaOIkw5?d0+8W)-SiV(IaP}E`rygNTS{R1L(wIvJk7XUPnhKZYR`_%KZ1dGxrIhpHgK5 z0|je#Ou{~1mIVBANr;n|C5ry?tU1)!y4B$NNE)U8!8!;|(b6_V6z#xz7++}#r&)=- zd0!-CMAO?pp{;2_CKaV@JYu1#`FOwW+i?PB zZjRk}l$TdngH?rrxQ}cU3wm)F7V@H{_5qB_18V->Zz(J(54gB3+Xl~-9!5!`003h_ zUlTEF#|o3AUAzd;wKHu>_$XAVG-wPlFwI0VYmI^2ahc$|)n)^i&Xjrajmy4{5PIT^ z_bSF9?8$F3m;FWVP3mLWVhq@6B7Q&9_|UJC>&$gsjhipVua3QwYXB{26yt)ay3*fBZ z#5o`Q@r08d`1<}%0PoC0%!hJzu+RhS-lt1vNm;B?@(KD?GdK`Jn_ptG3Ty_ zcW=A1u=^iU9d4$83hz(qY2-~|0>69C3kv-~xBcHY(f>9~lhsNLO6vLB9jn>Yl@%Zv zP$kYO+C+4LG~FR(Qd-6YM?sC6TA{YyRH>7B#?gfVYQ8TyXeFb_!d(wg8bab3q``Kk z)U34EtsX5Rt+#0m5Q6NMDE=L66=r@NTKy{N$&kWnylYs9A$U>g3G%&6q$jl@^WWNw zx*1RIbo<4yNY0~k;D~C`;&_N)J{oYA-7)JvP8SF4O+iPtlkwqN1>xAt<(-&EUH^RSz1NA?o_vr91Cu} z$~+a9E%Mpo!)J3Jxs6hDI(PoTJ$@Q^IL;-{g+<1>>_t>@`Kz3}Lo#k(LuaO(c!3z{ zK=jcD)O;187hn2}bg9QA0q}on3vo>LF%=P-nKFI?(_v8q>G_|s1Gy@s{PHp_K)3IE zYi3m)aPO1<(WZSQZ9TGYC9Ur9Wuz#ZZ`9F)0E}~i;(Z~5a*Sqo&t0UAOfLKO z=U)tcnG#%M(BQ)7zpE;(5!G=>NGa-pe~KrajO3Ey-uF>1{*}|(PgvqfM2M%Z!PFG zJck;dtYFQd&b!bhz*fKP#XUf3&Y3%%>9B=)G!dR1b5u{OM=PmN!e?~5fb3U=Jvcgs zhxSH&zmI9|r|kOvAKD9xGr@_HU_ym+m_<+0=V~22{}WU7=|v9m$zm}zgYDiVan9-E zd%knsLFS9;+$R3$gfa2`yavB*SDIc&3$Yf>_W&jQ_J)B%bD`unE4j;>9oR#KHpXlH z>%lZF0~_v(y?))#vT!MpadplL_Ar``PkMCiJu7Wvq}_(6!~@^goe!>A6Tn*9>4Pr& z41}*HWg9W#1UiFzsO6+U2g6<_|6rT<-xoCijUVgq+!#YzsIT#AQuM&9@=v4gHJ1B& zc#UY?M*q{V!li|gP7<3xRqF|e(K?IZ+>NmG+(Dq65X(`)ev!^U)J~gZ-TMbd!_PZu zjODTbheeP{6qMSQqDgr=Ko#!A%@jjI0`m*sx<{Rn;2FAU2oy1_kH9_3k`8`-RTG1| zq9NZV(=;Dy^KRVbU00V?ZP(qI>e@{0JDh%M$GkJ@fbp67>;wAJ-|l=Gg)!m@^@<}T z5xy?}gAPIj7)QKs#@=^u0000VC5-Kq5N9Uo>i|cGYkJNjGQ@zd5ME6@%3=k$u|`FtLytamD1OJiS{dCctj=f zZ)U0G2)0ZRyhGxlbG_1yw?ZW?sL-Dn*n{0L72Bv|i5)!z2Z;l~#W2wm^83&c%@MIT z?m$mcwEI$SOEEVePB5;25GTLnW_&^b{BU%hOvb;DInF!?((*~LLgb*4oG-O{i}ckL zmnpX8wkO_#rlU>0ZvfW}BeB)OnRUUyK~}yY@WeczfVbGi<)t#o6agc3D&KKbdu&7H zzpJ`t6;_zy+*7rjoYnr%fx5cwq+WQv zu4R;@w&cOd!1%tA(rVHd)fkyVVFTU9;5L_kIxFpXnb_SEM&XD65axLSIq-X&hSTym zNwAWOR)0HfMC0;B;-995GagBdMf$i?M7i>~mn>ge2(@oiwmAmEOC1T#(}ox!El-_m zY8Vn=HKH7${EmDcj_bZVP%bHxF>!mR&xm8n3OH#{*M5yl&yB>vqptlLPN&K6b=?lP zK=a!iLI&37fv}Rzir>T`h?lR*Ec0K27#rJ?G+ueyb-DG=XUhJ+M3beXA)gQnDFYRD zn@Q~z>WxW1#9T2qVHYP)qKYM&=?DXDfHGZo6o2Q$jTG@D{&z{JP&^P#B37T2AxQSJhbt<94D6@2IWh5T(?PIa&~a~yCYp9L^w3% z{@6De;^h}~1}b}nOOZx*2L!>b`t8T3vk;!E>$ zaMQc!ywzYKkmVUYpWX|1wvyoD3&cK5(u1Z<%w&BmD}?-Z&=MYm_LD`r07ZP#>WPRg zhfFb}2bjx3@NyB0I0fJvnpgN9o&1R#e%B%C5J!m(pI~k%b3_>8oE`Udkcyf(7CPs_ zAvGJ>Xl}nSIfsi1em$YR z@F*9U+tkz$yWzPO-%av}ABU`Hw_bEkh=aN~1YRe2NPuzhP|lghol{Wmd9jCJJEp#0WvXeMhJGtdnG$uxWRO~EUI5!xXIPmgI67u9JlOli~VwTObXDkN}y%p zu_{XcTP*7wlBLBq3#+M{ZQJ|VAbtz;9lH!nHCwrN{D8l}_?o~ZGWCe}mk=+tJS2Kx zFH>f6`K6xj7Ax4hnw?A2aNeBJYXf+3K@`DAo-(4)g%LkeU!L?;!7Y^dUe+C#itdL5 z&n#j`i@x{sJ%`T?^FkJM&-B+#+Qgk6DI9MYrfz_2UJ{nOI*f4>c6Qe$KHbUvMEn8A zQBM`y3|0(YS%`Pkp`pCMPh7JEg@xkN3roNQz;ZE~QL5=K%`#Brjkp9-Dn}?|SmK6t zhPKKA72wv)n`?S6MP|)WCi>nE2l<5c@sq=`Q@;lIh*IFr&7m+ZWu%=WNQXkN3JwUq z4+P3|bwq0C`$ZI5Pc(429t&oPF?cq;r`mb!VznA>Ly8|xz>11k&_tPs9q-c zW%P`)LBvy5b%mdWzBq;j9jjV;#2T6DOYU8KWx#`odEL*~MI+vkSbpq9RpcQkwuFj% zrb50|u?nxePiVZF%;Xgth=^$PLK77R2}I1D?YQT}*pS^o344_G!I}U4yR_VYyaL|( z>>dg{ee^xLCR$U^X|a>^LwzXDCTNh~tRA6U%hK={y_nHd%Ngt1FC7wY0+8lVs8})D zx@?n}b()8IFObc0ZP-?bhBvd(@~jf`9jK?$lNSB$4!xLQdb{yz;sXj;^pf#K6b<+X z5z)W&x907P!C69nR(E=>R9B((P>Z$l5w!RLvK~zumP_4HF-5UR#-yTFOf=nvQ|ib@ zK8saa28xV1)w*n8QI4FdPpXQMSdyg2SFwpP+dW?FA0!vL=8}?~{F z$%m&wt%~I}xF;)V%o&AB*(dL1eG&A(6!CZJ`uY&Lb-@xlH_CE}}0oEhuP{P#MEb_-hhE3ip0oQq|8&fjrbsUqZ6(}=MTsGFO9z_|m`tR=szEH%q zsR1mMXVvsUD`5eC@3T`^mD1K@=88vxv&js9Yp5Bw9#1nPk{dJ#?u5`9)X{~TZEGNE z9)7nhO@%bn8Mp2N4a!9Zk3Haf1gkzrZ#$VM$f?Z>(#kE!XK7rGwzKOF7Viz8o01)O zR5>O^tz5!_#Xn&T~Ga%;rZv+MIf_3RrG)}-dojI`1CST1Ho+GP*>-apS4)P^Wrxf%Yuor?5b?BgQUftTD=gWo z;Rj<6OL(wHI=CIi>w)+YGy79i3k?zV+yz`R=V&yGL zKo57(wL^?F0PjfY9U%r)3grj{d;Favb-1777$|D!n>FDw-Z3R<4RalpzJHh~hSPd< z{fD!==O{tG3W3hZKeTT1+NN;R-agav{`Azgo8ztE9v~hSY`?t}^G~6%rkh$8jCWlC*oUiR3L=4!&(#*aY6gPc>`3&3M#(_yG=ZW5PzMU+s9?t2GpL4XKbQ; zOW&n|Vk%1ZyCpB5^-eG17g7NTvo5QD&NC@rEb`z=9&AGu{3ehjKD=YWly!qEOMg;hQw6;UH)C|VyudPa4ta#n{~6^k-_HZ2~CpJKG& z;L(g=OpGVk7MU1WRv%U5=7D z8v5;4J=JRCP0PHVZ!>y@+2h0$2Gl>(jQwZMUPLV}nmAwk7Vvcox^$-QM!7LPc^*L| zAHn)5l~tcfYP;R(J)*$>YbMl>GHwlImq^yaumgy-$@p&iBw++JfLIup)JO>*gN)Uk z0!`C)31Q#fWhfZu(DYixDVc*TOE{TQtDTUI(67<-AS$in_cWwgypkVRJ(|@gZ`)*< z=?wP>)E>lJ+|#|jAB?qDJ-%qo0j|5|m)I9wXs^Lq{jS2SU4E3YYOR-^tm;_<`$YEK zEn2IY1`BIMR7$ax+-vBSNlf_!DKp zYg^g%Bj9bRWn?t*cX=^{I#~>Yyy(jk~&Q7U{NXgUE*-_`v_{fGT`^FYaHV4ju zNT>N(J}oBUi@a7e4)$<)qGU2KVXiA$NE0^z=wClVg6)zHV8*g3|56!;RcwtVC|>0*hG{t*f_1@BjsWc~`g#~` z)ixj+3IneQraCuJ4zzC@Co@BPa981iB`*F+X&1w^ZfyUJ4e&12*`e{a#P?y_VN#Aw zZCc0`JV^;`k&IY_yf=&D7nM^gmqbJ6)QWh-f{BYh`bp*293<$B`ZuPA*U`iwz_*Q8 zND=1hVyy1n8jGu316UKc-)?-sO@Yx{baFYKKYalOelUyEtDmHIS=@({M&}6G7$=z* zI$JG6=zRqL+ecHHr&imt%`9Y2(H5X%?vf2tw_;}tx@pTo(aJ*T@abn`#} z000O;M1_6=BijcA7~?xX_Gt-Z)BE4?fl&2Vz?_b@Js-WV<~|;{$*8^WdER8`_9PwYW}e(i4^+?YFHyt zw|}XHf62li+L)JPfn^HW%@k~5A*xX zK4x9dGhe`NR@g6ydk_UILrh@0bPZ6hTxJ4oiOk2lJCEk<>a|4-LX4c92|`9Kkk z`TaBaBA*2;?xs?Cw(Ifq2lbBv?@%f&pPdPv#ky&;RTUK+ad(?>F$1aOPZLaM40>uD zD@swb6n&~(z4eu|)!rWnj@K)`dv_QDTcR%xD?V#=tYk;>55m~6LxZW6-ScX8V>8F8 zNa>_QhD;E=VVMoUagM8yrhjM=u{gq<^es3cRDr{u3TLS{^>hT&eOKCIxDfb*FDr}H z**GY?FU+xr4iQ5MDUi~;n$1SypwuBf{PpefU)y>Rq{^JZ@=L=)zCKJ^ z_1CvGd_z&C)HXe`7*Tr)uRM2muV~dMwBm#GLedHq_{!e7A&VjR|ZjJTyRSKmY&$0A^9!P<~){|MXpTe5OK4 z^{BC>9{(}ZjRsO8!<-zB2}nUar@P*Qz`! zW$=rtRB3NLPs&u{oP-64dJx+xjv&*I9c4Ueqtk2obP15#FD4hCiRR0)D`yf9v#lR= zGo@)7Bm^LsJJUrUu(*2Ka$Q_T)O8#PPT|o5gIh^-ZbQ086!<=4My~$9TfhmEwme$H z5h=z+Q}%ycpLziM=QQ@;r4>FT)SkfPf9U(7Fp^H&s;-cpINLbGFhciM=sLU4X*dsZ z6%7#gM{hn-uP<%KF}5`Z9{>*1!SHSwFm7(+N%7j{HMg>zm&K)@C|#C~000003EmZE zU}-gB&M<*>Z)+$CGBy?oQn-k~K5@#*k-pBHh(w~q`w;9^R@YzvmtCLQ+7T!g z;D`~(s9kfa#XZ<2J1lVV!cZt2>!41ctbnMbtO2v{W z(0gmWPW~OsXOHj#No~RK@>L}_x8buY73|l)yw8!X zqjXGHTSyp>tn$OUSEp0R4bjrte734QPrb#)fsU=#-_r=ur;Y|2la6MS)PiqJLOay` zKrxCNIUV`JwL4lKh&oYY0BFU5X564YPW5t&6EvWPnykiLU?p+1Jk4dBcr163aH}n< zzoY%ArtOmGL;{_r!vhRAQF4dyP4E-jc)a*BnWhJsbhZo5AeI_8czfh!u<4zUb`IEs zK?|ttDIr49Xz$1)WLmF;&wpWCW*c7vAuJLPtab}b@W`APH&UHX-$flOaRSeU(@i_y zm%3t3?!2-W@ey3>VuS?DYb8kzFMUi0X}BR*Vdo0yVmv!cGjoPH4GcF?1TU%^-?q1zy7xMmF%4 zPmkN}oljh_&|vy$A6vmTUvTG~Sni!{qjhshSV!P*LfMTaZvyVe2i}D6k}u2=6i@m| zHtMR=+vspBI=H9rol~FTeQ_VgYOJH&2-^i1*_$5W0L9^0;RFlN^YY}BX%RstutA=sm7lt}fKb{VuPHC@L7@LzOgRq=4VFZ%!os*7iMM^I1 z#W8N;2)XI!4Q{88gqx;8@IBioV0UE0Jdhu&Sy~c{LDM6COphUj1k5*X7(+MfLyHo@pk~KjrxTzb1R4WKn2t0L4N#cZ5pkxX6l>j7RZUt z8a}P<;Wn)p@$?q9u$Vq<0xasT?>h%ZkSSk58mG~_ z#;YUW3O@mHYah4VwqLnuOcduj4~75$006FO)c^nh0005fMoQ8vyqh-VTw|EPI*_MQ zgk}&;?MK2NutL`t6^{z?&7#}y?{J`3#l&M)iU#`=-&@c3UgGMEp6nkzg*1IVw9jR9 z5Bo~o=l|aL*+hOl8_8NQFVE8f#`w+uOR1gHoBi zj_R6=P?VtmyjJXMIm9I{`!Y^X+BWuOwSGU!_;g7D?oD{X#yDVmZa0*WSZ)T0d_xCW zTS-if5zvxA3=*(=z;u;ocGI`co3h-IJh7y9rPY;`Ec}FX>T^LS`cUQM{vMdWf=|Q^&ziBz{xl-TcrtGvF^J zQi(swyv!g^Lxu}#O6fUU8Ypt@ef&nHWT`Rg=gSM2{lr~4FKZm*ua1Q z-Qszm0000000000@m;qZJ`Y;uh9*hqr3h#eHZ43FI32_7Oxf6hhVRDOs|NLe)hkVK zeG5CY80$P$pI3^HTG>8TC;GGYpfa9gYd)WX#EaGOKT*=op_h^E{sJJcSg%?=!%^v6 z^E!E{M6HzIc8}N6NBT?n6yaB(tDzm?l)3`RSh#s#efk|Oie%yiqVc1rP zKob(suS!L8#%iT0vwaZ|YPEy<; zTt+S*VxPVAc|v%1%jueLXbtG~{{cltwh}hiVI$5Mw_uiemF$!7YTHolE3|qZsZxDa zcj2VW*9pOB+`O&-_H~(|_n0zQnQz(4eGa=2m$7y@q6Zfv(&~)J000A1t{ob$9!@m? z7SLn6hyCN^Kcpu=-sn6grn`<+RgB3{V0K1;P7&`D8Gi$FinRLCaI&EqS^nG zs2VSj@_IA%C&->qA4>J|(`5sYF6iW?J+fXBPuc8j)ReufYcQCE>%m!VVc@-z96UsV ziEB={&zdduXvVjo(1sv0#!34@5Me21R0O!?e8Ai|$)ibz=Bd+TWhV90ap*%;3$h!| zUe|b{#i#gXu%wvx%;sg6iR@OynV95qXD4oO{iW2?MBXNi%-$F%L7oR`QDR1U z(Qa-a#K9n*X2_0leZ46xOSC0cwA|Zyk`W4pJZx0tXy}~blWFZXx!adYRe|-Kv*WuJDRb%6sn$U zDElEg?OLk<$4D|;-`UdgNQk9zUNc zRm!Xt>c_|-V04h61asCs&|W@MR~>PF2b#P+b33}b#VsDUlW{qOo9XSn-&cZbK$VdY zIZ@1wE2B*#B(p|jodJ8w-r+qR!e4#Wc2$qn?DG;L`W4c3nq)4aCaw9J`^y)C9+>(p z(25i`Aa7fU2AFzWfG8zx@i7vFDGwpoU0?C zTYj>~?a zUUCGF>||IX-%yzm%MxZNalFC+(_lddo*IC*{racw%A%&(HUkumc@dA%f^o0&Jg*L} zlAiu4qUJ*x5dJ~2)Ru9c;>!+Du-hBbZ7i>m-YC6J7J>kJ!b^NI6!zOhetU^WGd2|J z-Pnswi#s*hGUg~&WieMT>?0?pL-fAG{W$d_^KvhrqBIA=nYtn9VpL_4-~NT+5%0EHD{X7VY%vrfA+apFugI|tJ?ZH|g72>~EJp!OX+?}9&#(LzORqQV% zo#Oz-{3a}+8Tskb8-8XT2TCEh7g|=5h{ax6$Nm9pwubJdLjaOkQ^_Lp*x z5Z%bXZ3J&BupP)RD1N9>9<++|2{i&ikdQ7=?v2CFTgi`Q;_W{&)e@?~OA;?~j?B}L zckPAiax7I(`maWjQkOF3h!yK&%Xra6NTO4@$&I?PQunAO+2B58`cG)hwnAehY_*ps zdRNeVuM#n^OATv-nh5z1fMuJYm|9OlcWg-W1Uc0UOv@`v_>19(o#;eQMBOfP=m5jI8CJ`;lZtG?fPx{XUpJ-Q( zwOMlyTQRWgK7Q(e7Cu}||A@BBSECr$XbO8*it)Y~DV3eq4h!lYfWt%)?&0Guu5cau zQG5PDdkq5K-^J?~vF+6wI#crHWC<+r`El7=380lz+Vm8rW=gzYYa*Z-ggbKFTR7qV z+v?0^!nwTtd`~A!7l@UO_W-KB013Xp+|plgxDwB?8=aw%-YujY_RkUhUc2@ico=P( zK%a8=``5S>_`F7zWmt~OO~ftclPL%zhK@@h{a2>3qd#Hu`{y9-Cj02*@LjsZ`$O*3 zhiPG;Ok6JeyK}M}D5@C0c|jM)$(|+jK9T@`C^F<=f{CPI;ZjI$>NBLdI zgvUwSNNSO4;tBy0$jyWA+7GqrfzMA~T6&}h$fNSZRZdr=hYhHVs*yF+i=_c0C<&O* zyz>zkfsh)XBVJ+AeT|xMVTlW+-YhH0oGc|8jGh*W3Rnah=f!4L$14;_4Z2?5`;+I( znfzi#B}VDwS|?uKcu(panX zXz!G<1-iGalpNB$lPpn0^{piPEQP+2q2$yb1qj2ZZlfxkrOt{@8Crsfm9R-8+31hB z=_k*0ETNo+J*_7XS>r5Y(kl}5(>$Q*zwlXIxmAqTZ{jdg5zjyuwj?m_^kcU@4c?0t zwHYWVLmB*fLzEO`i0CPXGSXs4Wp-}CGNI2C6NA$=u%`I!yY+b~{+GIlHsJ%?) zN$|+WV}8!Xbk;n?mL?1TXu-c9dq+%`vqATe>#Rmh&!XYO`qp%jx5vnxBJ8%hMq!B8 zneGHwJYBZM`V(;=yjf)VwHiOC_ZpMvXIp?5ND%H^ExJtAjH;xhY3kN@6{Wh)Y`=M*8E zS#Yw8X0u)0Zl8A|sU$D(%EADuV9r4dRi3Mt{Uf%(NGuvW%n1Npo4FV%U~^rM*ICBX zPOC&B>P>MY+`Z`WafC%P5!nnG^SVPknJ~22t{IKW)5c@by1UNhmr1C{J_?b1R4`;F8m0L$ZTFE z9$NJ1cpcc%4`$24%O6#YqjM4oD=9$5TU-XKedl&EC;5@}lDf45d8wyrVsZu`ntr;} zO@dL7EQt(YrQ&*DVeXWssQwAUv!p>wfWK**wt3NYP@i<+T0@Ri8QI8GcpO__rp5h; zowL&H0_U{lf3_BMfVnLIJqecW9$%om;S*ornen;ebb;*K|5_se;;)G#t&Cbr8yk0tK{NlR-pfHjB0F**o9IMQv!`>c%@pM3S!`LAq{XrJxxr$t$$szY7Yu^OS zKKnTv2ZwQfC6M)Gl{;_9XEHCk;~d_10Avzz9Bp0ZNw5DpAkL(lX>~u-oh7!-J`fo- zfkl)$GSPaz9T}tcxDimcUtQ-%fa08`Zv#Jsxz!$GJ4Y{1an%N zZ{p;&GtQ$Q8PM&7gM$Jrxp;8*z-$A6fcrxnlPUby{+lf4Ye|+b`1qo8BtLEMX;G$L zM=vXvfI#1dYXfv#wg>ln%yjU8Gq3Rct2Acj4t^T7SAv-feMqADSY4wd&5F7MyFRK*0F$ZMe)70JFDGje_INEk-;e-{HmiQL z@clmZdjL&K-Y9Yoh~7Yw8ToyYQTPa$xUyqnLeFn!8y^w}&&fHr+k`t$Ps&Q$66$M6 zTD$glGfV}~mZy*Z4`-|$#I6L7%Zqo@tnu zVC1u%*E=6;2uoq9u^Gw@Wsq?Swtljsv{iQI$7tz0O@`oSDhQv^7xv{iNUHml|`x=>y8! zkGP+?eC%mwK3|n2|8rL$Y;EBC{P}Fht5V!}AkJPO>nDi9xYcgIMM^dlL6UFkgDKU(FI}8G>ad|XwqHp&R|n+FqsWx>mJz=SPGSX zl7l$nf|n$ttV(sQfuvsSe{}}Q<#}QFawwXWiUnwoie}c%H!_&|N{2}v;TI;_`Fc{@ zo%fi3N_4wSd>f)+YqS_@MPuxgvP%@9*Q26yFx6?fduf{<>1I!{!zPqT=_n6RoBKgz z)WfIN4K~py-(F4vxfocelS{)9rSkdqRYN9 z4hwupyMAq}+fHs*w*EW2ok2;PnIlx&-lHgTS?2RfnSbvz@PrU7l9q=5sKh}B9m~}x zqR@|jb7b33KXMUBMf2u+HD|uH;RMJzs1W+o zm-CCpy-@z)m&T`+8&GbBV@r4jg7&R>>W1dzjixY|3Q~Y8f>gp2q^RziSNHB{SbG4` zhqXZh&UU9UDo$swFppUSTCMMSH!sm%W;<;l-r>A>arY<8l!?J&pOM=eXqfuBJdeWA zR|a#t6Rw~gAe}U72%4MA>5Kpp*;`#BVQR1{f~ZaDxrR|P65O2juT;AEbO&p#?p#jj zst@<6)l8EKDYuKj;hH_uXA}A?6K65FUIU!K&)y-(3oVZYGeLuO zY0+0A&_7?m4FA%c{d68+&bQ-!JP807W?YkJ>#E~PrV*-g-%~VJW;}wPbQW9x@T2j| z^#`5=jJBF)JfFJ%=w?U>TsPM94iAMOB?(+mZl1)7@{f?j4_`G@mZH+e?nGnW>cHJk z8h=wl3EnZ(s<6D?UqOu#CYe>Eeo7PEo&1JyJ&M5QrSH@Z9&c|9S`X$?sMGGs#)b~@ z#w2zj#yNW!akrQ572H{-Gds5^75lttl3R;`a(~JDl}Fk#h5+mX{7bD1Md1G?XuNSh zXrDiV$Znm>C~fSv-^1;178Z>>!s&$Ce!@*CJ@m3Is(!H-W~&|-?}m5^7-@jiI91AT zimb4tZLbAj^@3!*F4qD%AK%s^mud%QYvet73_8h6g)8Q6y?4Oa_XEFfLF&_(Fa_1V z1kIlMsh|vI=)2+gkX#x^l#Qy5)|X4>)f@O1%=7;l(b3}eE-)_X*u&}>Z>b&CLowjG zz0$W}O+0`IMijQI-C4Baji!q^t7K_Gc9dzQb^wl$`t?I_x6ILI#zi9YGw1e_M;?`m z(HDhYs?(PsdeO!<;{icIK}b#ejWR)$SW@^}8LywmF%9X1faywEQ$Rx~EqkkRoPrIR zq(kt@-gLkP)B)RV-kFA->^$BbdSJ$yQ7x4YIY7u0Hj(1M?d`ENo+9@%k${uha4@#6 z4%BMm6>cd?bS@#Ro;W!Y#CcTLiC`)B&v}h^fLl(YHu5ftBXJ4C%*WUvs|&)9KR_W2 z+vbB3!0x2HkklKYHtSvOzZX+m!PGA;^n4&{-Cl9gTEJhUrJ@uDz&$1_g_{DK;XRXx zDeaQs1U%vDi6HL};VJQvtRxKnoBO~*3uX#-0z{p>&Yl>T^1_#7)V;?7d03$Gbr_x& zD$f){eFWDe2YMQwj=bvrUKFDwM&*M+Ee8X#;BP_@q%N?7hUVOq8PWBAIjhQHd7wxW zfGxyj^C|iV)cco|OxLkjMO>_j8{C=M(F-f(wr>v6a?SwKgWYyo2~d0$4Lrv}%?k@A z;d*Z~!dyFarO#r(6m#*8h~=T|q3h1`lO%4!^X>X8YEnBi>qBj&FX#v8K&h#twky>Wu)X@3fpNlMEt~N29iV>0hKE=>}Kl8&nk@0yL~eU6Hp;~ zzGjqbWBQ)b423q^I1$)9cFvhqkfescpzFbLNE9k)NM~zE@2GXk184QX3tfKDpK8OD z-6$n3t@Yc8K$WRp@j1v4@L!(cJtW`P!UHD*;G)A$_sTEk4fCF^ptq)MXNaB-M+qoSUpvFCE^rLV%7a3U?uUS!5Z7<82&u=ICXMSS}N}yaIN`&jxGb# zVGW-rEuiRcXB#g0Z{mn+-x|N4!d;M1f~#k|u>l!YUX%>1K@V8)Wn$kLKwPZ`O4wNi z65{okZ_Q`CbSxB3)AH2+?I@r!6CVYIk`B0)$5prH%`o-XoNiu(NsY5n#`mq~__;vH zh=s}>zx*N~Gu&PkB*LcCw~{}Ra7#OWXa`tu{;3Ouz{Ik{_`noTrp9&8iIWNQa<|gz z)hYLjublCT;WDN)_AAP;%UGX9FaZ@u`^vvmWmhzw&W&c5BruAahLlTKeCzX^unB;r z-?7-Vmz5*n{xj>*Hyx99>)0>E7s{!}DdCf?b+2G!Jl=1W4wPPOu(B=?; zP0=n0xEKLG0?UFAU#y`#OBSu^-WBunqqLuKMm0}k$()mg_ZwHRC6wP!Y*+_8L11N7ZT?II!sQnsWTY$FSoBoFVxya3X`Z02inB^@?UPr9jkmfW;6GQ?>t+_3;sS&fB&q{DND8oWf=Z24uc|P; z{(7Bt`Qf9gPnH}W4tEYY8-nZuL~TqvTjNE06QM$T-cjnU7Lf5CK{R@e)J(Q5WG8W; z$oGgcx;-vD-|3lLfnKf8hh?&D9s23!rZuA!P70D1@U;Ze%u7N-&VCP7pUxyGwd-UP zw23;~W_t=x_isG$Zg}M%^CUmHnC8g*l`q|P@^bHs1(rwKR*Dp&y~CjtXAO9b5`Tz8 zRQI_E%*UX45f?mA7?#i5GI?~^O!%S}J)OuIbqbN9u@iO|?1J?!zI8p3o`ntSJJIg$ zPcz=KjK0PxQ)mbMWhrHb>z=@LZ{A1@LKACU;M9GYzvB(-Zowi_y&!lsq~z5w#X;!0 zquXT`)<=2Q25oFv7T4V-J*?xi*mQGri!S14oZdb8zEyjKt>Dro;VkRa3$kGpSHVSg zvoTJb-Hji=9p{q8I|Z1}&Htp=D%uI1%7>l{0Mg@tmq=_vYEGux2UO6b-L7(gjOCdZ zU3JMlV4mZkJ>EBc2)*ccXLHke65bjw2FLfB1@zrE*Omb8$~GyHHsnB55$GfwFxqUp z4#I@H9V15GYYVJMtiV(lh%5km5k-8ISk3wwbJC{h=L}zaw96>&{^|3(mp8E|Em>+x zuTn$2?49re07yI|5BSPFpY%s`0^MYGmP1E^u=;$ES^4!b*WB;VjdUi#h2B>kG_~Vs zyTSj_OBzD4(}Q%H=Fs*rf711IRdg(3%wV6nuI2{K8oZpQ`uhrDpKaiF?+V^b zJkxAq(P%Fel0n1}=~G*A;BM){uRA=y0^^1$24tCoNTi{Kt`9}oTClP9LR~iQaca_D zyZ8#^=2B!?#D*caY-mCZGu*8gPQUXRtwD4PDR7fTvCGQAVXapQ0e$YQ0miDGNdmZz ze3!_7f*Dct1>Dbqe1mV4R>lome0k7}BbxZ(^ObzTVJe>+IM>Kl-qOt}5l6>$l^{1f zDww;w6R((gso>V-^O4@SMMhT>o#(udMyK^7NR;B_&kSt)&L!F4R6n}~$k%G}Nw+IT z=Ey|xRH3xR7!ed&MPa7Nr;M|T#{yqu^Otv$`gC~qFFQ)m$r&qh z0-G(d-n1HDQO`goF@#0K!zQ%SQ)KT!joqub~l|6S<2FDo-?B=2C61dOXH$#BJ7bsr3ZBe0)w3$jO9TZ zzzLXa!WDT&2qTFLqsVAbbmFNe467%B18xN(UdPfAKI1vn(6=YE8D5pp_2n zQL|y0&^pS_?{q8<`cftY%#T#WY|E)LBl!65^oIuO&DS(6mEGjYIAyJEc0J!8tbk~H zPB^%c8pma^m+R%+1{%?+O6}qJ(NBfo{9?UwAS+xcuUnC8aFHXlLal}ZX69jTDee~n z_K8HbD-ZGd$GP&oQj4IgS?M(b^WXv-L|gkVPuB_75uNz6M<;dleT1ffg6C_X!#)Vt zs8I8!;=p|o#iQ{~WE0OUmu?m6GGSNIk_f&m`4c#xiU!wBC-XV9mADM?NX>$I;rZl# zr?qWvr-v;HQM6yKh7j&C-Jz5=b(-0akFYeKp~*xh`Kqp>?V-*9=L_)c#kEUMvho+G znl)o!pU}6t47{<^mi?9~dB?((x@FYuE>cApid926dv%&7e>d(vhuP{mhv`s>F79Jp zqE6t+U?MuM;es%&YK2Tg8RXWWET6Z>wBASCViKu&!3B!A82RaR9n=@d&5kD>MUA|8 zdMAR(TIW+h7pic=6p&hNEhljEO3f^*lDRU|9fJd9Yw4%S{4_eSKWsBQtE@7M8M{1T zm>J%|Vc2L()giFQUNqPE=XpAW5PBn5w3TytWBjx+%JLj5Px73>3oFv&! zClwHU-8qWtssHdBEc`K-5zStZ_u~fx7kz;kJ#6BGi1m@K?+Z)IWb;~s|2}nUqkXlW zZ*2q?7Zkn`&=L_vA0K^o3&f#MxXQ<$oFRn2CPET06E`W-6|C~nj>9VGxrHV-R43I7 zx8j?wf1Jg+?ecIyc($wmql`DRf|7FG-n-$z zaNR&Z(-d#m!th3F z7(H_KX5w#cF3wET&FZki(gnoddej-W`bnjVnDvk+$6>t@dq<@6IHPQ_taH%XD z*Mr~P#&lLg(5Fi1qqIR6 zZjOBOkK474R-*d~UolA(bO3DPq3*vUwN6G25~XC8jOhh6so-mn5r?9%lF;2+g_3Va z08)k3aTXT*!fQ96vI)0-;z|+f%Li+EE@QukM<~V$B87-%p%lbpIX@faNd6}cyFp)_ zCoYEh)KPVDVBFgeZy)YpsFTToYnyeX2l#!xj1$0Qg()GeJlZ4KPi)Rd0fsXxvX<4B zQNIk>Zuig}AA{y~+AlnrcHPYddhGRyW7nQtz^RsR_ShVDu(eHr3&8r_`_>=fN^4;*E{M^B|@+IJS*g>5hDNwkY$@6e6 z^%NF*Bda_ZA0U14wJQ2jsldj7B!g33b!k?-UXWd`k!W0M&j6?Lweh4NMJTfLsFvpSW=Pnq=>xx6uJye2xWC>38)eATzp>(H>m zQZ>Y^LT28{p0d40UqeZsEXrAi7$^irOnD23Cg}zOGEx>Vvu6v-!}zGLd%#;UUaabncA0{;N1Fv@9IoY22jPb_YrwU~mLj(h2 z<~aBTJ2RC2KjEee>)x+I@M897(2|#|F;QD_UGCyJcLc$KR1olhGI#nMZBW*6Xf2*Z1z-RLlgQMS5Ph)I zE0rg7gyqK}kG+m_qLnB9F(#WJol_MZy+pf%_5Sw3M16FJ>LRE6@QvJ?EQfOVv&l?i zDV`aTL*?eIe+)bet}7a_tH3IF!!bbpyE(ojFy&|HS|tHAYIAfh@JmqwkN*sgrI_Y={rmtw1rOoU>< zt~)!cHII3n#YlzZYwg!YGXqzuOgcO6yN8s^@#C0 zwrpR0jo#PbWM&17za;-mcJWI~A!5ql6DDLO5;ZCV{o7~n$L|x=2{ltXb8s}q_^{lP z_y~A?FdW4$v`v-!C>-uy7aIbC>dHeA5xa~JX-L3^ zc4+I@r$4IwUBGaT0WxZ8tPh?``9kA{Bc*TD0yV!kCqp75pdH zpZd$uR&jSd-06+n%e$iD3qphs&gHzip$8?r(%}%4{KMSu?pM=&-_;OD$q1?j=TJ6*Oaa4(&^-&IUL6uC{R7@Be~BCTIqtrx z>^2pJPozm$n!Hci`z6AdJP9;j=(w$i3vxmxAnx|mGM=8wwZ|DWXk`GdJnGD}tU4ao zNbUIbgNh=JQW=f|EyuT9+?GYD0}#gUjZSM=S{jK)Y<>nd#`O=;SNzQgZUg8MbUp6e zz?&jYO_oCQNc4jDGQm*nqGy=&_= zV~ef5V5WMHbMnL)BXH>}Z1X>-8gsTP^ZuIKVF^RM~W5;Bsq0G5v*g(rP z_VmXy896PWG!&iF`yx{OX)W1I6{-T39sATaT3qGd$eu?L-Bn`QO0r1Y8-oDf$ldL# zl^B+av&{_qm(Gl0LL_&vG-PfeGD+vp?%vq1KESp*MR9|XPPt;-CykEIAO<@8RvE_8 zubhmJoI%@FB-;Iz&{dx8bgnRq)ebe^C8d^`_3&HWsyGip-01cqextuKN9};ldhPgCS#9rdhv3N$U8Fj9CvUmvyzH@^vruVN_QIz3% zES{VqI@szO*x~H_;-Jh&c5FWzEaf(9ihl?9-UKYRWjNcuU}{>Z%)dr3q_I&#KuRY3VYAWgWSy1}6ouRPMyvPl20c6tIZ2!`i&%u6SVFo6{9Fo?Nu+ zX7|g0{in}4$Y7bE-PwSUa)*qmyQ6_+!(+S)fH39<003=Eh@7Xu^nWih005vq@G-Ro zT+2a;nuH3Qx8EAZe3Ajg<&5C;2*D+r6hW#&A;cgP5P#F+15Ha0wLtY@YCOneXjC8WM>oNnSR&W39EX^5LT9O zn!b_64v{!0!;v6N^@fFD(;0V5^u-8QqQ||=mWs)^^k-fPyoD=a4U#i;XVkK)K`>Mj zItijsT&_^RIVa2fch;JRSCJU8fsQgRws73OC?h?zo8|nY8YLLA>ohi2H|c&Y9+5t3 zvGaYGV}DGKk7P~ATYvoj%cpU6u9o#i7dR^4Ur~?Bg;cYRltaM$iB80GU#LqVcB}=6r zpwd(_r>@1KgFVb&#QyduNmO8uYAf`4HtUW$L+rBb3Fpn7qAjctI6T0e_n%c1rcnRm zLm_gn3zc5?!&96SsvLv954R)o-XI4*yE1yJ8SMP~`xkndS}dP}NLD$=6vWx=Sq*O; z8nCzCtaHhHxcL>IQUW+GAhE^-=0oU?Ho^7TN-ciYSTQ=EHjApyF99{G7C~qkCscEV zRj@6A?eS4QOxQM|W1GRoB6P$yPhbw1n(!X+;0QdDv$Sp#p#*SY{yP{%c%o40fHBto zGR)yj$JS#YOnP%|BTq$hO>^bV(px}qtDITeS0JCI7=h)EWd6u(YRG%9;{er4N_+fo zS0|X3Sfdp4*~Vv+UXsh^t=goM1G%Ho+v8`#(%5xFF2)Aot>PKCbh197=OI-r2#ryp z7AXO{`oVWAsLZHGw;p(TNZcUd5u2F()ruttSt0DZZQZ>$VcUA- z&u#N1R%^!B@=C{s)CGV?o@1~A(^w*&EHX!-{i%5kOtKv|syV-MzTnhfuR6}XC4u<~ zG6~43O3w%peVhz{%paA1>qvNYp4HizTT4O>XC`$s_+3n&C)iFW4tbXHdk1&1G`t0Y zAr2VXOwTL{8XR%qQ|>^+(cz|6JI?o2b+?2-**H+gM%-Iy*(6=gCFl&e_?T%#z_dpv zsjzRi%ls;J?~3&@^!ncNA8|Fg{#rxgxXO9(wZ+bcw7b?x&dw?LrTzgVz{%|8<>+a; z#y*9uDzsODfL9XxV}hUrJ59w?=qA1vujRCT7Z6qylF5Qd@?8-hp3_HkR487Zn|~J} zEW89!6>z2?J@_Sv8oK$pyRxeBAl!dzL z;=`4kso8Yho9om29cXpJ&7$@z5=rhbc><0v==v=c#NZ_>u~4>4`@G$e7|Ez3CmqRj z(Ev!6Ua0upsy48g9PC4ujD9rh-lJ<#nWS>y5wW=zx)d;(qd83hJOJ6r70Q?To99)o z+$iK?i>GY{DaTMBxV(>YOp35cd|x@dB;Y;#?b(>s(dJ7s!Yp>88-A&RFVh$~mDZ=u zMFuU{huT`l=hg}6P!QCAQ0AoPv~nwPd}j3}u(d`A#ikB&PNdd;7@gW;POgjAuvDnwCy?6g0=4v7+{mRmf6& z`&eB8*iupV9gxAbLCRW(e`l3bLZ+AJJ_i=j+%_2CkxT=SGvDBT4e`cK2q81f#E6KP z#3MpW(931q z_}!)?u~m~UO|vj5=6{EWV9kE6*u1n_q(`n8(d>|~_fgme*9hjBvV%*6^x1}>bzd>_ zNgc`ljG<6jH9%QPElY+s1cN0!&MefE=M+EDn}0B*isb|d|LPToky)}31Qkus;d z!#nM^tBe%hjy6fCW??&HBN3?mld3a?8LiIAd82~>!MFrR{Kf^URbrIbdHm+dT%+aO zS8|@q2w%%h9f|x!!50743k|2~-af%JjbUxJg1#eetrKdHLZ<+$s$}~oC^f}EKbNHk zOL5T)jY>AF)JJCa#pxzthM#yB1!6W+_p|oF9@6O!L9<@oe==|pWHk&nT9fL2OD_zl z%wLafB2g2g4bT7gpDdNMcq(ay;$yRFdyt4h*qNxDX6v_$d9jXCw!{q98a><53tlp> zQLL3}8oZ?bm)aRSr$iHm2^-!W}S`5zU>@$m|Nw-8n z@D_zt@^mmK-f|Wt&T{nP$M%IjU9aVsKmJX= zr!V|mRA(iY2t)Fb*;fZX9((32N|J2z+=H7|I+aoxzb^3kka3)M+XS{%>CNf5OT;PW zgOS7Tj4T&e;-$(uYte$?;UkNV_-8X}OGafy^)A%8H@5AE%OG8cS5|<5QEy=wb)3<; zu1(nEZ@PH^1--}$Eyn8Jpc$VCOAZ)v2_t4=%&yjf=21++U~r|yEkRcg{c&E9L2p6u z9`f3l_Bsd)z@i>L_V<_msjd2*61lkFJD$-=^q^tC8G}Tuja+C3z|PZg2~-tt>j43P zJB0qH96rTSmEidh74H>hPwXO@dx7nhj==~zG3aUMg^yKmxD5EU87=7ua1bW`Ows83 zL%8K${AJh7fPbf5u*6?BX@c{AI1maff6p~fJarcZ58$AS3CwG-YOK3msZ!Dh31)>W|^7jXfdvYLw> z2Q|rvQRzm*w8>-3@fc0|5&uhut}cPCl7iE<-kTJox_(yt0~|||nq9|&>A7Rane6wO zXdbFub@fjOIlVA$egKz(_{Vo99vyFxCL@=60R{Lq)d2%e=t?`L2{W4b!E^NB>>74%^oAn z!*vS830(ODz@BX!LM*B#ADoQ_Z}<95>WYU(PP<&Pq~cRBGkR$QMCgcG!BPA{<)w9iG8~kyx}a%t+6a12LP}be={2>rMAKA z?8)5Y*K-Am09OocRz*pE$(*Kl$9=z|Gca(E>~gl^mMi0_mrNiDv#SI*6fC|0?Zs2dbqC5VQ-V~|;STZ|SM++2zFSO5`CBcwc zB&quymw-*T9b$Pfw!GGWd^`G@on9ls(bdLDbC!%LUN~xJP)^z<|K)?s`_NG{E=m*U zYXT>G#!EgjI*Mqzb&-zradrR!A(K4J458{~N>lUD@^0*&M}k_3!t*rp_gpsXqW@9s z0ZBnlUyMcai^#Y|rFS!P#BA5zt;g>VZtunx9b%J$_5>a$w2wRfY|>MEA(Cb>{RXtp zuNnnN;>iULcT*zO{STEd^+8a1Q#knSx_~(IwQn^jl0L6c(!EwFDVR`epZyTQ2^nEw zvA?AMl^x+};)5=5R=iLmr}j`xYUE?D!07|oa8a9f>8KFvLTOeFT$KaDbSjQ{7WADcr+mv zOPwLh_&%o3g6hEQz`8?8B0cZ9&E_sgGWLh4#v%~7;;c4E<_|9DV?E{1F_Z$+P^ZL7 zub(Hu%z`ySbc6~?t&QmiA$XXShCl@x?)yHdJhlFe$d$%R64vnO9-U>r^weOGXH5}j zLc@wJ-}fOjscaLwC_#a1Y#4d4@Q}O-aMrdp_HWjMxrBZ9crvZZg%NnZ@H-PEPx<7# z+eP(w4KjI*OZ>{fH>JwZ)iy%p2rTkS1>z46oO?XvDm>v3bZ-y1QfSpYs32ODZ>K%| z;rvlTRb`pxisUB?u$%?@Z>#jKxIixQjagfNk+w5SDiZEMN~!Cns#xLOeV3=86}m)4 zQp7Bi8N1XA|Gti^aCY^D=pMXb=dNY-p^Q+IRR0sbqj@LfYn{ERraZ@XRb>VU$zPnF z*bGQ99fZYr5kraIC>@Cf^p2msh42PIhvKK_Upb>-#UMvkfOj`hZ&WX0VCimKOnKbX zG7j10d$r$`Jr>CC*%DWW;LFu34=c59e{D)r{;k57HV(Z?jYqhntd0;BDjGe0bDC4oH@6VmF=G+)>52v6YDO{6pBcKUV<_w}9Zs*nPr z(oIiK*3?=i16f)XMdkZy^qn3@mYHQy!qAEQwkmcE!=aU=F#ntdJe5c2l^k2wIuw-k zg$UC{>;}cIn!BWps#wNV!uyu{8&nfIlDG*vt?ZOGDk#uxWmAc%r`=#M@?8cI7P|;= zH;2gFz^OZlEN_H zN}em6Q`<;oxMEw!YdS@j=Fa=sijM4`cyb7~SK($u4aIYs=9l23Yh_=shz)sl>}7Ec zu`w}y1ZUPdJ#AodVx97|VTKFzwwP#ObfFopIoK>I@v+|@1Owr4%JTHewN7`guF0!W zha^3gjIVpyJ+j%@`9&J$NQ8`J^AKjx57{e{M4n(okF|)u6I$Q1Zv7#E4@Znw#@xCRdFIL~FsCe;yWA98k?bCoxE%t_{i&m;WFKM8I$*5fzeY+^@$}i+g@7er2CR#R# zq8V5>+H&9|nxlGyr^S$p%U=D!i9^o8)6Fr9i!~0gLJ@*TEA{ULvNmdxxVW(IF5B!y zZkpZKX%&T_7m9pGT~=r!K8DPv;-=8KHDhQ?lGUEWuGe{^S;!S{q3MJe^0~lnfr7=t zOr$O_$dA@HWv9=hg`cw;9|LY#NomxLgHP4CH7mvzgEl@FcokpT&Nj@G8xDO-7C7EF z!hSSmjxZ?S`Z==Hrocgbauu!Awy&`j^VJx2T#&Ilw$e~Wp$m+5=EZ=(XBWxp_ zw250JHP=(O!V(V%{56P-cAQjiUva!7rUW39AnOiy0E-Q=!K<1X71C)|@d%a;V#puD zhWD*hI1Bg5J=-8w{oy#k+2sDF?gS-6$jqumx?V!fq5^)nU?cC?Yunh6)W$B~oW@$I zcq>l0pC7s~b?HTme;H`RPirwDguOkM5p0&;#dR;+eTJmx+%t)3%YCmL zXGTEVyVwrl)&(BDt(8q%h>yz|-BpzwSa(9=G%whQVs5;xYl<+UFN z=Fr|B3C^gKQ>QfXj=j8o=&e%9MX3mAQxaL6zeSJ_^*2PH)SAzTF1-4hc7{1N*qucS zhrCtAIC%!rwl3nn2Y)0)u+V$I6PbYOz}%F+U;&7BOiM(iBF{SM)MP(clwy>&kzN5; z-ir9@<~n-j)I7<+d6A5hcw8-Fynok$I_5=$Z#e<6v;opr-AkSA-$${GChvFzjS zl2I7+u&}4)?=YVcQrGC$!jv|OISq{4QS@VV*Dc8j&bkL)*#?ar^8=kie%3xkVE(s$ zW{4Gsv#JpYralDIiGNBFAg@m`D`HP3`JRy4N)Bl!`Tl?&fCoYMm|hy`7ITjgfO275 z&uHoTVPI|+^L-KJNog{35Vi-0P3JK)YBtYKr3J$N0XR`5>FMjPb_U$8HvFRjSiE}B zi?kc;G4(!SC(@<$*zMb5lsrFx$bYloq|RzW-zM2&c^0g=zh#CpMAG(CGIRy*99{T- zqa>f#?ew}TxfuFzD|)9wV{QD9FFpsHP94g|jWE#7F2H63**mpoA^LnA>VLPFqgx7O zo(w(7qNT+EJJ40^f+pnQaJ{|8l#%N2hM>IDv zzOx)&>#o%Sc*F>$;N3|M4zUE76_XhIDEx1~YB!%~JJcNm#6P}gK>LHnG; zq=j4ZJtGxJHG1}i^1f{qvOtaNl*)-tv$0s8b^Yb0Q$@M)D+xvRaL_zu&(mtNpiD<0 zFNHDOb_H&%maQ+Y-eJn=mFbs0j!J()1p4K?s#KG+dzOLNq0B-1ciLjRR0cWLSoCHi z=<69SNTbUpp5~P1&3c8rkE@aezL1z9FIAHsu6@8%*f&k6x}2n=O|8;4n-E}6-|#IK zkZ7RN!(ToGqik>nH&~IZ=~_6?4-?dM%Oxyq_EmzVno8zfhO{L|AbD~YN+U-nw zYbxRe&^Dz8R~)b%)j#jd*-2u$qX??K&bKh!$X>J=V-+*O5Bgk>yczThKm)q>g%u0$ zbL8eFEPwWE;0Ya5J^5k8QN>#q#thn>auZ3nRg?IEsG||LA8)DfuOCyK6oUhQA$s#=`(7o1NN1@m_b2C<7Y8sCGl~J!oAd z_=7yu1ll4zl&+&29Az62B}O7l2GFK550Zx(>K)2v4x#@*OPb%*X;Z1n0Ii_GIThZ1 z+HX@S^1PJ|jXF_he#BLQ`w@}mpkj4SMTV0}5+NL>xu(7!x7TH0rS6Ja4`6yb%v(%I zxQJDia+>%l+s3MEwvxm#v9_fcY~h9&?n0@EyJ!LvgDa#`{l+TAUAM zVku61*(3n0`usa6G(&VD z(8fVQ0k_C!3QZL?DGHZ;MXU&M=+(WtPZuL&kF}b`^-@}NYv0~UGloo#+k|SdKeoGu z(R?=$l%|(d^-n~IOs5BMiy_tv@@cGM0vL|9Ttjcn-YD!8KsgvYuhmVjh=dT9zw4(! zz=V?44$~Oe;$7gxWJZ&0=ZcU=Yn<-aoY=W42kKqy55^9LgrNZ=)a~EPO)bxEv@54U z80B91))gTPj`t0!9=m~>wKPpoA@tt&bgwEb5yMk#ML6t(X_3iYcKyFay8<8tL_(;e zX+SmJTqNrIkuo){tz1O!VC!lIF{(OhYIGIbJghxBsjB{!L)wY(S??OK($odHWl%V5 zV^;x!m)o*|;8JGXeAy~gzw;fa-|}Fe(PpZLo^&om@q~q8@qb9!c3>d`a>#y_i>>d= zdQ1%|N@hG-({F%5I)w+;m?l#^#Obzbjcs^M+7YXhqu|jcj7@{HxyHOaR7$u|*w|V~ zd96tu^!wGAu6cebbk(@awng$ps6(<|TnwW?t2fr#rZ*2fgH^c}@-SM_L&yqYQdn@| zB5(kDg@7eeQ|!SRLE`3~uNx_-#A&jFOTq}yC9+1e<56UrF^E}_OAB>yB5kQyTFx;= zc6_-N;v@dd9-q>7gI0O96Qc~WDxH*zhDZ20#31k!P`LAG?Sw9;8V*>q3BUAK9^mkX zwkwp$GZ2)zff^f0nD1be5foe2p8ejcVzxTnYc5#;Vo&KBG*L}Zf(ur$*Pr>VJz#c$ zj3j^sRZt6sI})c+CgKZ?C?V(Wy`>r7V8UGnnI|4*b11lYfi zz050&J@(^l)A;uO>E7*asOlT>NEUwAQbS;c?G%HNWS#v=_|6ZtH3bVUt^?QifjfHq zB+lb*vdHJFl+g*#YV#4)Z-a<(4g=c!mL1Z&ZdD!`Hm=SzSi-(QyzN0EkfZvE?Sg!# z4dAPZqb@rvOCo@!F1?KL>QMa1tO?_G4_(qT8mz3ZE56#_V}pk8jxL@&nXOtH1mq+yQGT+CZ{LoB6f~@V8^3JJ5+P(*FNGNW zE5s@DQ_KC8jlKdvRD47mgM4iT&+Ko@H`HudqRu~rqm9BeO9OL1z0+j<>NV&)l1cY+ z?fkQ-9IINz^v_?gwxO!~F|#SSdy?5Wphaf`G^(*Ha#2w8lilB#rUwFoDrDU+#8YfI zzpARDXfgi}Q&k;(L+kjis58(|3iF&RV3*89CC5s1Q8&oV0_#tJim!BcM!oz|r`E`< z5r)_H#(x<}5GkXTw2gEEUi#ba%US7H41EvRLBH>5SgZ-9blgAO^hd`1?2>~y)}#?q z;Dx{d00000006$Yz(MtB_&(?Kl&s1RDRsMpb2Mz((oQ=O+wmcyg`W<_6~>_08ZC?9 zSXUT(FHq$@X_1p60}O{7LpA^a000AvnQ^03lxSGRahi(L)G*$Y1?n3msK9@6RS8=j z=x8b4Ro%ch93eWuHfX{xBT1@EQZ{`vtD}*Qa=Sy9kg@KIE#6BW9^=0q4*IG9c>s+D zbNl{LRw6y7x)CS2793qDO9ws{H(bM4J)hwDGn~Be<2E4-Yc&Z0tOd;yPw`&TG(;Na zSs37=Qi~_>dd>h=-g3+YbP0nF4ilakb{KONc8yz*-g9)tm-$V23@xb5Z|sIp^}-HEP`$z6P!MQYm1}sMoI`aaj%*+ z$2Pn=ih3*ThNkq1Vx3mDe#*VY?PWOA_Fh|`k6$Gy@zf z{BF&$8;mDwOH4vbU7-!2o=QRRnK8eseK0OXj=b+UL=kksVB75(rLfs;Nz*M zPOvN1cv(bNQ%fUG^SAO`!|5cIh5`02#VFgl{wincf@7k~ZUFc;t;YYOhx2(vpWOuw zxxDet4$%PM3q=u~l5Twr(u7ovFQ&2XZ>VFzc!TImLx(DYKzV5szfOavCIoJiK?n-U zK(=t)Dz1)9wtcWgTZ;S|mXhyrpR9oTlD>oby7hFM1ipkNqQeCO!sMZ;5L4*&CU#S! z&D0UDi~wQY+O#}HbuYKtD4+{97u+g9TEJsYB417Pxcxx#Ekf| zzdMg*cK6*G{fPPLAH?OBMk$Ezx6e-j-{hCBOM@O2R&lW_qo4sbig2$W0~vU&5-oO(1+^#jr!~$L>X+F zL*YI<@$UbpMz}a$zb-ho2s_^#@Z>u`GMB@f3<_|DzVh3Oe?%`<*di7)KAI{2UiBq? za3Mdqi)a=8^|=V$T~qi`S*u#<_{MI@WCk1ICJ%ZY4h&f~GE$i2p>}|(8Q&b#^1!Nh zeK<$Ph;p44vAX7FaR3=hMN`p$O7`g9E-}pxiSq6vL@j1rfdj)k(uTRA2$HE^-L^#R zIkL>CAO&}3gqd`XOq2ry!O0WvZyIC9US=L*;W7jv-f;o3C18DM`HVGz%0P-Up@vnMAmi? z3gJVgo;h)|htEJ|(_KP{F+mZNkuF~hUKW;l;Q!+kM{L1R!K0QxoMbt<&T111C!PhbZIB{J^((BbIWVu#>_grXrNLz_;tlSpe};SD20mgl^G$uljv7A6&vDHDy+(DP|6KAphgXf}o#0~ThWR2W z$P+YkcSQAbDfzt|SNN*T@1+5M4iffCjWSsms6Z;{636G%x5dF(QL)~ry)P$-|LYXy z7D)c;!>;Z?IG=T4*Lxz8k$s!EqrHn8p`ZW&00000000000000iAry(cY;vHW-S&jT zvs}XI88}b$NcKj;wuM>8VP$7U3jf#y=l}p@#f+c;001=aB6`&pLLKwyD9j7bPqo|Q zth1T*RpzU|XU?rc!Pb(CoyxK*d(Y1EK|On)na8LP zPXPhKmyVEx4B2M{+UHHZm3F~7K&R;Jz%JqtP>Twuwdq98=kKl9pH03o9zb0n@!EdJ z>0wjywxsrfx@M=^ZHT1dC7nlcvgNRBG5>#(#*^%@mCZ3Awu2MG4m|6&=lz|Ry%@C{ z2U}r~${R(K)kBsBfLiuW_;}qINsxnAS!0;--MCyGT0+r7;!NQQBUIo@+yyBjQA5n9 z9jObet=!PddGm1p(^Q6c9VuX0?Tng2F*Iosy_wOtW|%QmgkP~gKdoS3j=`yvHEXy< z8M~CrF-%Z@;op}8Kk(WkfjN^Y{2T``t~afj#QYHpAVsJAzW(>B27eQ%0Qq$bgM2<( zWTYf_)ZFES{?}raqS%>+tfTB@g!tb!WG41kbTs}gze%Jc34 zrmqyH)|co-UDooOc5J0rk^{9m;M^m6X$D*Hx2B%~GCbHL)vH%4ER~uIGAN(#wpQ00 zh9vmGgZ7BWUpW#i-m&@i$-7cP2 zP~j(Fwf}={sq;A3SU1gZCEODXA3Y||34R}5R2>pnq~5e6z`;Tu^wIFP#9Jmha4h+t zD-)orF?27(QjgS@jmRMxQ+udxayZhE-YmxkN;m4;lol1^2Onv}y7s)ZA#5NK1z6rj z7Og06_aZHkvkqA#m*GGyde7w%3RqP`j2l&#gV!~lIF%_NH@2EDuSGwrs27xhMj#*R z04^gQ7NE+TySiYVKo|D$?F%qxxMPbcbEfT~x#pXEbFZ2yY_zniGRjt7ua zi;F#_T1!cQwP4hhvxfE|ad=s4q3WX|5rtJzySeF=zL4B9^>PK_Zf+UO<=Z|T8WlW4 zREzT-<%*xt@6zS-Q?KnUu8qsah6@O(J2!v8^F@h}8kBaJ6VAV_F8c%?R>ttFnQN1) z4r2e*04%r~NKN8uO>ilV&7trYnb5rsDda9ohM^I5e{sL?3e$+#bP=|DsNmN$qPkzY z^tYK8?*k}9X-p}*kP8s~7p$us&EFp0@E3VXKM74>`mr4Icy5Obje+(DaUj|-AAfl! z7e#X6wZ&9TO17ZpP)U*Wr4qy9l+_S~==d)qaOi6fG&}4DG)fnXk+hA~zxk3fAa*H_ zH$ei8B<|)+izDLVZUy=5JP zs3%ShF?xdCl|0uM8v%2IZ60L;#OOnkCfe z3;q55k0)KuNU=S~`nOsu`S3*n6U&ag5P_tIKB3iBU)m;+qpzCwMg!uGAjaiGe;)o0sBwql2Fk7bDH*|^gW4WKz& zY0R26$hi~Xc|iW1V8AR%?S+d>7w7T@{>*&#zGjD5BIBnv{$b0*y*P=kA#eZy079bGs&+AOuI)AK2G@aL>K6iU-1ulAxt_2!sxoJKZ5@$Io2Z6Gl3j_-mbiA3E;ly#o!lr24HTGCpq zH6+`gs+;;dPD5To95Dp8J&n;pZ>*g;V4@^Iv-N0!hrEi}P{QHh1SBURaoR-bb?8U<%0nGbgF7rsmSFTe<6 z8SMsPBA=)QLsGc|Jv*Q@!}#0-)6<)^Z`a^Vo*VvU)Kupaub z%F`(yxH+peQ>$D*exg<1JhMoV??G2->W`fy)d0yJOz&63@-pY~$82i6lKj2*0Bq!< z@XF@NIi>dDHF(op6gN30!Ax;`la<F@uVH;K}?=VR7K}DTIuIHyLP)v%}+&Ed3Hyd(@3Z!-OZb` zWA~tnO2+u;XS(UjB$P)%0aN@#Q+k3oQ*)7@GPhpG%h=E}UTz4)t`eV%DGa`3O`*jb zXjZY*O`X&5Zxvjxl{s9cB7^re4}j#Qoa(UwR&P|VFO5XM*Wr}SKyw`NL|%Wf@C@I0 z-2!rkBp8toG29X_I9UEqNhDq1(?ib}#(#>ytj5iG+6sP5UTPK5)1o$ce_^y|2YXO3 zg)Vp{{19$chB!j%P#_(eBM&KMrg^g?Wu<)4WFwGV`cKwd*fJ<{S(1X+J0smzl|K0i zY*fvgFH(DFAyTO?Bq&gYa7-wM41=%!EGdeQY-(}cHOOQT!oOman=Q7^qMWX@S*2`s zi3`T=X6V->c>3PuVu*n@1l}B`vXMogk*&4frnaJi5D1A2F?E%IurEjxudi&UyXggL zB=-%7SqGtB7mfhd#>1{@087>{pC~nv@mWq@{?9`;p}Agc1`{l|lhg7mmU2&$%&eF02z zjm{5)6}1qTHqa*!$EHhMf=yZ_va&&~p9q$U&8AH*c_?Pr^)HxqxHlMKV-IP8q{4)* z3{r1VgzEj_L|Fiz@A%H9i!l_bImB!^^X|w)R*q{fm6@y}GQpM=ZChc1QN%G_K`&Az zz=7}4#C;i71`vG-;Il}B@HoVPXa7s)uBJa!sOp2>goDvp-8zC)tzoDH}iRd-G}I;!QqB_ z)@(Im$dd=SMj3t}0--I(6#Sv96SZn8oLx>NcGRa!E_vOQGDkkb4^C!p$f5?hsAauQ zk*dKiN3Om9stR%Y({^URcj@}9Gv(p_ywe$QuN?SCz8uP+%-9Em8!UL$H+_IrF4so& zR9wn^G*o9n4l+OOho(=!Lv6*DxsTQ$G`JZ0j^r*Au9!4GolQ;`y&?PfX?j(hK&u-B z1P?Q8X9qN^rplvWhS1h?EarW`qRLl`t~n<- zs~T_;<5|u~7;}P2lZ+$+{Yn$7=)?!HJTdeUxn{uK*3~3`W3lWjo+eM#Ik_2uI=Qgw zODQoR5qYXy^44hMHQk20GC{a|^aUebY9~}6GC!Gn&bFaaN$2mA>oWAoiiFZq*uCD0 zj2Z|CP#&R8-=_@NceQ&;sSNsrNx>cK9?q1?S5r~QudE*6>A`birWOQ~+V~T%jBM?_SD(Hl^?|aFk;_`aZ`*lsptn&d=8q*y zZ$QGRJ$H~1g81kp_sz`nwnWk<0`Cs%6pIGH?rf#~F<}4DGkIUG1xh$rj4C<#kSw5f zgK~&&dMn?f$u2+7oD1TZT+GS^P=cL3Gs=PL`-4e9m>nrU>WahwYK98;7*x z{^R6Z{YE88K?zkdS!xlHq5=(Zf+!ZV2`hYnXYYK*DNKoN;9cX2&EAR4mK?juO>>gZ z(`Tq_h1+q0V=%O#NMm zp~}@bgws&dCr(hG1<&6u2mQlm=ddu8e0EvMlo)fTvigQ(&M$Mxs-oa?GZf7AXy=Aa z;D-lBo$H0pyl0Y*1(U09QiVDq=1hIvlTh{Kj=w*v&E|05%Qap62z88nXQqYZ+gOBphi4!5+#Z!>=UUA z5-;vd3OY{kC!qoZv5V0T2+Mz@qj)@zi4v7Btl=QAh5%6EQbx72GwI)Jh@ZP4G~-9J z+MQ^nqJ+ecG2bJl-CX8w!{Y0(KO6sG9mOKc^eM@Xz*=+He|yq7Ip37hX~XV?UGMtf z=aE??zL1<~FGFycj7A>e@Mf-^ReLc2P_pdl7&^bi+CG<+P7zG5=Ur?sShIBh<^J!QP-Nk?3b#UHPOs4FhNbkbj1K1?_K7&lWxO=NFKB9uP>+D(X-+`0$+d;C1Pg>)vfOs>H6FQ?^I(#&s1ofnlh@WBf(D%0II z!ixePzz^tVz*C5}F+B@M4Mqbs>W2YDL-|(fk*;f+Zn#rL?5}q<^^JoI*~=FqH7s)p z!TstMtzF0N9gir?#L4B`RdT2s2-uxW^b%H?v1*gm6O|JwlHv5WKU*@$u^G56D}LJP zbby~7o|_*PIMsF{WY^HCldY?}(eyToZEI*svXm}XwJqQjYu79hil5d>CHTNOjDRn& z7bfgwq(|;Bl7CLwpsjh)bbiGgcMK@MF84)N%A`ICk(hIDb0VjdHG zDq^2J;V1TFe#||EwCBQWJ#cdR$FOHPlTlIz*05y?3szypP51~jJG9#sGY+8Qe{g}S zPV_1>8H`M@Vnq>6#L`tl`)D*|phXtCUFQsK$GP}*z4m<^ivJBxD&4z+E9V40?2mxf zCo|iyD_;%HPG7q=DwmTVg%0r5Z8Oq`jQsmc5n#-I91lz)kB+`prcl|Ea}J%hdnqG1 z#$o%{y^V2LJ_3Y%Ml_-?ww@TtV=C<)grd=g#PLGAU$V&y`%N(|ZzQ*VdHY zifYt3R_eXV-~KLgc7rj#vpAx3$*0kUF|j^@l~C4l%^YVD{4w)&-Ei%ii#$=F-LG!V zrVZK9#&V*dresXV6K+gSx~6RIlFb(O8j_K=Qkx_V*Pu96z~q%o@cf1iIH{a9A%qB# zYMR`h8wUXbLikdP1(5E)HOb5|4p&r%j?OxyqHi#~pY3+FIFD(; zNyk=zs7WOqFlq4rTLD8{LBcqgtv)=avNtl;fCfI7SI zy}Gy8dH+?0q^kkcE+MUOIn(qauG&HWfWQ0<)MMt-Z?z#C4TDxpcLiY7VEkcE9LAnu z=ybFA;{UIViYsMS4XRLS+?4kPR{r3K-=S}lhLVG(Hv=Zyq|fTy;$N$8_NBhMFvUKo z$Wfn?yFfZ@(Byy%TyA7W!ConZ<|Lf4d9SQ_qgz*hNwmL1q%Yu=5D2*t>p zUQEs{(TA+&dNNl&;H9wb)Hpp3r~)*g6amxejuS$^)}Sp)hbGDw#rc_y4rSSj+zvvI zYKt7uUcy$!)0;o(&sTT*z~5$cB$5&->`RugOt&PiScjVEp^3+OyZCMlBC&5{x{9t7XV)w|ag&*xtT^tr zS4ZfZ79te(hGuje2uW^*U`++8No*MalAHcmJJ^x*)-3AEcXonivem56#5J5r&hAe+ zrM(*{QHPizr4~0s$Ny7ZcuX4{G9fv&yx4*HK##l0gSR+m<~$!ycM~Laho25T@JP~w zlN*1`Bmpjz?<7z5O30mMbnkc+5Ry^Z#)9AI)-zJDFHaL$lMPsj&YM$ZU@Ud3Qigfg zz$f*$j1!lVuJD1d5r>@}MOH$57oPIj7viGk9%6sKRGLv^SLrOlG~)g(qTV3R@t&tL;eIYfD$S6 zD{!6HfnXeInj^;;{e<1F3wVh+`|7Z$)-zTV*^~93L#^hrgmxVd0g4xdfPVFh?i$2w z%~xB`_hcgjb?h?RfOEArD?ux|T>ba>oN{V2xOGn1<<7mGJQ?8#HXw{Rv6wlAE9W+U zn;W{#3VMkGD|p+C7V4CnRxDh7=ripek#HY@;@$ zR^fQATlhRRPsWXB5ppihdFtrHS=Ae59R|}XLJ*@`0tZFXah7D&XXk6`K+HT|utCBx z_{)23W#P~-4OAqcqf}v-h2M9KOWdMaGylFjA_}1U2SN3Sp-C*CoBPrM>L+=P^M|oT z(BqTPT9?=w>e+5UF97>!F~u(PcisxvKN;s&lAD)Jhw)}l!?~?+gz!&w zg}d2t!f|0-zuB{H9AcaDQ}I5^RaI^T!+56?r_@2S=3o1=d5}~;rRy=`MCbRM6O_>i z7)#`x?l0!=LKmhFx8}EHkM`layPn+cRh-ts$s)$2&!s1eX@0`q#_)nTedLLD>$m7c zM9!@MkSQ8@Y)iweCS(}zQS9vD+3-u_>1weV3%rY%g|kg17*Fl4aFjPa1D{G#+b-z{ zSCu(Um2~d}55_5HP)uz6OI?>sA16{f7c!o@NE8)CwuTP8wu*Wp6z*3DbY!{sj^{8u zP@@#Ebt3)=!zKAI*NyjvLKTmOWhW&1-VN`uti=SqQ_E7<*=m;zw#Ss7F@Uh_*9l9Q zIo+@b@sNbveir_gB$UtoOsAn`@0_>tMW`RSQC zosvG|M30jz?{itKnM4gP?C``S0BD%mWWZUn7;!>dUhbGVSFmiQZBHii?pDx28*GNm zd_b62SUM!%Av>n>ywmtGY;6kaQ9q*ufMbM&un{5`Ay01HDx#;x|R zxAL*x^55tcu|WUsfoxUwv`K9i)ylZ>)5olW>LH^3Lm~Ea+dzji*+%z7GX#U%lMcG= zr;4KfnA*m=6GM0)pTW3OgY%jC@uHHA+>A_gMcWOh+S+FHkwS$pF1AgT6$?adTS()f zaQKmRk0Fo%CW8DyXmK|G|$jc%YL0yHxJlwun^FBp`YUjmfPZMxb^N#FwzR(RTb zk2FBNv*_=VeIrfE65*TUP5Iu>eS!m-?j}1_qS2XoZsr>sqWJhyVmebDWaLw53YMY{ ztshR*N>8PwDr+c74acI1^``ERwgy3SuUhYu`B)lMZw_! z$SFBQbuh~w7ceA*aaMY{#Ez>j{Tv}{@UFR`j7W1KUjWc??ps^qO%TKr#uT5_CmEW< zDI;_Pp`CiY0_%1A4^DNp)*0<%X2^eLa($q|n5+#LbK4925dZ%YRedb~`X&w68Sm!D zHtjg_t5W;7@6sfj8K8H(^&ma zE}TE(4SG>GkmR?U2nax4gKSDyD41!kwl|5{|); z%R}=ijH^}WC5ypW-3!hVtK78&Q7nQ1Sf2;G7QbjRvN2U+a|)zXK#KI)UI14z{n|tC zjlvDJIyu%i%5-msPAgy$YCfmrL(a7_ILc9922;L;^+7&6@4us6BS>ed#m^>E|V6esW*MS@rch6rNC*nMNG)8%4>GjI@#$ zb|Vh(;f=t2)qq%ha$01U&NmO3eYNS|QC7Af(hU?C^qjGorxV1t&?f`$oEC|CnGXxB zpY?!*=_@lusN3n4D`9QMR5FEGBZkTW2MC~HXqfl+v4oQ9L4YT7KjGDkm!p>kf~7FN za717z)r8c;-NjOG2>2kiT>qU^h zUN!&sS@!ApwK`lRLpT5WT%t)Yc@OC)OCiDS#?n~NT#ENfREMJJumxHwjfLcwbmEo& z&pLi!@lgB)+)+1i7-u4N%yLL$8eXeF%zf9Wcp`Sq9)o`uRMPeKH?s2Tb0fhTd`^G+ zt^MqTUab7ZkNqo8*oEmTR!j_-y;jSJzt3eV%e$L7ybzB`3UIH?ZbV)-Fo30IkAN@e z=mkE@5BI~uuZe*4MjCnA+7D-uw*wKF&%nRmmWpXM0KKgxmZ)mQjII}4d)<&~If|N< zB2)Bq+0OI&cf}PO`z-@;j!BR_|H~o24bF?G<@8Dn0x{;({^U1F6UQren{x6K%Z;AU z{TkUyamHTNNchin~g5!gQ~ID1D>~YVM7zYkEr2>J}eN)|1X08WyH$9+7XY zP>p;0k`b6be=J5A2-Pb;y8K38~PyzEW$|X2p7&BaUQ4bd?LfN2C_3nywVE^ab7x(oml%-JOmXUDlm(_B4$B@#Y$x<8N9?+ zX3l^&O}*cJxB7JV%l%+1ETL#E>YH*{3#*3R#7ZEIx<@#W4X4BfDl^KV+U3N}2A4XJ zMZts6oeK9ssl4noCVu0YuG7N2ZLyixG{pv=EKLgMf7Qu?4H5_Q+g>QeBjUuON@~q~ z9*W7?6&khF&8#@%=kfDg`&WPf02LQN((mg`xh-vkgw8`4k~C00GCbadLVtZb@R(Q^ z91F}ram9pmw=iPIsia7O48})q-w?j3OMyM2o#)cplCaKP>qGqM5T&H#EOmhZ}>*~Wd*P6z1NWt6@9x_u-X!IOA4L&3|ke^ zmL&O0$FsML8J3YCmA4@OQ-5+mR%TbhdHFZx>iq_AB(bbYAD4lJ?|0e`*$xxUFB(7~ zJ*0WfqyPW_00000009CDNC)HqFE;-K2|ZJ1Bec;D3EuMjLsN_2NdQVd?r>F;V~LfB0g0~Z!ymwIk)g$ zDwjn*Yj$PDemQkQ2R=&u%C-2jc6+!Fd3mELas^Mg_lVwTuayl48$F{Xt+95@nqIvX zXDim(n&1gb!IimVkC~v(Mn;dsggZ-KZ&(IqHA|)-OO+QCASuqhNZp206!)hLa4TN` z1p^6a!CzLoj!Y91p+5X8E66%}h%rO$Lid{mf@y}q}D6BM#p{i{&#zaDD zMToX?DhBF2+P2SaNeM|E3md(CW|0Dvj6nZYcUZDE_s66(b$vTZU_e|ZSONnO91)ua z{B(XELQwv$|Iu51f07<+!#WRbV3v&1nhbgRV(!nHX8tPhDfc5|m7herAFOv?snMeL z*7=QZ*{)uGd_`-9p0-Gt)lSLNk7R!nnz$UID`cN#q&#Ag&D(P50G(W8MMxU);B9&h zAv#OhLsiob;xf7!R`eDJS|QP}S?&q@Zchb*U*JlBI*0&~#}H4s7nK$^BsOjA+}w-0 z%epsX&W`*Q4C_rpQA~4COpgcRi{PB*hA_##*?vfXY=?R3Q_3MQlw#EkF|fKgw-Y;k z11Z%!$}89L!XY5hlaZQ{z0#BzJ2Pm=BthY%eu0L}H--(STA#Mckb$l^7tIh_r@JNy zz?YO=!eY-n`=2aFZgL2d2DD06_rv7uM|GjT#mT$&BxtG$u2x2kCvCt8Lv-$|8kQsD zRm2r$cJ2i_FDQNiw=f>UW$7OeFJC`W%rG|_KF36L?g7D-7biE2gkFVY4|JGewLegrY* z_xw4dZJ*Cy%vmM40^nv)xgB$g-1buFv-|Tv7O&YZ8C4(EV%MaM!DyMfBZthItT?}Q z>5Q|ms0smO#M)_}M3cMd-v!WVlwsAVoRpf@W7|M&6r~JXvw)AZu`-B3j~ZF%&Hm3P=(xtyTy9l7S-<3n_))+0(e zPr$cJ*S`KfiWA6S?7N|z_T$UInlb3uZIgLv7Jy~_rwG?IPoTv**L>(edNsO2tS;7- zL$1;Hbe2b@px^EWUZ7))qRe~{ryahm_`sE`G(Xa2f*6_0K&E_>oJDN;k}UZgo&e7{{PV`2IDomZ06_7pC040XccgWO2KZP<3zcS~ zq%F46a&I9e5|Z<@oR!rUrgQ+l_Q=}rS`=op z*sGXTq0D;Szjb|Tec0(}ghlNgMNo)*i-g(h_jxv~lGMRycAmib?j}#2*rNdp{uT9@+sq=t9K9(*UJryZZo?%E^E5){s< z?8!7HD?XiGcVjOS@rRwFM~Sy)rPOx?JcJm4uhxCWV~w-D$E>oK3NR;O1q&WT2jQR& zznGSFl6@KP$$pr2G!;?*88#%D1*;)YelRh-vWW?@!qj~SZZ&8m< zVl)I3pc$PQ+?gqgmD^TlopzXy7Q69iTINgV;x%5^vgs|MkZge%##s=s0?^vqnWbJH ziN377r=@Q$x26f$;i%s^E1RUCo4{oDjYv2->Iz&> zuI`Tw6p%yuk=JLul$yxb#^jr!?m>L#qTXH#m5X1kd)_ZDYj&DDq`;HAz}oP1kzRy9 z798DkNq4nkn(zV;jrW^@BRNKMEqZLvm(yY$*W#NmOrY+AA8t)O#<^pbOB&}6VC(B; zLgrHlctuh!5T9_cco89ejWYD?(5>$*zak&N47bX%g}a@HM>yQH=pp~Oiq!Ae z%YSf4%NsxzrshTU)$`80bToCo1gr`0Z6gW)61_JiB5Z@fjuxZg+xC2k;1(kxhHXNf zQks5d%3;v(qRlJk=QmH#4)G$@tOmdkf0?C6$j-z`b0rhp6&6y~#eLdxPU=gz&Uo|T zn{yEZXj^d10Ss+zeq4QCT!5+V>e#C6IOwy1w*>fJQdE@Zbpt?i^Q8(boQCqUH$XeJ zMNY6P-d6)i@N-g$b)kl^d(t{ussb$Pg(^il{L$!VbK)FofHWi5^TH9E-!e(i`vpSt zdVz0G!Gwk|d}Ri&5js&5j{qm3viVF#6$`y2xLgYp%O#nKMw|J}zY{0|a;?iS@HaS3 z4^US7xXfkR0)KmBRcO&ga~}j%=RNTtceZ}aywjG3Dl^DpehrWcxL7%VtKrUX=Y~K< z9cHVe3`FPL)wv=F(*hE}`U$Zr2I)kBG{!}Nmsy7?6{&)bn$I!pXOzW zc#=@>-=jZ^WEC=ZfGL5OkC>BI8(%5Wk&$Q<`fDBaJ`S0#_v0X&;SQAezl6b<-l7I_ z^*XQd^AYH89T>!P3A^_bhV42^JQh@(zK`)3yU*emmjmCgXHwb+aEYJ4bW_JvED{&g zeLIgDs7zl!-YpwtV@? z@D9t>(kTvURDwomkw&bvb=!j)zIR)b@dUjyY-62L4^pSgKb{%*3qmnVNPv>G%*;b{ zCA#A~dM_=%B3=szU(htejS)N#vs}sLjnr*wob#`SK7{y<(~ltl;P=9g5UP9jw^W|JkZ!N33j0a+d2|1YO@swhf zc>L5nH<0gjeEQ0!V4wl>Nw|)hCfV+Rm*1FL2Ebas4FJ_I7!{4~nmp;#+gN)EU+qxg zQ#_qPu3?nnzH9dk?%ePigSfv`Potui@Ut~?^QxB;Xc zfNyh`N>#KL^6-kZO-|$|pl0wqt-LVA0q=QuYte(O(gy9WubOPo@deJ4!n=xbspKCF z7}s9(1=$8Ui^C=4F&;sh8sj%E;3*7OAspKG9drC0T?Kg_;u9F?3*7+EQd^8y)(ROk zWXV-7FI5jMK>-+c0V9BQuNdzeBV~ri=S;cqHb`iV%25gTLvI*RSC{QM=RW759Ej4R z>||bqPi28F%TactOhlFTN4b%+E5JFjb#d|qfRQ1U>)8-~(&FLgeP^F4iCi)C`ud-2 z&+^1!Ee5*SY)+>SHib3BPe_+;5f!wsE`6W?ICdvm_jX#HOR`|P^O2JKKsn7}bvCtK zt*hlVC5zkE(|x`sV32Jd7++mRkY-2`MX*#?M-(`X-Vs-^wWw_y&Yntw`)B|Jw+6|L zhjhQ;;gX=VxWaZ=-TaiIVI{JWZbXMn3)>8HQm@#1Dw>Q|kfeTKXH-bOpc&XXL^eo$ zYx(yEclnnjh#;s3gEsxYcha!42RxAqK#C>0 zbUPQw=p{(m{jD~~OTpV2j$WFV@EdVE&Az);Q+0FyYu%J8AFg0`iN7K=cAeqlk z6i)a*=zJ@L_xq9c1moU4Wqe>KYcr35OHMR58aGa<56n= zkR+a3FT6y`+v2%Epl4T#%Hq5H{)sTS!6Jk448GIyM?~g32HVulxS8zOZ#^M-=(V7& z#}v+CV5)~A*UybSEi6{w;NcJE=YM^zIFO}8MPMVt2_;jWi*59)hNGK(iZ-btM-kJX`e_Uk|%!lB#1 z`p)_(o=KN{1PeR2N0+r#-d`X=uv?_g-l0Zw=1ltgPG}>uPAN_8e2XNT>^UjeXnke~ zomM(R={^GYFkKw+;OX2x4p=KW73a;4Dz#+2#u{eq9RdEkH?A0&tJdj0X6KMe4Bs^u zJ2|>me=Z}w&7U`>nQf!4I-SC(N5_DZ5YSIX50aL$9ztt$xl;-L9tDFNtF6|Nas@+Y ze=rPDEiu5@L&2AN0000jpa1{>7Of0UM@nM@j7^Gk_>q+0%bUM**)E7z?-Se!y<+OAtN zVi7+whukSg^qzBv=JELl5ePa8j;eml&dLq?LQfvFce5QVn^mNO~IUeIXU zOKCjeKD$*|PU+-u(MU5J?g@=LF+B>Zl-i(bYF-tw1w?hK=qWnbw6Vt&4N%jRdT!Ey zHLmWMFeO3O%Q)1`L@83=7B5q}lWdm04;3S*HkM8uztkbm#-&m?JAXX?7pI7V$Ar?7ZTsNE<2n@b0v#pm7GUgo)DR6yMdw<3P* zCXqe-6Z_Y#tLB;wGvojtYGGFm^ly=Q%$2`XxfFaH7qEb78v4<>Fzg}3z^( z|A!xB~?lzVq!GjL#ze>GrHKT}&8-Ezu-KSH^olP^HoS+yP|f z87lt}W8pwZemR@j^V=O7k6}Ts0!VNs{c2_E<%9_y6yxMSQ7{u?xtlTmH^ifYl&N}6 zg8SAuYf78<{jb-8tRGYRr4&;tbmC6_98v$G5VJ3c9p>y>#Rg@^4iDQUgcJzL$!oRK zytM+95AtC<9LRyTZL9gt7j2d9GiToa*CkM;W@{%!PD! z$hVqDH4&H{+HL#v_^7?GDd3-0D`g*TcK5-K)O?(&-0S}%F^B&jLBr%J9W);I-#Ra- zJ^Lq~HtI0QuRL#trjL~=Iok4%ekcL&viT;-xwR=xOFG7G;44_JJq+FAN?o#UsYFSS za!yheJG+aj;Z*iA8?m0E(Bp@JS)$K9N2vPih-z6u-e};mZxJolW}Yk4jm7=!z9dZC zkomd#M!|Z~qi#qw7FUZ$!qH??FVE&_4P$TxHgCIUGC@B;q2~tx08c0l7cos5CM_Bw zsX@ghUF8aavI8f#RLtOP_XrAtfCvUanY)|AT{oZwdX|w)CRx;yFyek+OlD=wuaYEa zEkxQ~`o_}@9WD6QICFaLwf=3qCbk;p@lk6y^J{F$+PW!aVym%*>YM3ZT|%{Y@H zC`I=%vB@A31RIF&nzJwoUjXh;;!)M`Jas0)UvH;oawt9)8fuC^HRPp8840FZMzx&K zzU0A|gxut_g@N2G_gAo-j&_FZ=V#RYrwkWSfsM6j63MEASdMla{O%;Q^Ecall?q?< z&1DlTTQZUKe^XSKU9&STsXss_ib&)54vfqd@_X6Q@@}0meq|74Kd_4_OKKJ*rE-@r zJflSd=TjvPd zw=~q=B>`H{n&Fubw7_p>lK}lsQ=!`)e_>IKFd)K6#ZLWi({eFIJ1o8V0`t*83FIBG;FbVw3YY&AMkYk$e>i@UNfe3blbeuj!O&&!WR8v2vb8>b#`QL`A?FbsHGWko#|xJNqkLPP?Cya8#WaEup;C;RjsHrNW=e;H z`oJtDUwgf_P)+1955fY@hOLOuL`h)ilHPHC9&PHX2-#}kv^U-BAyD(Yqa^etv$bpM zUSojm(0JNr=2~V^x7GuWfW>e_I{qMzO`3+SW$w!CZ=5QopB`52a@Gwo@t6c9?|I+x z=4#Y4nG914r2oE>5PXsy=Ce|=Wbn+J7l$|PejB)}qMqHrH);+UUk03L(z?J$u|bzB z2U+9Fsww!PoG55#gq=u~*KSnvC7Nj>vTMK4m+YPG#z10!eRfXqZ7&QC8DoWO!cG{= zGOq(xQx~>&N*}9`-b(W?h;GiB5$w?EFp07s{ zDBf<4vv~ileD{y(?v^JbO#?lPha%-#1+EX&X%*gi=tDS=qxhFs3RvuoaI>VaBNv|I zATuhDD|30cd-d?kc}cp#m{hCurKB1g8HCE2^zX_8`5Wy>&vJ#s)2W=tL)Zb5rNkJR z$k5NxSzeHm!sj+m%&N6+w#NtU@bUCImW@P{*=)#zD#QGkKvo?$&xT)DtY!8?@UMxe zVgy_H97Q20Siq7JU46k{5E6r?q!%nY4+A+CH-5LR%pI1gctj?xo1M}3PbuO*RcS{w z){kc9yTVy=U#@)N|MWQVs4tkpTXwgkq(b6;6IsL5C}}y;DwSTSm!(6Alj^^rBJN?V zNUYS9?$&&i7g@f9rad$8>c$cD$78x1RSEM6jKR=;YYzc8_QTR~UkFkhEb%VG#8Ly{CaC4&G zh$Vi4N=o?O)O8sM3uI5yteF8 zWO=vRDIe1HE=T^V#7KY9q0gpxKQU;(>|TG(ZiQMAd$#3EMzDb9{YSAXaYti2?r@kJ z9g9;4HY;nqXD2C$7sqvw8dE$9UYjNji!t;L6v&tk*1ntzDR?C|4;Or?>Vhx~b_3!= z85PAL)uC#kU^QsaR~NzE?nM~0niaC_Z=m`VzrpXV-;OT-m?XboZsWgS`xzlsw2XZq z0>?Y~`_DsI9kgUdGe>Mp#~rfId77XYNdYMIK=yh?Gt&zL=n(?ymWiJeT;sS|I`)g6 z%cYmBDx-4Qd(kOmIk86t5{#RTwDCX`C}g#xU!NY* z5-@%Z#hcVUEemXBbSP!iaOr8BA>i>0g^jycy@Y1I=Ht8Rx&`Z^!F@U^jmL>E8d>VpTVf_X~# zbRX1}2>{>i7plK#@{lwdH}q{<9SJNp%qLq-c@+5!UrA4Wtq)8LYzgZ)i5nwrBJ%C< zj_|Z-g=vaufapBbl;BkB2%#;bUF4-i#SWqKoJ|ZwN-v=a!^Nt686-rbcUvD-NZ$o< zuQxg<6rDw;xN_UB3GS-6_>*>TFbfa(SH|U=G-Z{qcLS`MIeZPAG3*`n?nDU-3=GtM zI{C4kn_W%#Dk`!(Z^|*V{^I>-J6gQ)L6Z|(A6p)!^BCx!E_BS(WNjX3T8hOOn z&?){5Wk7Fq34d5YP`Id!AjgruGP1^!ixKvhQ-i&-_SA zANn#rykgb~GBm32sBxc=F1P`WGf(K6o+m;PzLjFQfT%hfCU``8&AZ(5R~RNQHN*6N zqKX*JT3S}s4${+;*$>>cnDnGBD@V5!NacDsx9U-OB(ERO@sfDomcv((N7sdCm@CXv ziB8%^5oe=3J2s$VS`{3bYLgw!6H`In13A6GYo~%Ao*DAbpCS&e>~iT~qu}s_JX|lX zU*CGlf|7d>Y7!Zi8Aoy(It~e){~kmTtu{`+jcB8sEX|exg6{Vr>-#W(Hs1*I&arty zH!dT`!91V)E#cbkUHHQcQ-6~L_l=P0D$Q!j*@K3U#?0g^uoz5`=a3+TRj8*nJ-AUg z-ST*~86wSWEV6JvJs#4`eEiC701!k@X{**k?#ii)bhw@Q|&JX6o z(7kX_HsQkUjQ8ptnoV12IyDQ-IrrHNdSVZ?k%1mUctXXd`rh~aw$l-<7Wj(%Na*Ahf_L|(!^@y`+;4U(v0{O zs~D$mO0E!Bj`&btkx3a6-ZZg$vCb%Qg+wsE5jp}<%Txu-VQ!L|)+{0N*I{)?f2|6@ zlQ1aaC=JBXWs;Q~*6^T^uxHMJ?D*;`ulMt-KB*k(M)ym{CswHwJn(+p`i2~?mt5$I zW}=!4_G+Lk+340;!5MX<6%<3AG3HqxLGb7VEAuCjj8CrD%+B#?s!B8lrr37>t|HSe z+I#&@Mn}&5sqJ~!VPUB3fr($L{zeo6X-Hpnu4_{vx;>K^NuA&fDq~xk7e|7$!h$6z z2XA@;bWWR8)4Oq={jWc89hyHvKnJmWt+;x@koX1@@2`)vP}zUVq}P*hw#GQz=`((arjWq4@E1Jld7oQIsL1(h1o(~mkn#)K;t>&Y3=&S5Y zilg|S@|G)7>*7}sSnDq?ztM#K@dYtsg9;<&R$6}qXmO&#mG@p{xJA`=$KOvUKXWUx z)ral!IsT1_*F0|xjyqoTCrJ|SI8)$H^NZishO1bX!j&U>5bSAuVN`Z<$qTX^-W6aq zTV|=Hd%!X1+62M40di(cY98O} z+E?{rmDq}EBEf9^k7#yTWAzgQ=O>b;ymiN~1*@qJzp)Z!8kILMGF&bLYzlVobzY0e z8(}+ttLssdt50ZDY53zZc?bwE2^Nb~h^d>O<|U$7@<^vB_rXu&KXO5lr*$ODOB#J1 z5t|7fhfT%VuyLUTU_L+iB+>|I5N043K-uz{!RxYQ8&X8E4M!f?PsAhw781OCwAc(= zwS0L7+OrEd_=E{HY!MUdpY1a`yeuv5NLsOxWq>XUbXM|q*vibVRX{6iF9#OxFv}4u zcU;Zn`BpDG8}2UFyF7j&s$et?B!3O0_{RY}w(}b9Bi2saJg2mEAW;T0?z?I3-Zn!P z{t~cmF9%KPJi>TRS_kt3tUs{Bu!C0}-qq{ydUO5tw}J*aVBeGw{oTluk%h6gD5Kfq zZ2JjJO)lIKu$+oC_o7*i3P`37inc-M|FjkgX4p03j z7)TXoPOXuqdS~4^1hsOGaeeYucEyns?=#EJ2LNEPGAAKzU$~2~pR*JkvuJgGB49sDdcHK4_BsCOf$n>KVJ%-dPdp^ zqPBup?ET@Rv=!v!5elt9lC^hjf7GKYf=bnpa&Gt83b{}`VrEHm{!D-@5ZnL_SvNuT z&YI4W?!jnBixK?;v4vr8VsU5cU?B`StX5~B>qXJ%)~qiWF7Q&EYdXNIzZp}@>4t{U z^x&4gYFYX7#)s?T1)Jflgt8+*j^BV*gzRw0_ld(1+^`^K#WaSuI(J6Z0z)(H_J&=O zJ!N!TU11k3vD}N4VW7k{;6`jj{Lq@RjSiWovuYu>%8jP2#lOp&H_`#O%SeJ-? ztW!&sulXBmlhK<**+Y2Mjv9nS_9`GX>;4ulmiarB9UA{E^J4Xw@Vx{pYNAG{AmV5g zGj-F8+cNR$(A_r?>#BIx%EQVgNbsyMDH9_}zgU6*?6nenxo-XJSx9zwZHnxOGaU^n zlUVbjDoxypypr$v@#i6Y(gv0ic$m3fi9T;d!x3~aszs{4R7Fl+q^IIDdToK-+sTEhjhkCaTZUt?p6iCOR*`@owS4}`b#U`Nf?@SL zT6M?KD(P9~ZscjWvtg&uRP&na{!IB|WDSq&HiA@o8#3-nGZ{lLq9-0>zzVkHqk+8V zn;TXf!!;s`VP@kv6o6oQg?=Kl@`FLt+xzP)`n+YQ|L)TC7k@G(YOvvZ)Q%;YJM|NXYq2#wkw#8n* z?PGk-$@;c8F+@V9d(WVQ);KGFdyzq{sNOm?kBOpg*wag5dnUHR0RX+ee|xyP-^o(;JRUuhml}c9U}{N z&(%;*8{G^m97>&e(*2~+?DtbWqMiP3evO67c>>mcyWOzAr`xao1E37Laa}i@Qj-Pi z&m1{Bc^eE+9W8z50%uN*Dz>)1!MqZB zYYwznBzuxDQ4;O&+(2S7BvB+YR$rNSgs_x>B*gXqiUlfFLMZKw#5|mzVxfI&iQ8!` z$;#{cL^3f^igHcA=eazX6mNPBuKAt5i}E1;6*OZ1Sg$!+F8eUb9^JUD41)3r;#Txa;TeyU3>xBN54>Nm{Vim~n{iM*v=ex+ntZ z{)1K?(eg2SpIDC&%eT?~!5|nJgN%k;XoE$IqdYSh&{g6JSRu-?f>Z$>8>bMN{+fvg zv&PSV)9BY1I4M2#Dr8cFiQW#6Z!ZTyW3)y^!lej9{ToaFSFkOeA@#Is2 z1bnJ5p2jfL)QK-b^?+{P>@p4r#!n$@>z-RaxjR33OIR{H-qzzJOTqG&3u+D!-nmht zyT+&_ZPMxd(=wFiK#f#DLnI4p*7SpD!q@h?cI0&L#y&aJn=3u9GgeSmOAqv0gZYb)6h_2+~qawU^g)%To~7;;$Yh-6V9slCxq+P{x1nHMeDN%D!Rd7 zQw#H-3UuCkkn!eCf5{9LQ?``kn_7Oikx3wS4CY-z1bxy%;!vzS))k?*xO@S~r@}Od z-P=wlpuUS~H(*dJ&I6k;qY^x{ic7<&(rNAxj+52}?TN)L5b*Kd&ZFFuoWIO=fR#+w zjt%@g4)P$ldY~CTt{a-MI7jNb`B$8~y2`9`a7;IMH7W9V*&;FZEM#0@KM!*HCa#{h z$#!4+GVUURCh^69!69ETuYO*WKlsIpyIw=+hDW%o9ucEkWPbKOrl#FwNgm`iv$@JG zUGw1lt*f`OUaqp~ZH43iP*7M{6z9wswEns7T&Rf3j5uNZ!!vT|5i))X{LNC%G_T&n z6th-Tjlb-S1@9P2qJq|blkYGUhn>#+0uJZ~7zu-GcHUi|p* zU{?tlqG{zq2zM*2R-b|OSUQ*i_i~#BmX>};Y=(4etokC)M7M;sH<4eINx*+aQcOZ3JXO8Nt2Qth!`)^o&PzCsI1i9?TeU zbGG~Nw|`?ZdK_YvlpEvWjAYUAuJnXho*cXwe_zVwG*Dv*KgsL%Zh+4?&vuw z#g#99av&G9o#Ei;g|d0kwVDDRZQn_bi=_8qJ*&Hyh=f}V*PMPq97SdKefXq?-q=v~ z;mZ;UzKtryjn*CxvysOZYHQg$G7X@qZZ8(Iz>7~sjv5##Opjc=(kX@Djgab-2TpeBa~ z;LOO!gK}LU>JZq21p=KfP2f=(rlZuO`xqbZvXlJCTm3W@(2ZU6UttRQ{+He3-tsSw z;KlR$g9Xc+9Z7;fru*?6!^#of_Pwa&08HLxnk6ve{wb5>oUOCZ(9;I}J4zt7${QL_ zv(Q^%0z&O!`VT8o4j-yl)#7rN{Xg80!sbHJP0Ry*^-gUFk0BE8I=r8>Qp(ocEav&Ap9HGU zV&}RD{NYvL8zrNIY`#?!ZUs!d(c!7A26rn({!??FzlY8AZ=EA9(*Ao3m3z)z%=Ww6 z{Li*c_HE3_nE8v@@q7V#aX>i$$qk2US)T+%URI2-d0}D@?q5~oIWbPYt1;*DKHP2t5u7|2clvFw zZQ!CQRGCFVPan$}@|)_f3f1y-HBp28d+rtc3D7#%2o7eLhsGwhc=7gvC@VilElXZ` zh>3@_j*WVpi8=?V_!Dk?!C$HH#E+mE+^M&*6{_rCKj1p#Vsmh+WGKrK=M4Xv4`tE$ z@G@k9){)$@{y}qqjp)BD0DSa0KZ%Gs7sJ>)vYlhHiq*DB+5WpG zU*SIoC`cEIWhzm>JOI*|7FStd&xo@8&b=QAQ`P?P;5N4C;u#s_{15Wd@B7n4W@ef& r&o=>(OzR_wULKr!T<*tJBK)aAn@;Z$8ci#kUA6rNk0Km@P#Ysgl1Z@15-uIGN0 zoo4*ECI0h4xYuURrq4xApH~W}=bJy*3G+FRY4Iyh^CNHaE6@KU@9yH@@?1vgN8U+I zMeI5M`Z-Ty@rS(0AM&OSPCv>=JeT3OvvvJ(uOG{gr$aQeS66*bVV_q*fHOb^AO#Tp z@%+#G&&!)!0D$`n0Dy@3{g_EA0MHZ+0N^hDevBd;06+->02(KLKlXc@I2bz_|GXUJ z^A_CP8~`{k0sxS-0RW6i003V1=eg(2U(}7{`4r)Ez1}?kSpaMSW&jd^48R^>3SfH9 zu>n{BtN_mEH%tOv0tP^}fXM)(1HhL;39{mbi<6P&poU#GlR!Y$HTgh%J~2Wv+e-2+ zzeIZEx}Sg4w^=;A;u?}`h23|1xDPz)X7dyB^C>;F`ED@%qD4T^r610jK) zmsON&(1Y(NaM@S=p7qJ_DHK?Bu?AS15vl}Lg2;iUz`>if=VBmg-=(Li8wH>yh#A;r z?lv`fzrB0U`!TJ+7^RlGfIJ_dweqAz(5HG|6>^aw>EM67+Evdhi9^C_b0U z0_{G5{NL;ovyP!qjJdha#x&zr~Er9kwhaf57^Syx4wQmlxHhfQkzM!pp z0O;b__YneQSPWCvB`1{cYXpLLN`83rgpzGY_IPrCa(!~{yDil2yKxKL_ zc)NDIb_1GwbOkv+E%%4!gD8R1Per#vcWa$%G`{%v{nwx)p-s>i(7UJBhv^5+o9Gkb zbL3~2iF=BeoBfshHL_=tm zNitnFv!UdE`b4BqbVM$;SA7kkPlxaFfB%`Kq_2b?siRrH3K`>bKRm8;^O89|U(RYA z8b*VNtKTPwU5aVu!ta<9QMWfsq}gIQp#Zqei-!TT^T7WdOV2No;G;Rn6h_swQKtG zDpzYm{6l*%d{1x3CBQ}y0Qw4n%b_Symq1{jt?@`yZVv;s&v+E5V zM(>s#n(BE01%o4B6vguV%9*@k!(js^Y$c31=%Er}0&Y1`UP(V-HR-rB5Wu-~Y%VeO zXJoaPRfm1;hOTL_U9Y6^hLWfQxGOG5MvncbboOm55egr10E4Tgs2BvCsOd;Y11|0t zcZi+=HUk=w*UPf&L%ipkazdYZ&ckb)eXRv#`A~0Hv7}7vm%|E2kVYO_+<~P6n}^(( z5|?j%ORczQW5pOxWO!uJSuw(J|8QCA44b~Me z&UA{te}J88X*5FK|C{s>UG>1)TjAAlko9!$ZA#?#&vHON{l*!!nftUX35n>t{3PL* zmNJz8RrmVqk0_eb6axQHK66=0QEe}erhhdVTHEi7FJhv)@zLKm=ifE=?uF#eq95Ng zp8Wsm|LfYpYbg3D>hHSYe|w&I$R)wvE(N01rDai7Fn=Z`{|Fn%W{rc7E90Y-$O>lyS(4LrmorR$(NPVe*~L|+6eu92eXusU)b}1x~t7HqZpK)wMWP3 zBAFr)^Q|8xNtbCf+BQR0#tC=uUcor2TL_;&v=n`l0zS1%Q#%k{uc9F6__gwSF%@Pa zhyALz%a)Bxy!qpe`M9{DrI|T|qouzfQk`|GS$lVD+JQL|bzP&<+qsdqxwi}2F`T7!p+S`fBxGQ*3gy53>2ms+`2Iz$?3!et^#G&V0u;Iv2UcUrWeOLgZ;2k-a z=R?|^R=JI%0Yi_*Y3&}eZ3qN5=2>Ae{sH`a)Ogvyj9zX~or!~p)8^kpMX5een&_*q zEYa-qM<*gCUsn8VIG;*NF4c$WWd3ns$(>I+(!V;Q;R%O!7gpHcTUSKcEc-9|?$)Th z)BnSx55LSYTl(@3$Ut~vQ?>eGsvA&WzqIXI_*+~uN5DvRb8Pl|$JhTfx2V$tBOGxw z_)oZaz#{32T6_XwBzNIFf&KjCqpx?b?12}nzz7m}LfZPDz(Q%){9oMa&ByZh2K^hl zvQq}=&{4;}FLKhT~bvndsN-S+a(1!!rnixYMY+T+S7(kZq@kI zdbKSdy@pAWDHf(LR?yoi^s(E;4Ir2XMl?fR-r~R9q(cILyA|V$)3{A$B(vE`6RWXj zjw_sjcZa?G%hpII6M)Yid0`yj^_ntSkzzwteiJdo5^5DJS*q)`x9-{@YwGm;SpBIt zWLJ!uqW1#t?Z6@fV$L!Jkn8pw7+*%}oQa3Oz^$>%W-1wWZ(yrNNiP(G;icdLawHOnAJ3tb#JDM9-Db{^UTX z4A*!o0+E)ZsBnKtz=fXrcz97S=xj1KIml41cd@|h9i1bq^a29Yg^LSZM8>Gl$Bgb{ z^(K+O=YqfjF>kh^IA&Cm6xsN_T~s0JV&N*&hihOJKsX=Ol`cthZ>imBCFA2?1~{r9 zB6!eHO|mI^v=gkf3y@O2uk%Bsid2<&1)o$n9775ix49tSYlD!uSQmXa&sGl42hKzK z(wKLQ<81j=oNn%OlKH#3GXvj9kj#AG9mQR@83(rmqYxC!+)`H-l4=adFW_R1Ym&1)5^?BX-vvxcK6S z2o<39YT6K%aJ@;{{gDQLlyLByF6rv0o%9YH9b2JxIn=Q5b6H_UQ-`t0JU=@QxAc25j&uQn8;Uwo@;i1UwNkAqhFmcoFK`W)Y?A%DMW zPu?E#gGN;dro_^mdYuZHmm&?@ATh}O*+BZd$pB`R9EIYAqB@z z`NT9{5=G_cMIWD&yeLJv(?#16taL~t@y!Tlb~UWSE{>8ze-+Vg-SNWlr&mJgseIfI z9MF$UIsED!q{1(nbF_y4;?R`q%WjP-;TVaH%#(s{V+z2}gjMCJQwG^)N1~t-utOu2 zY#Dd5WE;5_Vv-I&4Pr6BI~SoANLFJco2Xb)a_JUMpnLVj`>>48P}>` z4{avTth3S5E#ebIt!mccrV?0YRSgyq`u`FR!30Q4$973%py4q@&6V zwS3~(KA{(5bd)C((aM6l9Eas?zd(pRrkV)(YDTX;o#8_=(<|+lO}Y9it`qv|3nE-8 zSBzF)W}l);0X>*6QJp#IbeG;?FLhY;ygZ(~V4uu*G~3oywv#uzBNZ;!&(*DZIi^58W9PY z?iIxR+$MEGJl+SpRf6%t@4;@TbU~A9d(6Ezc*)-u!CqZ zYX5pEK9d80mG1?%-0p6eCKk2sGF(u19fTg8LI|fgMi_q~a_U?dw$eB}B!sSBG}V`P z9gSN}G6}rgksX)(TzGBKbs@m(Ua6O5X3kv0byQ#X<%1B~EkxqTLM4C3nT& zV-JOqvZ|t9rvV}L5P>w28Unczl;gV!@g24x0rj`we#0RxBLrvlc=^=k?Q`3oqG#^q z!Uc0rG>fBF27(9<$Hh_lZsCv{TO)IZnRG%UYq+th3os^IvQS;FO$Z4nn(q0$D~4b8 zMK#K@a8Ku#?DMC?Q9P4fiE)%YNA3zsG9N5s8$aj)6ser!MwpUxIz>aycNx9v3J=2x z0dFYtil{cvZK*M-i#A??=9w93$$J>FS1?(HX`(UZ#EZ4!21+JjbJBMm?ZnX1X98B_ z8NiQ-7^KYwUs=U6HAXI18PjNh_mGWqP^;8p&u$Tg z4{)d?J*M=#xT+%Q8(}~+rV6{QRelr8Ghu6&8)8D?o9MsTidPayeh9$EhnfX*U+dlWFYPL7b(2WbhK6%pJ^ehWoI;zo+Q-qDFXf@}A zw;X6Ckly&dc5X5b{;p4OS>kFG$W#!%1($UHPIbgouh}&uv3wFpWkH$4doQgW0cLy7 zBh%>z`w(h%Jsjr3$>^n{L$%=wgfS$BWqloLx*D17#{_@FEXgLvj&Ie%GA>!xn2%lZ z!?}s+;g41R;dC8dOb5XB@r0UcCXOzVw-l8)8 z99<@7yZv;fl z44KgiVEAq?G43I0yQDvH_{jN2M_u(=Y7cU-(-uQ!0fPp?8HTjJhdG@{-Ysi&ab(BA zIjlDa66fsY`dkV)W)ie7i?bKsHi+xc5?Zd2RDVp!4>W?$ zFz_brVLTR(`x%_=JQ~mA;uIvV$^;vQqZhvvsm&Oum25ne_=Q zR4C=oewrBwM}_56t52oRI|3U%@`D8lw?Y?-mpD7o9SvoTlWWE&iCr*oet8g3`v|cS zjm%Q&GNH9@8t#kri-_=sE(wfh^UZB}FG}@`ppQ#~FxOD5olhOXBIpYU3Rh(uBAPAY zQ4$D#y}fx=h2ko<^KK-AAmb=rDo$|sbWe#FQ<<6S%6p!~*H)owD>yG}LN@N~oQtF> z6-=B%2I#BS7hQ&>le#obW8MfYI!+f;g?2ES!J$$V6FSx6>KI!AKJ_RpI-Ksu06?(Y zp&>J_+YriGm28p2cf#OfX`(Q%Tv;;ak;VkCTE2+Ff3sr;yK?+in#PXVGjUI=AqQ0+LqR<7wA6>b%3CrQ^!UTIOf@_Oo#Q zMKVx0OuRNca)Vc|y1k8xvWGn*S6wIA6LR#4*(76ms`rnZxN2~;%`0EN29wc9n>pFj zeE*$LNTD|;T3|5E?KOw3tyGKe-o23NNkywK!Ybm?5PxzOezF-k6ImNcM7jNe)q6W^ zNIqL|UZRwG(TUE4N<#miAKEq4QVwFbJbL_l$XLk_<83Rp(!sZxa+8rT*Ln^L&Ol3M z+PjO#0_IFEg3>h$X3z*h%Cc}r1zwhNPy^Dq2}DkVA%nR4xE8-n2r)8(p}rBf@wl`B zf_qq$3D#Un!1@ijr0qS0<5ThuFOCTHbKC>`J>L1}2j2nV(O$4gEuG|3bxYF+{$}n9 zbPd85W@OZyL6)gp1j6Lmx%yvnEpl`PX$3nvyY6jQCu{1?z{Xu3#`n!UhSV1@;3}3r zluEv|N79_lc?S~t`D{VOak2^GzqzTc5lGSgHK&|{5L>^U{)c39<^1xE^AnU1c^0V2 zKo*YNw7k!$XD~#y{FwFHkE!v+OO_gzeT4`I_+frN3E=o=hIxdLE(ESwVHhq|^{;>r z4c-)K(C_2l3q;5WcnuA_GPzMbq6^@f(GKvxm_^axny}o|5yJSqWhw&RBOCFw@`u{} zEvv)6+G1!195l+CF)@#!bx<12U`aQ&w-K}>Klp&Ohn+}NN>kVqiJd9#WlFM0tfN3J zNQ-_FJT%A%!mYKiPe3F`$KEmH`}`^9_BIJ_iT5mFCr~c7rvn5#*U6hZsZqLS*u_z}G(2@mDPI#seL$ ziUYX`Rj9$Ud|3Rn)_=b?G3&;|XFT&*MPTFa9`-(9o05t^IZN(*&n*vYoi(lqVP&1okur~y zzC#2SYD(*|NO1n5$=wrvW7Xdb5ZR?2r4j?Tp#Y6uiD351{Ks1vc_J=#f}W77E$QGy9kg%Av)(UzCxeG#`pkv! zDf~uUn06|X_ddN>5&R&2kdz*w86hNSMAg7EosP%WA@J?Ss&q5t&D@l3v69*ahsG)E zu`k-cKzoNBpn@;U(iHNo^g<+Ut0KbTC>=AoDiH?iWyZ)aTXH$dy z63Lxd)P}S3T5s8mBMsivg|up@`AHZW%w>yemvmArE$gc$ zfy4M7w>7NdK2PISuL7!#(rpL zQdTwCKf?_~_n{vR^EblH$9ejru7$&Dxa&vxcg)$Nh+de|S~%3WDN}~f8M)G8!EY32 z__Xn`B1`2e5sjtsk8`vt?o2V1Q*m#kMEj#XVq_o(rWs!L@CEj%~0g2k7g=PcC?!%}*z0GZjb(J4%%DpB!fhPAle)VmBXFB7+W z46%u=>@`!zAeq>sxix!cM^h{U;kUHhQokQWTaR|23FEVP;oX?O|2wbvR}C-&8Wes0 z)yHBaC`V)R*Y6s)>`R7g8r*nUF-XNIz@%jx9Ku9&0qK)E9d;hjL8c=l$CP@yG--4p zuQ2U0zL{_`qg9Ku;I7rHbvE%`_G6J%3hW0Eh_%P@)4L#5Tnm2gAE%(G7I`6W%^1Z# zNKz%j-l%ciJu@kh(3M@>Xr}%%PU$8JNc=A-)b7@w7}#i%HTvv3E1}TVKg2N7;J=!r z87QGzc^UB4(H}+ZLHC*FxzhTfYPfww`w1GTgYSNj>VG5XSampcj{HePl37Niz{?DA z63<%H=yZ==uJ6;~{HORQ<^3D2iB97C%YOgEK>yqHS19&JO?TXX7laE@`IdT=YPV@z zckV}THI4gCc9MRUTLnt8+9ZH6^0*gUOZS>ckmDp z)M85glal>cCFjA$_2Jon;L_x0dv8A|h;!Lp0frwdnSOqDeYi%V;s1oo?ZAC#*>EoS z&PZjkbBBV@u0F^x#rd@Rp0jlM-YVjbWTX!|`&amr&8u>P3azC`+TqKo8#rHA%HF4@ zbstaLZaG&5=C#`EZtb&DlfF5-$t(S;`eGk*DK`jwS>>qO_Ak|Qu1d{C-vKEE@8QGEVQBv3n#eXtFfj9FJ_)Ns{k42Q_#(mUS;QR(BS7NdPVq`QIcHQRv*!6zO>C|nKtGT}DWfJ&b=Hjx3#UPvp6%Wo?RE zrkpzsV@>}3yfgLitzD{~M?+}m7RX%n{>9FMLk zYY!AIZHlDxX#Tp+NEL(=@z2A6p7n+2?k;_{)C}h(RJmb#>={$5{3_1Zj41?i5H4EI z=6zv@v^GtVj8JW|7`SQBU_nFQv_U>Yf@;noyKThRC-Kmz)ek-bG}jv-Q*;!Km$MVv zISZn^#rbd~C@yVh!#*sptSIsv^%NS4Rd%1jR+$Bv;!7O(o@Y~2lnBfpyJ|WFKJ^bY z{)@47X27=KHbQfsaIs%~0+&%@axhi_JuH}wHN`_()u!BZxZ#2)=2k~Cn)#f59@rfUX)+nj7)u^G>;4a}nOaB@CSn}JYT&Tx{xP}|BJ-u1& zDSD@ME%f8la7E2Jk&2*=Xtsj(dic@GP$R90z)m>Qc0%6l9^CS(a0EN`UPU_#0eua8 zcbO7F8B)l)Tw4NQPhaEvD9&6`x+{=<7+l37RT{X9u&q_lt2OAbQhzNyIW{KB%oq1! z5@hd^8LalDAMcBghd-~;6m(M#aqk#pg{m-uZEveJSNN%^*1j52=4?wV@3UlWS-Uo2 zD!X?8NphC%G1O3^nBggoc*!5?_6Q1dGGV922HXwX6`Awv4p>VWZ&{?Y3ks|7_Av5TZO{R|iVWR6oHMo@l6h03>_ zWQ!l{IWN4ZNwLJ=X^wy0Y@GZpN?~22zTk6_3YxD*{0ddLwye#A=TtbTIaEj?jF9O^ z>e$-(Z$qq)mvnv_hd0jwm|akyMGez4N7iFx;o`K|p3ptOMg2Af`l<`k@h#zaD-<=_ zx%|RQME^qGm4i~bA@sMz;hin3U5uW|qO5R_nm#$b5Th+#4RPh8TGfRXWYSwNVd+!e zSKF3yZ$FUFi}h#4zAP=fzvU4K3#BB#1R9o)F~kO$o%VAPreOCby=-A3ccJRii<_s0 zTe!FTNq7wD+B4_IWJ)!hcH3>J6T>iI#itTBQLWWI@g@G-Ppkt+f= zQnU4;zT^`JUOp)^@^N3xks`RSZL34JXemR3&n>c9M~SA@&sq(_Lhf8kUA6a+um zbxiODt`JYEDX%RlUZe<1kB1jD_~kQhqS#UuiE-61e90D7OV_U#peIk<^`A3|NmXni zEVHg&E%7%c7UK5Uj!11E)M2a)O8&-byp>*sG?hB%d=Vq?WdM5L`cM4#Yy*zuV1Y{qOcl=z_>Z!J6b z@nl$56vxfX>rN`OnZvMaT?jHJr|BCkYI(iV1Y2Iy4rxrY>|_h{uQsy2N5LKQ*Jk^w z2H`z$T!A6jgN-Uvbj-S*QgmnDiiM{8;~WH4Px1(}%znPR`}eF6`1iE9*ArULT!Hi> z%jmjrmjn!DEgb$x@&p}4>Eb5DLj%C5UW)7f)Dkz))|nqKAB89v&2BV(LNxseo-WpK zb;a0>i)cqbwq+PCVn(Tw(*Ok*Qih2?I%Q}tGa()u4?z){*FTG4ZzZ#q03F-AuqNy? z+#*a|DpKkIOwFID(dcJp`-c<%tOFIjBoh~sFxf1NZ4Y{hxrj4&o17tqa;`m|=0=J09Ua$t;h5_xL^FIFbZtJnF>)vFD6Ki(K*Z(l!-1_Zrta!4iIbX^MN*NKizdnxR zyoRew09J1EVHt0$2>g_u>uiQTNw8%l@4{np*Q}@ zSi-NgOQ46oS7OShPiCpTyu8FxN9*nIY$`ZWMaqv6Z0`k_=UyT>L;s3mU$DdK|C#|X zJN`UE*VLuY9_Ie@H_pM0DqlG+IU$*ZBc&ZpQt_Ti;E|nh9gmyz2x=Hkn)GPb2W&ub zYsq2MY$l=AXr<`;z?`RrC4Y0Lqu3-;RA%6g^#dZ4Hzm*D504a==-Apts}19qel?!0MrYjQ*z-D$gbG%F1TXlWCo za8pBB40x5eD1Uv!ukgsq=F@N~mHF7>kR&AxTYpAQWE7O-aZq9r9Jx;4CO8I4+f7e& z5_(m5FRYF*Bq_reoh39?n){Jq@Rf2%Q5f%e<|ezpH(z0mxG7FO1s~@9n-cWn4}^B- zzR!SUWc-8VekPWetzRYBGiL@Ou6@(L(u^A)2$16?r&D^+-T>UXm~Ow&PufFA&V5rKZnJ4! zBe+9CKcTKMo*sUq8Q5I;SRR5}}A)e{KjBGda(t~X1 zS`Y1(kCV3s%7*bteLT{*?wl&Dn&RGI-yQVJ|#3>pYyvnOaP8>EiX5$;8ob7)YJ z9VB&-T9A7{ut0(xoc9t`m^zWs$i<p~t}A*rEJE>& zS^vtECqJ5I{rlnYf104w*kstLc!?1~LS;oN`kP1H9dUmw=T7@$8s7g9BU;n`$DRK5 z&M6Qroo@hX&+~DCAgL8GU=tqBkj+;e!{@oTKNoNS1@}Ml|41P=ihHFN1vm9P&0oWD z^Cf>QGHj4y2%!BYW!IXiN*#Td_Ce*Vlcz5*ZU|UP1Vy3mbt#howta(z`anJH&Ood3j|U%JLgR`B+}JWD`b?du$ZWQZ=PHy(JZUbX`O3y z8K-t(TOcAR=1;h5FzG5kFW zyyfdXdDs1=QH5$IQ5m<0ScI zr%+}dl9^rAv=9CQo$II>kB3Gw2IeX`H)K~D;CT@XB2HLD z4wpAMhq)9;Mzq@Tm8>5Eg{`-dpvN!?v_8x#P<;r;f|D(r7aK9-<6q*556m{=0!LFE zIXV%c6Az(^RFp)4`CvTr;vAa*LlX33em(%24H#coD%clX5lqShJHLh0E<%IxcAD{u zq@Es9w)gZ`gXV4<0+C zVMBg>tyDsVc;0?g=OArZcrc&QXqy=d*P;-E<1@jOrd>z*x2n~MN#K!5w}Sysw)^R$ zq}n|Qh3x|Lhv=LUog1Y>li!?Zr#mWNkX$;D_OoEs;>Y95KY^1;+|TTyG+8m3?91&s z4D6&N_lzi?IJ{O~BT`Th_u#`7`IOeR$lh}B7VTkcf2?Rg8p)Y;4IoyLW>#}dYWU7N zR%(Y~S{qXytl^3I@c2>%qryYT!@Fl5h96$-!lS@fy?AUg1oex>=B?TMCLv~QWfB+4 zV%)w-&CBku;Auhc&F8j~*7VLk!yjNfSNbBmM%;?&JeEg!U44idR-3-LL3>dFX>LC1 z|Mt)`^B{y&Q$7w$(H-5*a-cKwu$iF^(sb^Ap?EQcxJpw3eM%r z>NB!pJ06qP7VD9X+TGD#CZeml%8}5VYrh$XN^sx+r{5Q?Y}Kg@zQn=Kq}_$?;p8&c zehUt)tCr2d6Tm>OvK#%1YP?euSlpeasn(E2IQFeB<@S9iZ3(C9u)5n-b4487*Ls=q zNyps?sx2i1jwdy@XISbiFM=Oq0>5ez^Ve|=6ONW(3nOjx_*zjFQHOX3r5iy~zV>!-u3kxFG(ACvV(?dcNnO1n-A z>4!8Vxobl>HsA(t+ua*vq_dZWF3I{TLn9CqRE|4baYKe{?LHBJ;|0gNnIY|pBNsi@ zY8`O0Klqj>(LZRwOT-7^O-H|XOD%u(&h8*>oR#@~*60tm z=asslc!%3u5+R}aQAJ$uZKtR`1&_k|FgHVdbmb@y#K0UXHfx4wFxGnl$%_R_l4_4M zmXlyoHf{@e*xgA_N+9%tO-a@Uf*{v6)ysBj&!IY*jMJ+3M;cXBoPA)RZltG|u8$k& zsGYXhU$~tHgruj%bJ?;y7PK*#&@*SeYI@1wpDoG-P43s2-Gse|fN^~{I$d>iigy1D zZqsJp4YYEwbLcDi75QSfF}^~$GJQi%C7wE)N?TtYcbje8~ny%I8o(chu2C#CV|($>4aN2iNp;?wTwKuM@!kT9haEW54J| z-5eAr4r75j;fwf(DVkt9Gg+nV=r3b>ZSFvj4RAX;Eg$|oG^`s%HBQI#Am|)yK~B4- zs=xY$qxuW=gw#C%cMoRkr_UC;GeotHMC=R*3RO&a$qoE3+8IX`x($8D3H@$7K)|nW zd9NO-m-Vi{Vl~HZatpZyO#8b$=CBrTm>44mWrb(ZTrhe+|4#%zJBkku5kxfNlP6k8 zL~`6S)d3o@qqwg%6iE6!zPUR*_z4wHzYHsHqIR)|A;oCOw~QoM9K+#U?Tf0^htLj5 zwZ!Q9o*|!ZE@KHh0j^7F{?+#OaBI8!a41V8x>#0GwU-`*Gt72Qmwgg>AJo6$rJXt> z!6ZehG~cE~XF_V3}n z0u8vyHC~@S6((-e4j`HW;ZPIYY3tJys632P5^(uBx(rYsT12wg@}&6rt-+hKy(1RG zKl=y<^a31vGwUZleRtAqa7@{37AHkP+l}70HUBW^0HJRbAeu+Xq_E@U+ zl4FyRh#5Ctl(1J$Ki~BWQR+D~6?XO~fzf8uZ+!B2Zg!j6tY#mDXE2yIa)53P-to>l zP&>U%zF(uGOI+~dKB74Z!coL#{xDQv#yFfx3vIJ$0g24^A)1SqQVTt%oHhI5u~7yj z4EteDZcN|u6Xvr@EDS<9B>FrZw3uAw)KO>Ahp{?rtT4p9-wE=wZ!~~Zqa#>r>sjem zS;21YiTn*-Bd|Q_nQ&GRh^M80&Y$!bhzIG5hefS`R2N<1%gppc@02JD8qH|Uxi_Uv zsWu;&B@1}^ZX@Tm=^$GALIh4AMO#q(l;d9^l1k&rjoo(0T$a{$80u4;YQ7FdtANU+ z&GDLPCqL$JEESm)%*+UZA*?`{c6@&Xnlx_Fd3)XE&&BC=Xpt}K9jHk*sM%oUE;uXT zfWOH3hMzB%0t7ZR-k&<4jItN#VO5+RA299UAPUB>^$r(j*D37L zy#&MZsJ(~uK{GrdPnL)ezZ8jsJ7*46unRFPjBUbnFRRX`?*@g$BnHST+5a^P46IXk>ma&DfHjTzgI#SzS&Y*!>#+0{ur z>g(_n7DO4`a{J>Zty~L$#b#Wpv^Lniz5rcS=8IY=6RCr3>D$Emn24;8vuDvkS`D>R z(fLY`?@RO(yF_p&MuZCyp(vbV!%MN_13y_5>Q_&fmQq~B4pnUWZ^XZ9o{)WW`XGO5 z(F=gOwHvbwWSkED7Ug~4M1Dulxsd4ZUq_*vFKikd^1#$0K3heK7p;%X70>)S9|0NK zEI@6!bTY;tNuF``v0SS&jMqaPOi{3mxbkVDE0GG5)cWN*zR@=e!lKJ?=~wc1AoNGM zse?WCKBXuA!`O@&dcL67<8hj3GsBH+SbB#Kl%h!#hrt|;Pq9?$G;5+S!7$9U7QNO} z3EUkn2)C)mNQOLA^136b%e|zp!VyK{H`ZrH>_Z&_v7+TgSC&4+t+O>OTSA1Y$UR21 z@d#CaVko{g#QfB1Qm&0`{qfjL1GD?Al$=cAUZ?h`p>-LmD<2mh7%&oth+4i&mQ zeh|xT3h71%{qF`3Uc6gOq|{wc#9&+ynlI z!)X~q@9GoG+YFzp%vdsqmi#7oan@_)5s0v>C*Fv(dNc^mSdtYN2n2qaifVQQrKHM$UQbgl|jbh8_yv z?S7Tv1pnOyOk@ljhxuKZ+zZA;ARv`$0s_3Gk-Ug|!Z+oikh*mMy~13oy@&E7;JvcM zfhUqg|EGHGZ=xlNVG4=(!IsU49`qF?wq)Y8uh6d}nnex8Sta&>p}qLnT(g5h_PK-+ zs2PV}4O&wGjlHH`l$Zq9rw=DbI0cEEaE?Pny-HrOgz^0SD4@5)Y3@TvjO)Rp?FNQT z%ZawM{Wz!irk15sTT3si#v+Q7McmayUHjkEC^F%aE=r#)aH#bnK^3^ZRVuQ;U$2oF zUf|84EYNK_!uwJY<4cW3k`RT_jAU-!TzVP<8?@Lm!hFKR+1(zbqF~J97cAAv-H32a zax6PyyeYOeEoHrUg4Gcd8l>E>QFxOs4rQ$SY$Q(aq#{EE3U_Wx3Du(e`1@i`R%LjgYhV3oM1F@ zn7fktK=F&M^nrdJ-kT2B0>LUug*ZFwcfJUSUxZ(+vjg2(Mb%%4lJk#R^dmp2F59KC zk^wN9w5AARE$+S$rbEr8Q|{!$dKpgk+#%{%Ke7pwY*v5x?88cimGVSI)3FkRTmWTN z@HRPb2IsvP)_s=n(@N-5cf*cXquqhoD4)dgggmc9nV@M=1KA3iGY0wL=1QvSwzY-Z zX(w)0oR0Yz3v7svYfJ>Z_l@;rna1(kIGp#g;LJjHh!F9k>WHBn`+mM=IrQ6TPh^)n zzN4JS8T`R>Qc89Gi>gIXks&V_KMMFObM>I#H>Z(jg6Ih%Qq>ARN$SHV*7swFcqTd6mR zh`2NMO>X%JL1UetzQfUCnw%A}?E6E26`^Bz(!vC#pZhob((A_E=7W=c75wHRefC%5 z7Xt`Bv$m`Gp+m3_1y9A!_wq5r`2)In0UD+AXMA6pvRqS0J%8Coh?3y~A}>vW8)u@AcXdIQ70bbH-Ju({JP(WxBI#eeb^Plf%xv zU!=@rhz20kciTwF`9B-ktT^2y9MWR(^$CY*Y)d2!@JJ-&xTx1_29Fj+`zoaca8wbz z$1zqUZ?Vz(C+883zIlF?023Ho9yw?D)HtV7RcA3Sn|eEfz|bG(hob0S%UWCSEVpf_8@&CB zs~U&S+cT0=*tArsB~Go?MD*n0(Zt2T7*5%@sW=-=dURGmd427hO!~rnTy1lPJ&(&M z9s4a25`|*yQKN0dkZF`v4CQsG?OZ^6t$}#LnI++i+okD3ak=kR&Z!WJny9HYWD6gFfm$iyg+6ne%<2W*W^Zg@1N|- zR91~L-lo9zY~eZf9yit(%D0Wm@xr@HYb$2DDP@=a`k*GBB?QhSqjK0M)*=hVI|&cg zsucBtt6AbfVvtkcwYXefLk*kz(&Z@(Yle&JmIgZQF%L7H^$PEsV)kDAl)%X4t{P_4 zjFTEWx6zna{g9VqyR83(Ea-7(mhm2(66iGtj^qavuOnv|TV>IQ5glx9nUOl-#OncZ z3Ww6@@lgbX4L9{%U@7~ zVx}7Y+A~$blrr;UwIYr$+jH5jE}NNg^lJNAZ{A-Q-wU=$XFi z&)%fKZ0IgdQ$;d4<_~22z{^egAE0OfQ8HsoS7Y^)OlEi-U9THNu)HNK_8OflR*9p$RX3DMyD|19;SB2K>~Poa@(SE_lnLSRTX8K|4ohL;{2SN3Zp#8#ACc(_E`2Qj}3P`0y-W%0&*C;83)Q?@*S>S za}84eKbG|?JfT|45TyiGunSo>V&QP>&aN&k!w~giuZ5APEfp{z--2V>>3)Lww6%|4 z-5r3wpCS+E72JP^FD!V+Mm1WQ^49IE0rTjsqsDn+&A~i^V(ecXW&m7imT(Xu6g$8v z+-elGN*R9!g)XM-V%xH&oxivwLw*?(3!J;4SyizXamnA);Z`fBAR<*ef{XkkAlUYy zI$fpA&q+$a0ay)wi=~%y3Io4Vk$;~KUibbka4$%BAGOBp39sy%{?$xU2mM2~bCcnd zsWEhVi8c%~kRS7_guD85mMtsG`s^Fn+$+V^fN`Ax`Q?a;q`Op&*DaT~y={KCIDA^f z8Iy$DVHvrzl6nPWbse<%F7s?AY?M8F&hKx%)g&p~mo^zv!TO7RMHG#|V`# zx>R$Pu}69c5cO7xz7!hXy1B8h)mY6q3Uda1OL*@oixC5dZK^gcbP)FD!F3RY& zQtg8*WVW7Yd`2~*t~E$P8By&16mh;QC9_lI3v8V~^!^5Ew*K7B!YZUI;?A&A2q?>7 zC01*$A1t=6UJASO&P;-z_x6EH&9%*9JBm%^tXR}Kjj!3M*#mlHV-U+@LBiGzAogUv zX%LpNHc{mIWrZG_`w~Gho^Z3DOSINGslWM?javsE%xz)u6j1prklnhonIgRsRdQHg z5lQ!8Yy#{ALRRB9XpiJ@>2fM+&V)?KXrQN`5+d@s)NpGXvKQxYQ8>`<@D>ymQij1L zaJisTFu_CNtZ?%wLpCP4tn*^|fNOw_=da`k6{%e}r}C+u-z%3(yA91RjE1V=8-W*@ z0i$B+-;|338CT-`_Gv;bX8`Xm)YhJwDLk ziz{4NPC}xT2>HNw)?U|?==R=R%*ah*?R$z`ohK(hmH%gVSW#VPDOI-J+~p1v{Bd{H zzvA#Vleo-ksv=oCDYLlsWGQ2l_>7JhYE*Lz(5(WX&pVuuLlV%sj&uNUbWI) z&9m2m%?0Jo0&{9Kr}e@Cx|=k{cg#&*kL21BC;5*3V{~-mD&;{??v}lT=^=gdu4D7W zh$vD`HksaRE*W3dZfw*=sO>;amTe58>kZg|hnmOqe@D&!`||&{9x%kbvX9)}%Yr_4 zCgA@(3IuELUn5wK|3yH6|96mb>%W7*as>Z{0yO_cpxgof8``O6fmPYRT#QRbiwXb$ zvely$$uE~tDtJPS2&+Orgi+~ot^&8G;osNH1z>>IYLb19nW5QE`rIVvos-rQ5tFE$ zuF>{`8wj}W9lvqe38t;)at}ba}Pn@eUG#BuzZp zaem(nV)_vV#Qd=<$>&JlS~i#7?%YM{trdjo=FIjvInPXXa}p4K{2Z@@`g+!}3oAne zXPZ1~OL%j!=LE@hnay(5_D~ffMJ%6ygRU0rL-kVL__JY=mYOKXjSr<0RBe?gEdKw>x%Jz_h6T`bJoXt(iJ)Zn*k5;0gp~_#p->=Oj*Ju4M zySSUy%DTIJ$>NoOv&VP_f*!)%D@S^UH1_h3>l|dltA_x)5sf6fvh}G7a?$m05>AEC zHW(&BGvHP6d8uTL06YX3UhiWcV2oV;;h{yydPb`@zo6w;xFTB-Xcs3s=8Rb;^ z^oT#Q86kCL)K>zFfyAc3j9d%@jP|%boI1zr+DO?0fJF`{1h^hv)xFg>H^g*_HUu#x z-VYHYUBc!FkC`QMNklFS{O~K%GHp}I3-(HcbOeHM9fo+0h7x!5Mjrvwh-Q>0VvQ_D zj637M7bB9Nw43iYI`9nUy)Ia3Df2KXa@Z)0fr-H_U4u`lnT8`&%GJO~D)bh5AjmI~ z6yWc6OFtbE?dzq)ofTl_(rQzxXN~Dkc`>TECv+p=Ex3MZJj@dw&C=D=Jt-*(o<@M# z9|ekn)_$q;w~OHbWTdZN)%{5&sL%zoN??&6Rrbz1hL}PLNPr_hbB0U|fF37YugKiy zA$Y@Fdmd{%V#+NJcP+Ce(5{l#PX9a27BZ-3b9;h= z4&`gOlif`-UrD%EneB4M7r;Y{vxUC@720XnT%98;G1pI&@!{DhaY{kNM{0KJ^v0)bnB)cZC-zW-(?P2m%cW4iJ*)W~tccc@&0Zzo4wTwkv(NNhW zFEr6s)%wsZ9I*S*@&?oJV>b>$LIiOOt?%pM8B464(*s3=caAzLa+b*^0|g8YYf31- zA4X<;#xrlOZjigaOmCM187Ej;G>HtGbaTek;@`lnY!g-ip3oZgxEW5C!a3DQA2h$q z%ujGlzt*vwN+S4YMR^P497Vzhk2uRzZ4e(gl#jCIP&BnavuX0lak*`vH$5k|%O%-vM;AoXenTk*xi#X;9UYAZbbCwP;U`C1PYqkKZ zyfp^{?y*UpA8}mP_CF!Uc72)F^3+6M3R|wz^db@y`(ycHW}V`bxSs>4G0qFU_L|pn2@ra_6GQ)I~Y7gz~c+WkNZ-nl$Cy8{v{}*e9Q#q>y8+|b}|l@ z00XoepRmZtKxFicnvbrp7>R;;MyZ{#H0LB+2yIGqwM313O2&` zM4Vv_6Fbk(cIf9a!Tqd6u|xY~GXd)Qp3!MHTrjQxboS5_$mB+qQ$J|WUyp4434YTD zxJfPV^ISR+ad%g89gh~kEpTOsg$;YJ(^uRu2m%O!?g7LkPzBQ10XIV&BkB(FVlyQu zXv+W&>r41+UinK&#TPH5>*g@G(m~dwMJg&$@>PLI4@ObMwK@Kb;`q}gW-{6(5V}x?X_82_y^1ay zgtols4kt(1K~1QTH1a#{5jFIHYFKV~zHY2j`*vQ~m5MzD7-A4eSee)@gq2(CpvFJ- zc{j$+mDO1r_W%H-e%SbEMv<=LqceXyCnHE~0gmSi?O?EJWSbT&8v>PH$Nt9|p6U&&r z&~A+yHDEST{^VQf!J$(6iJi)z-kPv=WRY~D{Yd%AX0wf)u~@S>7-7l{>;f;NQ-!*+ zKc7Q366$6oj|dVXmv@uWz! z&IWKKqLm3IpZq36?G7GD_!$Ks!sV|S^jUsLKWTf2N)ia$ZZGhp`O4)^N{`bE!QQ($ zQ4j9vZ%!8{6_d*Bw7%-ZNLGq0Y?#t+HJXNs%QiiShO3*u5Bg)zIy_Z}3jy|$1>1Uk z+7>9d3DHuzTgz&JfsMLYCY<)5pEh1sLbMlTd{TP^Vl}-L`UZepo<9OVs-~bs!zOFW zH($^&>I0%TRao24lnq)1R`ql`*L_0g%^#8@J z0y|qZC9iTd#5-)@zf^BJC7gK6e^A<~?f1a0B|+turi=ix*)NX(?4O}ZZHR2u2fvG= z8yz=30lbIhEAsHY8Q2!0Xh0qSo`}o<%No}P(F}tG-Z@|fR3ylcC~(=t1&TT?jFC>I zKdyIV;taZ(}@B0-bk!v*YQzTzVDJb{k3OM(a=AjOWMfVZI(V&B9(%1b4i z>U43MfYZ%!BQlG^T+aZO>LK7i%~G*kPT6m8+1~CM1cr50T*|rT2ywB_ zPHZbqp>d56w^D}T6ESbUoeB^=vaAuBT*EbT3eO02jTtgcw&p-Y`=F-?${GxIDdB5* z-@ZxqnBw94mCk)abQ2;$Bd@kQ$zz>mXqRTxe%kSM)L?e@d*PJZ3M(9;df^I}_@V`F z{#fY{5qllNIn#hDnE=tE7Uj&4qgC#Qqk)fz7To4_7x-E^tCV2)O~Ex*Ng|*8}E%F=zOWzGWR))EPmkID3jSJl*Np5w5Ppjz@Kk zMY97iyNe%%a#Mi=BvO|J7tDRQsB#LUdAfnHz~QwJ0zFzSfj&v{@iPk|J^S5UXYQ^( zXjkQMy=sU3jfkxpEHS1ocU&F6h5l`XH$jolG}4m5GO4E@@ulA*LRw-rMD$bAUYI`& ziq;2YZr^hFy0-Bjsi*yEsm}-)zJz@qQsdGV^L)=IS7d+Fk}11VkMxkuNNNf?`B00- z&OLDQ_^Z_gFXh^Woy?+@ATDBXRg*-f%)_Iof9jfcZ)zu}skedC>LH-{vjHyg3nyP6 zhF}TSiTP|oabSFdf2D2LsA4SPLxwi4j};$IQ`B<@bWPM(Q$nrae^QuYN03)SM&_j? z38Ils@%K}y#|X@My+`z#EhvqD1tymBzi>rnacZ>nO@XE;o&mG|x_>%|jK85H_K3+3 zjdv>lF6r4iMwlaLc0HVJ=$tSK9p4~cnIFm(I(l0djS`9nw;JO)lA%Mp<%6{c2H*9dI+xi)Ykx(X0>!QGKw>>SaKIN5K^#73zpO26k4{Pcle{jfrH@wES;9bvF;+U<`%_uEbE>)0v`Pjop z^0|9nFEX0hV+yskBTlbq`TG*jc!+_uxVEH}%>GO-M)d@dL~j}Zf7YfcD#VhQ1kjiC z>VRw#Us$Fwu7zbB+!o8eCH{CpvjJ3stu6}b(CfYN{t#G(inh8;)7}XKakI4*(32uj zC_-^KBqc16=kSlt^U+LpITlk{t=3==`qN#2u>f4?xIy)T11d1*#e5p6-?Q_lauT_g z{1$yDrXDM`4qYy;^NMy1d~zRMdKwR$U#6c!@F_9^b@-|?*q56LKRpU)d*shVih?t| zpzm7kJNq68^Fz;bq8baTaDheyK(4T0Dj!K}Y*n$P?0TLA1`-x&FL@|xmv3RrMHbr( zXwNIFj&n@DGTtBbB>vt`tDLXMhE$-H%&u|G!9Xf_-V)fCyLe4;X!!nb^HSts5!H~> z?<7+=ZOU)M1n5|Q#<(teBizFhGc51Lfyq!0)UqYp+GMa-N8kS88$pW{p8~4G$e!I2sipmU{AM? z?mE^wz!+@ym_JYssvT&Zl0pM<)aYV+KF6~X`)x%%^HpQB=h|<96!MUnE1lkg!(cp} z8%u>D6z+eyihfI`0xkQXWB_aL&eqlrz)b&#JSnn6FO(BhcvrcKB&nFMI|v$@hNutE zFE^mi#j+uU8+g5zjHjJk@l&!FR4SrBB!q>5DV zl(~oFy!?v<4e^R&PMDP`6?wjr1wQumKSwk00wJ#k0kR@m0aU6NCfG2}NgWoLR0?^G z-NSiMv`2x#W3>oWR%pm&?r+`LJN?MB@VW{gul!2pxj)u<4O3fe+o6A>ZTGw?+^xZX zECLxa>DDAaXu(#$30SlIz2_Bg=fR9H-=Xe0kXy}bb=lue!I|o+VEtD1V(l1Ngy_h6 z%~-!>;K%C{dmw2}&?747QykS!F)Q*;+`*~ipHT=A?2NVO2LMUzfP(+2rWOvQt>8Gj z-jXJ^XZXlQ#;Qi**h6QP!3&F*=?PIQi^VENob1Px1N8{K{AiAoh}xHH`&0mUGr?&6 z&+G`r{qc7v8a0P|m$w}f3a z}ug56fWA*a%ZP+UBV-b1Jou?P?xd2?f~Rg#NxQe;%Gt- zoCUlmv_V=iz{I54SCP%rM%(;gM2@zRSFsd_sRFOdUFL3oRQ@2{7 zvM5mlbs)e9SRcM^M@sHT6Yz|0UgIQ(#F4G~ce7OV9*3+nXPY%-?IMai#(C?*BfNBk z$R(n0l2c`Ej3e!+Sv6*f10+y;Y0)`g=PEEDAs*E!I2|Acu8#xMy`O7Qv~nvH5~EO} z0d!s7?aTQ5J-VeR^k&o|L%6s^Q3++@U3OXCH1 z#HTqtpsx1GhymmP(#jvHtcKmTth%UH*9eBT#Ti-IYmIUx{a2^G@hPsklk}gC!Dfr| za)CaF+|7qYkCYpQ++lRETUYdUc8WkWQWE`tQ7LSBb9iVdPPubD^7`^Kkln0E-$;xe zUqK}SQ4fLmAfGWGv(}QNnl*bC~Q#tJ5^VLpOZ@|Re(@){`Pft}XuGCEhjCzd7N zhG1xiLz?oU;ZcBZVp9#rB%fGalrCt-I*8dbiGlQl>!5_m>>vj_FAU zhLq`@N-hTEpH;2#;?$pmB;q@)caCoNb8Bclk95Ne*vJSDX_{E&M}mI>;LUK*@B-f} zI7dd`bp6;nDV3~__B3@H1P^pNlm-iF-(BQ)&}#4&P_K50^Sh0sWan|lBx&FJ>?~bL zAZkdI>zGQbl+p!CzL+v}R+ z>D#pVIj31MUB^~Z9S4zqm)I+zQAdT=XntE2aqU8Gd*U+ciV{q&)~p;G zq*Mz*pIf1p!{veSY5ss)speEMDa4L2gy(B*w&KQ_fF!Ht3W4AQXh$?EhI7$t=awo9 zhI5`vY7s?grHzDBhp1Q)(Q`%}2AB#fe|8SdVuL>fvbH4p#H<1?JdjFHel=p!!tWs{i1Mf zme|?S3pH0u5h?itok0q~4I`80c?+nGwNm~U-*CU-!XgL)AzhTTEs%q!hP9{9_JClV zrv`<_C029wP^`?qvqt8D8|c&nGYxsSh;%Q>%ZEFOZ8`+2B?d=@vlCYUcct(XG;h*` zFG0AK@#QQ4kGj`{i?lR&l)_i*XC^`XBHjUJzc9o14`{Fl-@wxkbXPFRE9-dgz#Z#5 zdn0ODj?(_xZEw{HD`G2V#lcbeM`b@iX2GSW{3&J!&39kJXS4d)l-%Bkd;qM@D}c5F z9Lmmzo(c1lA)LOlS0dci2(6o&m8S4^eDkjVtj zVzAt?$}Cgiw0?p1NTYd7QioJjMwbFzoMS~Bel<#69dpZxY4}=o z7-7>}VpS<)il&-`GwnuL9ESNmp*)vZdruNfJP7#3P#1Sc=-YkLCy%nbIz-xdvVy3hXsj~AP zJQyLSJc4{PYeO@ATl6QzYQ+f9vi|2f#(KKm*SY+t_@9aen>EP~x_;_2iUNC~4}pNN zS;JN3osXA(HRF$3f3}dFCCg2xh|O~w+uB_Eg7;{8&|(EUq&~tqNjy0(@m86X5`|VM z>oXG7w)rC-Qml@!Wf%GtG&4vM25m0=YJ3%2@}}A-T+p^xx!;2*3v*1{hjErTdv?&k zFJ0f<^yF03re~MZTD3|&D+=Emu9x)B_j~nYmR}6NU!@tRYy)DST^yHUusW<|Ok8Y{ zj>8mu9}CUHE;W0-n1W7If23-26Gg)rZB**j{rFvFVQ4RXOGVzPYHZ@U$TeLLgl1jt zOzfA%ntIafw181M#WSQ7SOaCb1#yYilk)mM3 z1Ul{mRW-A1+y0^7h4bsgwZ0d$h!ze!p0l?JzQ1Vk{%Y$aVv3R8J?82)l;gF#O%*aw z!5LI)dDBaVD)#qE}SX7V(i3bUqid13ujf$B*fGbdD-z zKtJx-ac%~}#%~RPzt!(;$M~%-Ro~Ph$#OhY@)bdHD&`{ibzmWu9hLpA0`9 z7Zl|&hx^HiFZC<>9L86r13$bl83P{uiDsHv44X!uW zg(@Mwz*gDfnjGL5^h5+!c9u{#-y2rjg%3bKNDQ4==Acwa^FinXA3z0o)En`=iviA5 z(19&9+WTe>LU53bnsZAIaZ7XkHi|Z7=XS2Dfnm&NoDlio4T3&C&0YBQN7tCuY8De_ z@vq#td<#uS<^oKwZDr}4ut!rDD149;-2!5izm^#?31yrpFWO^KtIRwh3 zNx`NzVgw?8pZbyblN$02gx*~Ip1%LQLr~Y@6xQIO=e7AL%&hrsPEj(*0J@U3p8n?0 zkWs&b}jXtv2ydRfVQR#V&nb#c@;W$$WU8bh`RuGp?0zgXyF4Mm*#Z`OQSFgz!zh1 z6Gt9nj(i8s9nTaRCMfT$JWq#60@zL(_Ped7GAGihH-xx=w-9yS4F{`bqwySVeBKO% zTb)Mpvek=Lr&&v%pDUBzZKq%yrTOk(1q?mQ9TeN48N-3UAPznSuP@P7;MSq-OVYRe zRBfz?ygkuN7Gs#|ESB)-=^DO*hN_&5Aif5*c>z=i3Y4Ao!Hy|fV0C#>B+fhZY5FY; ziXX>AyuJSQfIKT(n`H)v-{6gV%}Y`|4gqbPLt>tf0M&MK)vZn|?sKF66w$|_yW-+j ztTXcb1r#iNAhgUZf`b6!d8mtqHlcreRkKI~-B&eUh#QWst38n#69j=W+;K+9%n%}6j1&&P*b=)<>y7} zKoM?OWy>#rQj~4J@L**5d10)By#T#z^UxJLgPhmOY8J;CltDdR=yD-KAJUMs@>`49 z-p&Brh9~$oqk$_~GOe^5l_kYM1KZYh3b3;IAb@7ay7J&+gulZ+L^xch^AGs91Joz- zG!UQ(t3W0I@C)62LkqxW*0hDUfYgH7108-Fmlrh;!kzAQ(>~{ey-Id2uqVk$B(HK^ z17%b;937@E?={hr4F}E2m%4t}8*buQzqSS|uswz}Z@Oo&Bx9~K+??p<*vl30>H*x% z;mVynxn5)?}uqd6u*yM%_1vg75m!UT1|a;8k2ORa?^!r!5osT2Lx5hljGJ6<5D?3`|wS)tL!^q z(N5#taELLAp|$}Ze@UR$0da#LCpM36?0dP-Fx6!pqBcul)~AHc+9+}}L`wIJ!Y%?X zCH0V9P*~j3Boa553ih6`JIa|;>qp*_1wqIbU%@lvSMakP&x(Sly>~mL46|1Vtyi7Z z5{6I=suGzzvXr-R6ziYIawp#h$;z72AJ&_`k|~6P-QYKy)$|H%%lx|Ih!^JbJ%aVD z3kMz}ElxJqklju(XuZN;;1A^I_7=unYTs|&1Hl>5eE5U~tB;NuTTs9$BJ<^fDDat( zQ(BdIGS(8*{{ex#t;m*^9Z`9;beHgy;GUj*LBU%4z1+PpaPfbUm)*|~F0uEYHB#_} z{^NqiJ!Y`N_4LTKzk}SML~DToegNPf;s~(|0DcCKI?aIUOHX&OVWCZvOiHQp!L(fy zRx%Y4LvP`)$!JZPg5h!e#FTh^{!IdYWjkSaqnO&l;XNv966mX{e=-Z6p+2`|u? zd7r<$%4#?#|HJg3pf#rXE*}gIeSUma)I#gZBG3+cn1JH&fJA1$pESTBTZ_*v5ue1D zI{I_|G3U$;lXs^a+GBs*s~2U!zW9{pDYaq>f)FF)A<}&(9_5yjk~8en2Df6N#S;B( z(hwk^mJUf+fJJET-o|9a3=DW6ZfZ6uht|I7B^Nco(jtR0U*;$;ln)*NxN$y@c}p1R z`wmQ8J!44yx2Q{1+n?WNvtM{**g2}dujd>v!-WK?hM=X^Bc~dORWHdt%6DHgR6rV` z-Q>luXh0WRpCgUZ&n3ZA-7U~fe66+S+)Ad)a|`ZrL`gVf=MeQHCW#S&t;cbH?wr-` zBmt=SZxF~DFg>aRZ^#A}$9UaMFbVcKVrc!7|1?qNFSUmIposhhJ)AN+wE+;~=wI<6 z{@cSo`)aL~^w1Ra`lBlm+|}F&L&n=0g8jEexPcP|eCQKcQEZct_!@)sWU6k1%wlWf zlYV~oBDH%HCvyt{_Q@>HQj*lmCs2W|8x`H@Y;4GcUIwWou$e;0n}tqx9F_gQukv9j zGc6qgYH>gIGmmj{_rnGXL|SS_?|nI6p~v~8+-mMWsfUfXBE;vc3W8ZVyWd@`<2;24 zU6Nzvrwl!LK$rBZ)_HC#Jm7WmoM;lB>*HLOp@-N76va3ZR3ASibZ4UNi(HeT?9LqI*C3XiAu z5bx-{WPza7~f7aJ9IR=Q>U(T$5qsqAYHt@TD+7(IyOz zg8b>Zj#>3z$FVcOv38E{dcLI-qJr?-b%VES4rENw|7yc~JTx;*{Pb|$peIVyO3mPL z&{qR!*-k+v??uP_lGb|lQsb}0kkz)8FS!2jUiQ9 zZFZ`XtNd6c{D&_8KMgeeGdtKVmG9?Q5SL0z@Lx{w%i3o^(71W=omyEwMXzx=IEC&n z;+aQZd$@MBAz(}hlS|q zW?8^3@_yx(M;AmH6339j@1`+lPC*zChAPjQSAC-%T`{rn7A{Y}Uq4!=FGOY&?;gUl zG-sHvwMkO`ktv>rlb04*xn-qmqe~6UJR#@RpKl84e!k-U+kyh!PhzL*wX|EC%5Z6%$<11ag#MB?2u{&3E4x^f;MUDF{?&9HX7D*G+rdWhF zzGrCTw=(tYuy-s1N+cbvFx>3P8Rah>$3r#d#;_7n#ShnCxYnGLlydF zk#BYQ2MWBs`vROxx)^SX-11Hte$x`bTogJ9YZV2Dzbx|ta$KW{dx#V|`ep!3yBCk#UNpJKf_`=AXsSjaC zJ}AoEvJ)-$=}a|AR9esi;`P1C>E~Q7zL*#X5jjDTd- zf#eo<=Y@S>geKf!^lU=4tPw44@hn%Rhimd~nB9T7Q}v_0;Sb^B?hZ1yepYzXJ@7r+ z+pHjrqVJpi%djN~%0%fOmLRj~wStykHr%RYXCb@@v@5Tq8eV)8xO){mubc>iA>%5% z|JVbjKb@I}-`O%P7Q& zGEEKjqXrf7w#nKv5V-&Lz~y1-=qB$T0Wxm_7698SDCZ3~mJqE;yC~`NG-1-G!3+CK zpVFKnAn9P)K>Ea`+-PQoX~ZmA6FP9%n_N26~q9#UC1`=Rha!7x}ZjWQcAW zx})&7yYB+2W07nKnPMc-GnMYQ3HshTRIU&N)2JuPHjcS#X?bsnvfcAQ`KMmGW09ga zG%?=4UO<`s-(5-n(_oZWSbB2SP;>(S?zE;6>-M672dULhYqe|xC)jwyLtTaabtGD# zuQHDKYQ`U+e$0HL`*fbQBfAVU(UxnDx$VZ0alo}tLNoc02T!q5mQl;4!ZWpy5qf%z zCN@-RuE02Ib8(%h8@;Tb5Q$6Krs)ubbP(W1#7qoJ_tQ(BKY(|Ac@$UL<44#Hlc7ys zyW>T9(|deG%N|9uo&79T6~5}~Hmx#{?Ac{6o<~&gz3iqp&lfrzuF3_jBk0LK+H0iK zVep`anPd1~?fO(3yP768Ix_h&8aSi`UQdI%(I(||-eTqOZuFjb_l)2AjwJ0@?4Xpp3&!n- zO0hbuXgIM{NRDT=tdqgVPu?5oA*aXZyUX;C)*Jq@qkG5)q*JpFifIkfO^`oBshp=Z z^f<@}k?h5aw>D3Ss@is1p*$Kt^Qx_dE>=wO&Ov|wDgW^+w24$eCVx+CcXXTmS*BdV z&#~f8*zN>!e91t7K-6x$f@c{cQ!qs|EXRey$pu}Q>yDSKwF&@PQU~*(tn2LVVr;9( z^)WKK!&D;N^px396k*e|_Ad%?ZF7MU#TL+CW5K7rVP+LM1kF^^IwhpJV&zUPtaSl~ zE#Ze&nSVuakuUPhHs*VM05>JKGE?K3dZEW#&1ZVo6p$4aB@Wr%#lX_At0|*9isHZl zR2L^O3_Y7Zp(Ed&9YO^rqtKPe|9PE#$lOp|{FIY{Ck_$_BQfMmZ2~XVaC=8GUiS*7 zb=YjbS%krPGw`upxdVT8V>5fx)t+*QdK=f;>vyFtE6a3COb)##4p;OAlQ&pQ2a?TL zXDj34_an4|ob-UqO3EuH`?zddGKjnRWp6nlq|A|dK>3W(Aene0Nv?};`nfYP9~E`- z8`t<$&+3_1<_MQ+nE>uH>ah^GGEv=!c`-Q+^T8od#!KlqF|dNJuv*zop>kJq25*YW zJi0d~YlbZxLJP9U=&0wuPuJLhKv9##EN#B3J0X4@3rIRPR7A5447i!z3io&Caf0y{ zRmx6?LVFpQD1O%9Ivnu53b)-vw?u$5g^qqv~S4vNu`Kxrw^kO!*cj*Qm~& zU+0_D@9kgewnT3)VV3bF!MxZW# z23TJb7`bEYWWJanGGaGy;x*RNMJ`g~&cDrLOz|;W)jE!XdF=rv)^~829s`}YuQYGI z9w(bkAZ1oofUeWC)hylF$8sF87&0TtuU3^UG)LlvF zcToowPk~CrFu`9I<}URAkdk)dl4{cU;*(7kF?!FWr7Lb(a?%j&TPoNHpo00Vr@T~X znA=~|n9Q}^hVx1{@vf8-pwaJcG&@)xfa&mW9_P&lsAFIQLt7T^=S+smK45iA?7M(N z8LkxChhK?|3bXhn*rlgI;O*3TTpRN!^Dj>H=&5ds{;Mw-+s4noGl!?mmP-0l2#KWU zMPo?M9|q^mlhX2795L94zrtNa8!?x+@S`)Ul}XM`eKU@#L;)WXgSGDgC-38=b)dc= z%cotIG7OzDj7K5Jwi#5+^yfPLGZcG)q`zoDn0yyw3yD%s!|Cg!88p2_NBIdI(i1w3 z9sTv#=yiJr=eJTk%b^=Ow3g)HB!-}R>>8~I7@+``=Zy@Eu6UohXpO*_A900)y*t+F zWgW$Po4=X&P!K(xGn5GYNK&pVbkD4p;64t&R?L%>1kPQ<3x#Dxp0u>_rjs42pZ~Qb za%lD(3A)hc9L^uqsZdZCZz5JuxZAbnPco>Zn;Uj|AYBsU(!AqQ^sz98UDibfxb*m+ zZ_ODSL3=c4vTR<#E~SE@C8Jct%eWDbnN?EZo_5OViio@95_rK9P2KeGVgl!Uo>isW zBq{~#G#u)i{@PLP*r z!cLiMV31}2Uq4C4; zvxI7mdJtA;w8dN5Srzm$aMaiHk!cZ$6i4Z>7-7{@SzY0=4uQ{}A^uab@|L=WSL-s! z;lk^k7dA)lMcp*OQfrC96I6O}s1@4I8NYTfw-6Hl9m}1x7;jNj^pJ3T$RF1xJeTrQ zgz{yvh?REuYw`)w=JT+;8r;InXK=u;huQ;2TWKQc9+VA%L*#g8f}M3y;%9#b(DRA_YL0eXQ-k*lo%jcxp5 zW%+@+RDWiz2#L3(H2KFQtS~vOrO(C-ZlCCDhuKO@`xVe;f|9o4{OR{s8Pe*{rkWMK z|JMt^#mIUTt)US%^oF8*yjMn%@XPWo*jXu<64ecmA~2=FGh_h_{9*5emmtc)jl%0) zml08*9ZUq81%3dpB3D5N$o!p;mF|D;s7-N{#9Q973-55t`Cn9)%U{JBXMIYIoViam zrZz?lm)K~{XkR$Rh~lnD2DB-Ley22{g7*in_gFv7iy3(aVCJVoK8?qp!Mk7Pj1Jg~ zvnw!m41-nhdr;qX@87q^Til#-?ENu;xx^SIjzK4CN-<`;%;YRONH>3#-uwYXncu;S zpua)G>h5$|UTCI)&4Ba%GW|zF$BsK;Sa~eSWIzGzxuB($1b4WN^?6-Uc;Y(^7S8wD z^8L3wo!1u0dE1Mxk5%XN2F_H^pkKpV4ol8`&Qh;U;hRUxZn3fNk-c<5nH^fN5&H_{&NQS5__2bB8 zZioskeD;{dc`7VEdL@Xr`VA08l9WY#bM|r`X#TUX&aQMh7=j2J&m3 zgGQE@+#nmKE$Z$q=8fYglwy1o@-L(UMr)QTZaVnsa;G3>IAcRY0GAC%4?f#4MrpZt z#Ds9E-gVKL@b6J>6h7}{&>UsC{)N>9K`^lSUkMk3g-0n(9c3bY;+g;SMNjgpNL2Qyxxj+B_U|sb&&!eyM zvIFW4iWe>*S336L_I2He$rYuYI}971^?)9=4x~nz$pKJ~vxMf z!Q?spPfYw<9i3AdzecnJ298)YaDMiLbu*}OU*$dwoP&@f6hCurC(t?+tWUHYq;qCB zT6O6#=EMDB>r@U!4*R(rJL^q;p})b1F+<{sHu^I`t~+;Upj!!PNn)MTBE^JUF!oL*Wj%~!i%e|&o8Uh=IbLM zOrm5h4zSf=R{!2w{s_UC=)N|BAzs%{CY&&o*}oozey(WYVVvndoG z6(IzdmRs?rM@AvU%2_BLp;YaUVGV_H@J;$B%rqQWfS||q4Oew2R9FcEI53ZIvuj>a zIRhjuN~5c@G()u;ZD zz`D}MRph8;NCQgN46750?Rz_k-giY5Uy4$IJ<<1NQgjJ3A zCNBJRfFJ&~;n!NvNjf&lGvDcA1LcP}v%FiCS!MD~%=-|=P~gGzhn%19mLSqo><#R_ zj0T`fg3_n5?)X6_Rjkjob0D2Gi87O-K{aQ5*7@&+$oOKZQ~ip$nNyG!mv%I^->5O4dSH%q8%Chd~O(SC>!M2fcI*IRVGKVOtGrY#?&n9N!mPOJ`khaDUge zv62*VyJ`m_u=XAg4z%roYK!w%udx$;mSZ^({KvVQfO^Zr@atQ_A4E|)%FW$#0~^ca zXfLV69nno;f|l4bqSWX7Ad~2&3`~13w(k4h$R-Z_!yrx`B8ebtJ>Ys~N_{?!`6n_j6ZbfgS!}28* z)|s-*TR4aj{6wb+&ASDxc!U}`MPsMtC#oS!^Gc|k!UsqxjYxLmOKcN$qkMax68I!f z0&7z)2GacF^AFs6&Oi;#d~7~V{>eo_lfSrkv`qfKuOs%I*1RkT9Q&v$Km@FFFJN` zJ;=OMNP99@YgZscqz@$J0Ct#M2*(;MOHDujoTfNz&%6>ZBF#d!PvWDw4rBkQ6=Q*j4N< zOC-eQZ$dIbg+Sa=4gBHq6SMK!lo&RpyD(IW^>EgL;Os21w|OqM6P&^gLa0Lm!F$si zVFJQQ&iqA<{{Ll^u2}xnr>l*lJ{qCPxEWmi#VxUsvyIq8ix*=?>;*wU0RCE}%y%-IPw3(`9OuVou&@vMUc zK1@~;a2!q^io=ILRP@d9LZkk8e*OE{auNH{Pu}1O`p!2>f8ZRY@ZRE(OE!l$Pu%qI zNn|DIMY`4&LQ|A-5#DPi z$_dnWYi8O}`jVp;Ty=+`Lht;C$aSE^&Xf>Fsj&GZP?$yW?kcRB8z~T}J5Y@k#Sxmr z^fI{l_>^Z3p#p(NI%3HIJgxwPYl4%r)rpwTzk^K|o?Sy(DEf+>MoY`=G&{BzH@B z1Q*Az9Gg9RtWBs zXQub}Dxf|8MH=gTd|tA^Mk#qm3agFCGfrFqfuXLbC1tgl6#_YnxSHJDR^{8PhnQ_O z2MlR16B9l#DcXofX7XP>3Sf_;XaPLj?N5#3#q#z;{FjQ+fPxf$2vW%d4)I0etL*`f z*H1jlMXC>xd%iM*0wu(rQtMGw+kvtG%5xzRffX+LRjwzzfUTVGnDK%Ahdo%1qlu1l<^Uxa^d!b@#nE&qFaz1dyYMUIfQZGvOul&`lz zrb@6bJ*hmvMX+>~-%M<2u%wjP8iVD{L*s_Imsy+y# z6AyZyzbOSHH*jX;N9!jAgGUqb4MZ$u<aWI;GZA76 zSK=jUh29tze+Sh5sJdv3z@1a=(TqiWd8j;fs^VFl#>08!XoBQ#3G|vk1Ff>1betdd>;98{piU%y{VuNAc!N zqt@V;W?(3y+S1~yVP&?)Vtrb{LBrg;u*yLwYUtKBF%k~Wm@S2aShMJzO9#zTDT&kkSTl@g~wLJa2TjI zx64*m$S*)b{DgEKm5;Xb^g|pFV7$*9d5sAJyv+hQ3 z24Xo!0SqCI3T*@2rSFQ3+j)eE52%~oZyM`>hB)g)l6rFV50IA;uqmaX4s#3*e;z=x z764=;7BR}dPnU?vIenH0G$zAXa2jd{{qQ0{Gq9Tsb{+_yJS}j^bE!I@+LKMj1pQEeUvdq%2KzQwMmB;^aBvRlJGY#m`Y1s!~>MNUlD0BYwJl)O7 zRRu`rc)hjZ2wag8oypS$wxGv(VW?Ot)zG= zJ$zr9Bo5XqoDMNfpn|pOUZJ&Uz-Tskxf{h=KP-& z7aH*+6kUYZTdDlNFzG^b!(o$l?^ocbs{9l$8dAo$%^!Nf<60<@=2do!)XhX-j~1ZR zq%TP;Yh`Cp98;GBiNk8_RE|@21LR!@1gKnnV zM)+89goE3;k%ty!wTf4@^VGh+_2NvG0v>b(C8=R3IQ{UNx<(2Xvd4luC5}LOY%bXL zr5PnSkf)O#z1)LW!BAT*yqH9r$_AT}n1SWC;%G8v^hVwTZr<_3#agB;h|42cRTuIf z!6tUHecn`YXdKU~Q$DHB!Evzz?@^vrhwsNTOuXaQUsr;uZJp6KTkW2BRyS`CR~rjo zL&te5A);o>fB-O%9~h~+29MS+7c_~WzNDzK$yes@8i<^_QK499&gpKl)AGkNWvdD; zuGIfn8oqfsu}+nMzNLU@&~gtqVv|(RTg|I+b~HraX1(bEZoW7iE2-ZeHTE@_7MVZK zK|I$`Yso#D5j|3Hl?ATiI%&n(Rq?W*`iL%|}Y;I64elQ;i!_nj;fd{Ri=2aMK`lG;6Qb(A0mIkLO zONEl~HZm-JX_G2fVe}fg=P5^GIG4iSF5&|08xzA>xeXN_b+37NU)Yy~&H*sU2a28H zlNW&WoyCZ5y7EdMrr$|7?m!gxBjl0zqmRiicj2OYo)%r7_GF6H(uD?C?B}Lw=m=pv zb57~+`9G5q;sx-vT|RO#+(pPut;==`Ve)kN*HyA%Tk?lVM>28T%4XOtLzWmi_UC2; zjhT5I-Fzise*Gizp9!d7;A4ygL=&}}gVqbvvNfMO&5k*8&1Qwslh`{qq4yostw0p1 zl5V*KP`XB9U(`_-b}pJoe6z*ZUWw)ph8`Go_`K{o0 zK_vdu*uLo;LHIn9XkWZw6Gc#$CJuqgju~5Fkp{0vamQumEe^N;#+mC>P`e|sfreVe z%z2TLM%L5C1@Z9UT)+*^DX@^|mkJ$5HoIFs=XNfq5?Q)N@)?C10PB-H)kl?avj0Jk z;U0?F_udM%%c^_mBSHMSrV60Zr9K(#l;lvo<8?)zO z2&>8+Fz6hZ;gz--5Ud$IQE}ZA%4v}nXRT2}?2gJFJy|=kfp*|3MAd29+lE!2nV*D* zVup_#d(|hfyCnU|+v{_QIfwx6vd(&yy^y7x2c&7N@J1T3BzYFfMq*cFI=re54iP7w z;V?eRn2@4ak}*mjQNKQ);^~OkUgXe)kl-)yvJVz(^e>6cTfkyrbs?KZKBB`$!4(&> z1<0}K%T;RteQFl^?}N3Qg{4QtlEDyn6Cz@M3$O#8I@WlquoKZ*6HAP_2j|w{v>cY${=cgP0LAaxyI^6&eH3!XyeU=ccF)<@Bzoy>P;~Geg^y zz}KWujhIhjihaTVk70u&roeM5qEwq^7m2KsN)jlVFP7aAf^$9*_0yz?sMq>7(AUpUFe(p>8p+S z@R9i-smbEmsM#fL9qY9!xIZ+kl7Am{JD|~I%ZBa?9 zzf`SLE#beHx^!*`=tc6*>F8zrAyr`;t8^1hmOW{l$OgYHd0>TeqNJhUqfdu6B?5K@ zVy_)F z^{|EbPfOfoL;Zf7&rfl8<(&3&446#FHTw&`^jg$a`t>&S+3}G>`sN}Z_wz86=`ELe}p{b4Wum#^tTxYjV(C3 z!@**Rv`Ow!T}0DQ!!n0Ve~i8s9$}47d@NN>kEW_ycv3?QXC?!=lrR5S>H`(uK-+yX z0kp5t&>Z5Q1CEN%79~K3hc&GNz}&wy&pc-r(IEJ3C;Kd~m9(1UDBkT)5C0?H11ZJm ziSGe$8LUOu$lgX-Cu~-3j0?Jv=nI?oI>*2>D`q@$RLKS2K`jc_UASiz(qKs{)p(nRo*p+&p4imT=2gJp= ztdahebQ-gsCNG_KMsX*z>0_>}S@1?@(>w|giNO_53YJUw&%VnE!0NaO#GzbM0vx#4F=&@2e$s#?W2b8XHel z7931`_IV{`Qv_%%uR-^bc#Z*$L62j}@hb~VLgC*79@`<>5E|=rm!I1f{|Tx$o@mi4 z+~44YtveY^k~ver(Kl1nr3-k-4poX=nSFr{+O%63^AR@JzEZ^M28G{xOu**sOB6KQT13 zamUiOK&dT?9gVg%C)lkmTR^7%f-v&PO_PSAs5CQ2&gobptvwMc%PdX$o*Ugb^wF7K zs`!d=`kW)Lo`*=Z4T`Mb>K!U2JX%w0HploWl(E)tlFxzWAU@Q=)$lS9bwk#3P&ZV4 zq+QKr<+@y(2cZJ#cJV5LLbvyyar4!cO_dzf6T&R!c4K+OFLHrWq)P?E2?JmuHQ8!4 zRmv`WR_Ah4YtN8bPE{B4fn6wvBjpywW>|VS_pd|`5qRsGtb{NHBp9`(&(uCTHsF%-9Pfm#Bj3((k>j{ z9PLTnAe++nIFvQ+fXZ=J!7(N;J)!LnXmhS7m7|720v9Tv#){~5>C}w~B`TtQ3K7{s`Nc`?d`qC@J|vyum}U()FPE>YwH zW^bpq@g>jsg1{e@1zhXrw&3)b)S-d=2KPZcRkc{SImR4Y8SmKvi0N_&oSS{YBML1P z32Sp3kJoV?haBTM8SC7{j5c!>o(JnO3^6(x1Vk@0wJT8-3~%T0+jyURQ-Y%#)5KU>so`D3 zyqw%;f_9Q;d}187A!JuD;Xc}!r$ua?ygOT0G>L$uNj4#?B8TD!_^6!2cn!mN$^g07 z^{Q13mka|G^BfLCK4$MI^ck6yJU^GR+ZsI-MSk4}`vqb;`l^d;(M*FS=)gV(VD>85 zHD!19x^cpfAF>PX%NlqXVIT*%qNh7}{(`bVfZs)?0BC12fSPpje2dFh7deRoK#K~= zzOB+IEgmX&0PMuFn$!K?{nn}`;>M_6*xMYB7X3I>3%7$!WR5`qv?g5y(+fs<+&9O- z*wQwn)dX zc#K$uqkwzG*k*yGV|))t{X=VnYSdo|OE7iMlt%tVn(%scglV`_z139QM?Jw|M^YSi z&ldK^ zGw5FA&;#nFx^1RU=RGzBTe4q|3C)Ds+#wFn>X_rjb1$NfO}9`6+r6ktyac8`Yg4H| zSmWHJ@NL28h7=P|g@f=&4cknxR$JosVFL5P@7Rl0i9_MN6`%=Dr@)Rzw#+yov$dFf zzf`#ns}>Zw&8jCN{uRO|*wRFPh|?!?Hx>g|il#%L0g=7?`PPCoS9nO><(atHA_{dv z?*x_y^U*9b^Ll8hAex=F!QYb+LXlt1csB+246Hv&tqUs&4Q?7W!?4_Hh zVL)mO%FqDy$Mo*XAmdW-1L=q*8$9!vY;KG#`uX^4S*SD)x?qim2I8HR;iv4oyk z-UOK3-|$xi%@;1(NPa<<&4pg&F+8h0xWcY$F+DsblSoDK@6RSN9k>H}oqa@&UiBCt0M=9R>VoGD6+OA> zvk#a5kmeAaI?gx&9OWyBJgT>Ppn*I1O0N!taRcm|# zJfGG`#Y3aW=}MV_H3$75lSONCo;bU@4Dru6&*EsK@lPvYB?VTtwBLn6iF>yG3Eaw zfM6}`Mw7Qc0lR3&&WY9GbcO!?v4;N0Q~ZOxPr*}z>Z6d52uYxf2lb~Hnc9SlL!S2o zBddI#2F`eH{MI98bqHvg8UBp$Zpg8?mMmAmSazz6g}Q0B_%TiCS>T5>T~u`|!`p8Q zZgMP9PT#4Fk^MZKrNq(XoGKSzO@!N5|6qcS)nnIwaugq|LmpVj!6LE2TI$1Y@V-NQ zBd?x{flYuZ~?&Vn=B!;_5LKx;_q$d`hw-G_$}?=B3UmGx(?72d2v)aH|BSH zMx^iUnN>lqHK?bd&w?*IovIuCpQh02Rz4fkjDkX!E;^TOD&eh@!(lebA)8iOX*HOo z?m^La+?RhQ(VLfPa_;MpAmXGxqORzK;jn_D{>@q5*0McKh7@i_V^f#aWX@4^5VM7-043d?4+kS-o9XOvS0g4$ zrl>+holMon8F#12sHoE^iBuQ-RVd!tv=Ofl3%_iv)iPP=G|~eFz3w39pVl#m{{^H4 zU^zLWC+?ZObu2=<@iPu((g-Q|Ap1@VYKZm?D?JkI*>Pe%WY{kYk)te#k2TF?jK;`{ zw)Qou@ThRu5KGa>exe}Q(n-j1%m!mou)w^Gb+m^go2Qxfe6w2_LO#F~ILMIf>TVq9 zHj_&5ln?1qqBGTxvm`}h<`E#@u64zX2dAa$3t(@?(mW@tEPCe{&Wf6#VQ5B+dmagk zyl|7&AZ0nj&oakAtKp`e)lGpPa%!@FDJ``_iF@xuBs1~a=hNwi{qGE3e?AQOorp(! zoc9`O@mSl#VV*(SP-PO%=755+k2Z55k)r(g(^x(?*%ZMh-yCmZ=n}6*T^=_g432LmC;7j!zK0d(Cm{%)$JR6u}JF7;kiDU{LL|c*i+wh7ndRp|@H$ z6=i=|Uv%hOPM_>L2V}DKHz#L%D4?JWQW8UC}r}E%M2EzY%;%iQmTzLWDpY!I&Edw5h=8^6Raoww8 zJIZi0`m_Z4%N?S=QhkL7Xg54VxutiKOv``{50E-8dyRlqPsWxGEztUaHV4T&0B4IZ zhfNvAahZiI{%_#0TlvKU4>JMAVg3zXIK1Q#(b3=qrZ~xW;ZzJ$-mm&~-#q=rIL?t| zZAV4$54hs6pq`6R1IWkQ_ylHli)(4qbO&QT=3`WT09i#}&hpWEvrCAhBDlAa0BjGv z`tPvYqgzPGQNKBurJE=*TN;1m<-?6B#X`%$VCye#M)somh91q4aYI#MCj%t38CT|8 zZ|03s9G;@!x$GRvg~qx#L2-8)RI-vKnXBfv+r|B(Yf5@K?8`qxIV!B5(n4+cgoUWG zvzNTugzG?11HR<$Z?8fW#LW&kHW&dE%6RW3V0s|V4mRXZ&RTxa)`l@ussJYy!22&U zUHnhWM8DUJ!p4K#Rg$gwOiY}Ih0(gUCN(rmH>t;6)W;i#o>k+qT01}Y?JgW{O(i)E zT(;zvnb&<5Oj+68U;w%-%X&TmlZ8JyOPt#bkk&5>oN|&V2i9Y5o<;MVf{tTPY8myH zJ)t{CbF?Y|!@(%!%S*D#YfvqnI^+D4CU&%A8|i*yxOQ@@yR+)!#X+_PbH2K93k`St zVTT@*yHh?0-ASCf$$@Y+ItX5DHy$+TJLRt_{l70np>JZsB5jq2^DzXiU<#^`{EI5c z7ONunRD5IcMeIAD{$hmOpg?h}UANF^k?~|tZUoR`Br;!RpJ+?Y?715CkJW$`Z`{oU z0^UZ0hwRe$y(9;lD~}>lH_~)u#pRCTcZ$f%;Q#;#pk(8K0000000000000002ziIh z0%-EU`~~TeZC(rM1r2sds-kT&bUhk5LxNuiyC-3{o`Wf|>ivWp`W#f+l%#vQf(4wy zXezvtD6s;%LCs~;#(b*{(7HL%sgJOA((@KUvJXCgcWIGx*1UCTLVkMA=GzS_eHbzj zui1ZWAgJ4m8g;e}W`KT4d3bTa00004DU)VjqJX~X2{Alv#Z-E^e$wV`QT`_DmfOn6 z@mbQ;nBzO7QCW{X27;n{cNAE(cEdng_))SyP(y6AQ0tA`j-q}+lM)A4;fxu@GDFQX=Zc zt5|8R1L+E5?Un0c-rH8XxtuA-5fkK9@xKn4+uYl7JiDazhis z6!%e+(;KH5RTT=h2Q`dts$e7{mONztwVw5onSr1H0000002v)|UVKbe1PluMTDE)1 zO<6p$84sdoK^|GY<77)fAM8Bxr0jCZdv6t4=PBQO*#yr)=iL>tB;v)%8!qMI=!2}XuV z=7fiPz@}R>uvD}mAFwi?dg#ow>DJZQaD4*#C(K1In+Z_x!u(2==9(hqs|CUpPJr+q z!Tr62<6-;x`~(KJ#iXe)ayMX+BO}ldSJOyC303{=hMO6KDR+{o+qPVLLm)>82uq|P zSrD(_cilg2mMM;nfBFFNo&qzukD+lxgZLRuhZ_%s$#Vh}NAyrL7bv2jy=0jLlz4Vv zWzy((vMKe_Qo)K&;z?N2C*V3S@sqod7ZCHB)fPxpy`^Z-t7qBdp`&LcwcJ@^aKtNr zAuxPKRmW3>GS33HE}eupV~UnS!eu1KLh)O?8VVAcqPdV9U_<#tz4f+(Zn4us#w2fOfMom%=dRPQmoi2cFU<1iv~ zMEhg9>B?{$G-frFmI&b9W>^?tihJYW1OwJOy(d5vOnSE1Ku(+Akkgwg ztA0V)-hGS#x+^z8v*HqTFP?jHa=3Mb5@O3*$^ui~$^uU8e<+,pjl9EzQ$Wuv91 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 #*e$b;?b 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 K000000000zl#P4< literal 0 HcmV?d00001 diff --git a/public/projects/tfnsw-train-alerts/timing-and-schedule.webp b/public/projects/tfnsw-train-alerts/timing-and-schedule.webp new file mode 100644 index 0000000000000000000000000000000000000000..c039ccebf3788987a1583fd8843bff96a32b2af0 GIT binary patch literal 129046 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⩔UH}`(rw@h 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�iKAq!9S*fTF`I|q@tbPDid&TK!5Pwg163!DIeGFy<3=3929n#B#`5QaLcpf$ zqIgvw!oKlSMo`?uLMsQIPH3$!QFLHXlN&?M-8oI5nya4sZT!yrs0r!IO>Ib&aoP)A zp)fm7I-h}CUDw$bw5dI4+UYr-;5OY<$^8uiFe%Z%E3c?Kj*Go6GwWvK@#|dws0Mbh zpJ$erF?<6_ z5r30>GbZpMEjV9hTwBv6)GKR4i1z~eNuLm43DX)s ze&PR}*KriZgS<>@WdOg1pzbMDe?IZ1G;%5?yM?Jb39L40@Wxn%Z`EL7k_dfR{fF8)zeZb0L_p`IUe{&Kj!tA#fdQ4WpxuxevL)=P zzgs9gw#W~%es17EF8zmL5)jV=xU#L26BZr}0XQ!?QI!+=&81kzcl7C_i zx8C|T({M6u6EljkmX$h(`J|$P;e6T6l&cMbrx-VT+#Eyq{|Om6;mw;Ii0EmQ@wG^Y zb5!h%1^s5X$o(SVNcGefB;{F2GmOZW`omU6ng*;oLt8AeZ>e)v>c&Y9w#4+7srn=m z+AyijwCX~hOlonSrYcm>0D7jaVDarEGhPsB_dDC-$EUef(C-wKqF;0?TNI4cdkw)( zO(DnYsm}!8SC!MCuIovqr(IXTHq9}SFpqxbq|Esd;tDaLsRkQct>ZLT-Ab$UJA*Q_ zVq=8#h9g-O%qljnYbUyZ4ncMFv3OB<&i8kp%+M@?e_Pfxhqu(EQmtgAvhQW_hpHb z1{k^wIkK?fR|cY0_<%6HRQuykasw4_I4!>fG0nsMNy;aOBk<(O5$UMB<*veT!hDKuYt`izXrG3%T_k(Hn^BwC70TRi(;jwNM+h?dt}3O&^Pf7xptETPieXoC|y zh1D$+CJeWbT?8yV4jCeMY7rZaITkn_^J;tLyu&0piXfG|cF%}bM5eRbOGhUXkt9AjS)6DUIT zCLFFpt2qAkXXkwZy{*3pOBG4FRU8#778yY>U+EUJp19?SCzfckHG{Qp=-EMP$-tu? z2}L9QyCSTL_x((Rm}RPbCkhf|JkY+@QEYub_$U;8P8;uRs1$G5xP|=KzI3kVat$2h zW?HxVx9(SPX#?W5-1>ph zRKG*4FuGtLJ?6tbI&Ab*t$wh!@_$Ti0c9ZdD+RE826Y=Zl|!@kb>=Xk_T$;d(3mDhInyE+ub)dV*6FIg zAQF~uCL>V}1jwy6LV%Xg%lQ7Ppq<<|;2LGLnewTErNMC^- z{{9`90YhtLQe)1g3Y_Ca_QdhJoOdWMA4y~La|eJ7=(=w+yH7~qlZw$<_|fC^=y3h6 zoSbnp87U6eI3lrA)0khRgo+Eq>(YC1m%dZ0E1d8YW-p1ExbQs z$jbYoGBd3@Zlp&+lfOD*_xy*VOAnYXM@iH;g9Y^#tSz^r30#19c2gtrHD$NyLU3$* z@c|Uyo`vhlJ`dC_h{PaSfY9YgzE;jacg)4+bqS!_!2}JVoZ;EM2FMcXb^Ypc;X}SG zqMBgZtpW3zX{$BJSPR;b^#(6R!ROUnoEDVUk0IV$71K%yrqQg$!IuKKQnTul!B;mT zanG5B0k67}%e;82fB&(dLvXSG{r&PPzIwkpq2u?k#~m$6=Nc%Y^~wNkxkOI#?pBkt(hgoV6dc+968&=;|%bVs(D1{w*45+8cDcu%8`<6X(OUdUZ+vY zm5biI!_teDL$3XxS*M=i#2h&Tj;*4eY{j!t1|Igs-ROL#Ek4?9bC`ACx~GR@zV1?( zgjswLSS9*ONyyB`;{QCRP3u9iWx@LSU^J)(ob-{fcnUx%s;_@qn5pfE<{A6hjV({r z3Bi*(eq42{50=C83YdRm$4@&CQsKBNau5hn%TQx^QB)Nt{O|K*CI5uZbAw-?oT`}? zF0?91c{g)(mWiYjD(!C~(Bh3r(Z#x*Vu74BpmbJiN0t(ODWT8Nl)Y(0o1?>**Rq;% z*9P((!zxWH1o%2Nl$vFrZvntPX!J>{HPU`7cTtwzddVGs&Hl9f>~3qCP{#c z&LFB+53)Q0uvCq5`*??eP9cK9**6cOu76Id#J`Pbx7K9Aw$nY@J}bq>{WL74N(5Jm`NG+iSJ0f2R@>HMg7;D`G&s<#vnaCV8?SwH?4gg<|N z^O*P$Ms|&o6S;?L(P4C?#Bp@o>F(WFA*aHIqV5+ofaK94-7MYv(PTpHEIzecV!$C-G@{@PE&(m_P zr{k4&=a`)+I;QIe8(Y~Lp^01vC2#2%ku!#kz4@pP2IVM!cE)b9;CqFLYxMfmmbxZJ zFrLuE>zzYmtaXS)2<>Dz2l1cJ6y^md(k zJx}j{b&QargIrzvANUJ8j#wEIC8jJ3a8~|1W~KCVQ}sNyw-TC!$TzmFoJOslYyY{3 zs2dzEZGFqhm*3rbBCUE%Ca_vr@{ErQ<2Kh!9d|K2c`K))BOKDy_AR89^>j17rtvSf zL5AF*D_Tmh=;59`+dEC8vm~JNy$?@2`z!-(Q z`18rChG2={AO)c(vP62k2LgQ4z^K9RJ@a1?S~mcJa918{^i2i$^MZ42S@a9+fnewo z(A$ax*Oj$XaV6b$TF)9m01za)x=7y)!;(Mrems8bW7?w|2mr{7D4{|2;Bv2i zRe_q6u=W7%7oIcmJL9hd*FeO0+&f{@JV^lHDwaZ{r8hF!i~nRw4o$ScFC<1Y%kcJc z{288K{iipFF4U-x!z4sVe#ZJ{%Wc6MtP^`>hG9oRxm1BD`xo@53|M4HDe5qfaW&xF z>5Gm}XV%8r1@E`{M}ovnkhmve2eR}$t;3;vOfs#!F* z<8%Ki&+5vNH+C=d@B0G)y$RC5Z7v<^S?ra#4u9DG(SM8aqT0I9H9=r`jm|lS5n-Hs z^8V`jzQsZpD-+|=?y~Q=k)&X2KApD$x%G4h%hio>G8~c*poJT7X0g+m2+@5q$w$)Q zM+40}ly9Yd(XjagP<}nxS9fS!h?_y45zi7QmtW?inyewA5x8cVYM@@hC~^IM1J94) z7W2iomP968wF@2;?tKUKP9G0|pxXL9IP)p$B@=^F^xhwMtKFFUQVZ=aP?b6V%Q}m5 zrj$2CDEk86ut*(;`>BlOiJ^_dv zLBoxoNhd;y#rD=bS*n#lXPaF|?SKzU>qi;h+76TOI&_O!VbycL9Dz-^zQ;TB`?whjrwAsWkx^}WX0FF;AEj8L9_Q(Df=%)|?$b-WJVG|lhN~4v_Qr8Nb+Fl0`Oi>=$xro8oo=ym?1QC>ie$Q2aR(y+{GD#2I18ywWT z+(3B(1*`?ad5Rg1fZ}(pGxzr6`DQZs(NC+OHj;H-mn<%jehJL_8Ad&7N8fHI%6xJV zwB4K-b5}N*olqqMGb|P>&w7SvKF7gvIKktA%$a`Ar0OzbkW2>uuRt;9jOe zitDBKKesuOmCYx~P7UuJK^n#iCRO9!O+xc~AhO=4hF?09=+z1kDgueZASis6`})c; zdzO|zt2UV}HG>9n>tX0%DumAdxsKTz*L?MsM9n*3n*cq?0a?x6HXeF9(rTnZYw1*XIi_ATNaXv&Ui zKDOU*MuVk$5uUaNQ>Cm#&4r%@R0rhElprL@pQVl+rb`Gi;!83^1^Ok=LwTb-Q7_|L zZ_0wF>M%)i*&QwuaON>K33+z#Jo9u(jqkOgY1NN}=9dB~yP!nJz=3&r)S+r2|4B2V zUrAq;)U@i>QUWB&dBd+A?|Qe~cSMavP}-cFsn==?bCgiYhAN!=O`a``SFR!NMwqd1 zC{b)iA+EgyWtP^oaxGQmQI>mqw$iDtg@Z3o>t(xCFg&tt+#?md98;&fc;|6R8T7cx z${|>%Q02oVr!C2^tZ6M5C9te-y*S|wkw(7*a8&%ZBhCLts~f!S$Q2zk=YnR(xXvfS2l`jG zQz*qBN^snB2rWo;A2V2G;Fr!Wrum2Sxx;%EP6O^hYnXgSkG;WNGYqz;alD zxCT0~Rq!Uyf= zOhA-j{dI3BnTSx3IILtBYy2DJk6_X2D@cnb=$M_jpb9EOKpN?3d&5e4C@kF&VL$+% z+ifx64XS(jPi3JFJ!i+C`N}{xvq$~_ev=h;QFL#dERDA+Zkayh$pE_Gp`-f3;pq%O zQxvB91u1;^y!!P5+}TIfj;$U{)*MqYsvP+m=X6}^)avdC9yhw{;g-KyKl|&U`fqG9 z&ep>S)Hoi$tE5ryQ`U@R5tw_ZD!1Tkj<2Q@86lg@VPjAq`)(GFRgm@FZ9u}Z*Gjxa z*?#vA?ZsB|3V>uq%4@5OK+-&*z6Ox@KAg`dfRrF>V0LS-=?`Ziyk2TCPICfgYn~Iw zol2(8G)K-Vp;xcx)kF!t_OydIx>`1*nO8oKr zv`j@bPIdjN@3tbt9SJ+HUgD=5hvW&;=Hdv6J~K;l9jxoVdY3#3 zC0Q9F+2C|45m~UcL@E_#fzvbJqaLZAk*Y1?5vgvZ?)q&JK=vf+Fag$SKI-{M?%R54 z3fCLtJZkK|7OTTK`Q5o8NIZHwz<$qzcFH?p+(Xigk~B^mf z3l2w-WLE622cw#BQaH>VW3s85rv~NKQhAC5Ew?U93nYUGD@iykIBr_CBIfr3%_zm- zivicG>H7P(6}KDNwI4kE$Ia4~mcPxMyBjUMoQP7FaM!C&lEwN> zgd7#RQ%?4td|!Z5FGz_~B?`j({72I?AsU6diN$jZMt3BM9KI9=P1S^-7+JMsQEV+& zwizloaWOT*NZ3fYw!YzuDmVOq}C=$!N`3S%EOc9yqZ@sRJ0 zUux9Kw_&L9N2=J6H7?sAX3$`QZoa=0%z3Ck*x(LfghcHvLRC*>Z2s=$)*D;Nf{ zT~Ts9=vYhkCuf|?w4o)wnnt>AjEa6bTY~(Bd=Sj;uuWr>gVNtlH?HBbh6(8F8!P4u zawc=~sF?UPEPnRkED8V1(y2-sG%4gR6kmBmRYhe~oGUf2gqeNLmhNgs4-3jofC@&u zaLjgx372q;Gtwm@%*1^})N(@VXEAPsyF$tvW;-mV7(U)sN~M=uZOIyKwjvUg-J`jS z3HOR&ME-BY+n(j4`hpj}4O+>)#%x#(nnw}Qi{Mg!Wx;5w;iBbX7zBHLaw|GCAj}!! z>4VfATF0Wkd?FuJ;JP%hUR_7ts*zkc=4AQ_B$LhVzeH9ye9ypQ*`(^x8pDx3Y6JlX zgWPbmaxnz_t>p0QDVhY(M|%7XT;$jtJJ6qXd#S-~$#x@T9KI9qd5vfe#r=BWb_fFV zzCkKcngv^9%nd%%FZP)h8_Go-bYcsGRb}FaOwvCa(sMyfP9Sg!H=I#!`}urY;&>?U z={+{;vL=}ZWX|~Nz2Yu}xcNmeUn>jyzuuDrBd?$+YV-NyjX^IJ9`&StmeHZXMnuKM zO}Nype(7ovK${(Ff16!O|7>yx*IAI3}*P#c4Y zfj(?V`?-Y_RUnOm8Zk;^8Z01JmutRLhfZV6=cy#&xV~UAiK1$^D73oeb* zt2+wzW2ZE;M;D5d`aocX=WYfuCd*IDr1(}y z=t_b}H~7JOY^PEoNaNj#Z!%PH+^hOa5aYiw&_7UUJEpK$9MsH@9~@7gG(nVjpQrD+ z`gABZ{l}-4kuzJ(lvmup%;)RK?jr=kIsQ3r{oFV(tXbQ)JH2~(#Tz8i(mNZL_WCR{ASu8$ z_>j_U-`Iee7pp~u1yGPW7ycAD^0J_7=lrNMVskrf9AhyK8#@5Uj97;&Jcbb(EFN8W z6xyQZ%VHQ@E&V&BMSu=`9q`v+4>c<+Lnbbg2@gWxT;&WD`$CICwN8SE;SH>}RZ@+-=0;kmCU{!a}?dbu0jnH!@-;6}-$8d_IfB*2C z?Ldf|n{_0#3GJoEUN2SHd44MF!;3ISrqcT&+s+E)UMiPK!N)hdBwvn1(?G%t^DLeG z$UMgdCeDdd*uG7F=Zpp0G5xdg^Oc7z2Myg>SzD9PZj>YnsZ!Za|74C;jn3}L4n>N} z)*DebytK(53_FkCk@GyjAgyV!B7no`PX#K z18YH29}0ie+$VLYf4rT#S?i=;UyowC<-Zjx%=)tmo@{4It)E@?d(kc+bHk5&rdu z;>q$;7^{L$ot@C_m0G!VEh!6QW6G= z>!I7cX4XV9dYKX@VNIN}cKkc;8iT25_*N^t@W&E@>Gvvd@dT9&J(+1N(RwuwV)FLrc+oX}qVZ#Ny{c-68!248`#xMZBmLOdFkfz_y~)t%7Um*BoITd;{^ zgA|)3Ft^|+!#9YCrhJ#ZpXPQcgXn*!u4$WhYT+#VxQgSK@L^tN8QFz z?SPYIEapeQyp#3YhU-N0pRheE@i0Q)pw6^Y6#I1N$aCYRmc!er@xRpC%HohPLQy=Q z0d8TP;=Q?DAo3mCN0M)e(cg~K07vt3nd-PVe(-=`)d(Zwb0g;zPYPTsi0D6$wy*7F z@FpRuF4ihraVS?W!%MfB^QxzUL^T0tlq^l09e+>k%@%{cO5yMe`OrWz6@cWP%j$=n zIoxobSJ|?ub!?g+C<=R6ismHH#42F0-EFP`jn6n#ZyOgmvRNq)J*0i#gI1!IsIq$y zUJed|Fkd2!qk5m5V*1IXNm`1Itl0kvMMopFG}W|f+daU6h%E*kEoCEC@F+2zf(L>Z zxh&0wWYO$U41|`ITid?z4#qHr+#W#?kM>@P8^D%%Uq>&6MK1%wtxWW3OT=`s#<5}W z+UZ+h>xWLQGt0Ta(9eU1MY)8Gx|29kA;2+{P{^@!Rptz$rUg?M$*WQ!^^ds@KBcO~ zJQZFf9U`a-gETt>I~9E6AeQoI>6Miy*#VaSRE$MbO7+Js*=st`|Cd11g0e<6KUipV z!zOaiqV2rk=TKclyk$JGNAaj{h%FW(QFF>__~70{XHb_nMnAV$!Fe0C3i~Dwc;>}- z=?Tj*o(fg{XyzpKP+PJ>syZr452XsoVn6|I1!V-><2$VMFEI3-_{FH}P6{t`6fsrH zTv~xM#D%&7Pb;0V+mRx%o^S`e#regRc_@Kn-OWK-UV3VH7`SBPX;6FNAA4`%Cuy4a ztM5YrYj|&r8yq4R;fQAQz?%2>=1RW`FyDI29~)DV)DL4$oD1>|M1ldEqD&8O+}p*L zJmfVQZ`jCx`gS&bl#o|~^lV(>!T;26ZBfrHTt?iD*^~D5>B$A^!ZFpScKIc;p7Tm%z%tmMWQjGK8=P+^35IFHC$kY zVT%{Gr)mVJ9JpsFM8{B)7?{T@9iIQ?_l7Ct)r?id$hq~!&8&H z0sH92Wm7^)mgd;ylXzZ^-at4}_452^v3wN*38$O~S`6(Ib*845+dpK)Y0La@J8}!Q zP7^H*L4?OwjW&)Mi#{c!9PL+Tt85Jt$$!3{f6Q~6$?Vra(vB*~rb%}5a$NVWW8?Vy zL5%t|jO{>Ezc%qnss;(gBVD3Q0HveaodFMTz=6~-zs`mxaYOpdxgmYj@SkuZP6~otVJlb`i3+A(Bn6&~srNKWWf*W<_+(DZPn`%NJDilxc`yA z$H%^+WE|RCw1bB?`s!!;VqeGz+6~i|q4F~ezCB&_wxdekj6xu-@bDUEk%6i}o#5iv zy!?T~b=feCI__RVJb(H`Zo>Y|k!EIl_-*gAQH^((-Svx7&L@`e`S0pt?8lbMrP=eL z!W<-N^R5+c1GQzR`^{qpwkH4q0vi0AdLlvZ@~z#PA3@s$_cSkKbK;!X>Uq zw&@=IZ>9Q^C^l@!!!KXF*f+t!_R~i3SnsxGDpF-I)l?kV7akN% z-v7EPKVYbuCpyR7!w4f|f1Y*Agbz$_nK2q0qPth6C{ZJ=T>MES)v~`?0&01Xu;ieN zhU5sI2$Q?x((FSQ$fuY*ic4-nOu0h2A>|B{%edJW2O%*9L9rq!yZG1bc}w6au9k@x zVWZ2UKd#T;a8&=YZewdC_rvs8+a$Mh{fp>Eh8u+m+V-CY{H*4$t`%)u21(7zUdi38 z%R%D=+j^X^SfX!y;Dh#FS!;6{)a52pT%b8$sU2|1D5=8KP|MStr1jYsA{Y$4HxZ=iY zkIEOsQfmR)WkCEZN~FG3bdUh4JVJeOZAm4Er-CP9IibNzX*o1xIs{{Hz^eHlJEwwp zgMn#Pg(hy4jp0!bm76PGNbQ=ICiOtB^=-nY{B|v0j;r|?k}X;L;X{y$u|lAN%VwT3 z1oFTN0;t8_Q$2lsi~^LNc#wm&V=PR^VYBB+%ABa#XBq8u+OG&aoK6^*n3%xY%$UFl zrx{lOvbVOMSOfj7=uVcA6Y4Nio0@Ts;PKSD=CWQUb%SF8_K9JF30$&Bo$LXDq9H$X zUDBcVXD7N92hV9U4r7!ot3yE8YYOhAAj7Uz&@*e0VBc=g_bG&*llImwwy#Br$+?}bGq6jqpAW#-93z@wKZ zPdRt1t$qI?8Z&C)?1Rvv;i9J_2%5C20BO+-;j-x7A~N|oF*Gx6wC%|eg0)oDDkhrX z??hAbegLpM471_F`>Cfo{K#U<#ID9AEXr>03OHdJ7`e$ z17|n%m%EnRJ)GDxD?LLvn%UE)EOs=D9ti0@KsKNGRM5c;j$(^Z$@)+Q0+mHL@EA{F z906g+d~mZl7D*BSSMLWI+;kt#)JFIf_jwr1G-_kZKHqYaqY`?LNd08QjtwOkv;nK6 zmS1FgMi$*!s9GBkFebbN`p2TAUgmUQJ$fQS5XUmeEA}fGBMV3sjqryNn+vI;@!3uh z*g@?gDR~1C6aZQDGGpmh2&b!rw?FUv(!&C)MTIGlBp<7vc5&o_zO`|^RChJ#ElipP zN?bwoUhzP`P_e%$Yl-v*@z-wgztkdPKFsEMhXsFzH!$VS!mdKDcifg&NJ?1{@GYDL zlKq31=6{pnipvFayRRK**s+D_ggTzvYMMaPbSmtCmr|KyWQySN1zqs59s1#ckW6z= zI7uBYCCbOFPGsdaQqsaTtm?L~p1n`aV`(ezh=$kPiFb33vWBP`NNS5kv|KSkAT<-Z zIk>1I7@0A~iEz`Ec_@pAAv%*S3PTH%NGz$j0$c%~qz?YQ#P5o| zaQDeOMlv{`au}Q8Q;&WtmL{tzMnto&m_M#Heh_D=LRvedU$Yca^MdIhnB7P>QlCrOz1ffnM-0g0a={GiX|#<#TArkaA=Zr&i*fq@wBeyTu)hQN-C&aeBO4&wCb_5=2s|-WIY0b%U8)m9-6j{5!{v4Z?Lx#V3;cmPQCuU32>65IKy$^eT=-hZ*E#vvPMIuS z;V2QD?jtZpMDQ)l95;TvrRk5qz|^c?=SQV{J~7h7{Mfu>y2CyG|Luw07%@R{+j@ULq_mZp!=sl@HFeNS4+dc*e9els^MS*zMsWqM>{)-eTGMI$NHkK z=(@`n9R7jwC7ag|bt61`=~XuCxxXbecM?41t_cN3adI;h z$q1gKTG#yL|KG~zUq#0%Ifz#F?shYr?Cd!QU2S@_7+A>-L)ZR+{Qqxoe;J!EfRZn9 zj#m6B-mBbpIhj5sPba{y5@5d5d8G?Z~KzBk~mlMND{z;AEVpjRg4M=51GeN zzg5ehSOTW`JBLc|(Rt8yu5p_w17$w|v+y*+=BC7}I!<=V+z>4mA>G3EI8|F^u6ny_ zyUd}DYBU}=pY~Vw>C_FF15souzF_oX=0>bHkOD?e*KHHM{6Nr?wSyqgDdn3)^7Jcz zMC~Y(=A$_7M8o$}!K0U&NyK@pL;wa6rcmaIE4v zH|B&Qq_)5fTwHANXB@VJ!#@)HW+B9}rR6O`{B2Lng}ue^@owOs&!*B`#o@6#>u7H(CdIK5S}!_%6RAR72(Z60w_Q zZPC7`a`n^^{*`9swYk{wx{Gh$TX)fZmK_j{w0zEKc^)6TS8pT(I*QQYY_5&+Gk?I^ zj2mzgwxP_wqH)z9<5^v*NSM5|YDdEx_m|tE^oa3RHB9B{A6U`c!O7?CzxA>tf^%qH z;j7J+t}Qqic6hyNr(>Oio2^cmlPYyN`DQynG3qoT<^{bWa|>19uv3HZBBk#h76TsS zAnnjS?3)PKunIJCpGEs@XD!RQx!|{)+?XBqNRs}5>4GQkyN3^l%%xW#^|DE@u19bG zM+3BffJpB;LLbuZ+dJkN+olLSD4;8(2z=Chh2$*@0%$=vN>XE7knRwBtV3Bj{6*5r zF!`yhb*%j}`Dw24;TCgW6z|c+=_C)O(NgWD7NO0NA~YXE2?^tBEe65^Q4Ir*()L%M zo6Fr@hDK`pLo0z>a^9O7=&)yIQUH=~qjJ&@O|?z@zr$$<@co}ie^l6l78m5yrE17! zY7|=V@tP=lvh`nl1dYQ! z88D6q69GSiwLqxsoyl>t3#VB`wmp>>WVaAtOhtA;A5J{NqJ;eid4$Kcua6iwGyKv; z3e$}~adlRnOsqbNmPn{QDM+`$m&dc!;fPHIoK>oJ;0Z7yAPj4uBJbu%7P3Kg*ga#1 zWwjxxh?7ochw~{pBZ;TjoCw}*NK&z=uoYapkLo&W3z6_8pd?|H`M@cPKNA<&8?hez z2{Gh~sA2E2z^4|rR08*%qpb(Ikb-b5m?{xw<#lx+Qlc(fvvn3K4pSJ;Dm@rMe0=DW z9NJxa`ogR6J|Gi)f8fnR%=!z$Pf!DGnp^`>=?YaFr9FJBp-m+C(%HKvU(eD=)IlK? zgtMFvdilVo=cj|_>)^oK%G!54+!L^-ApY?7#kOP=WZF)t>B2FbMl@FS9LL7$KJ2^6 zQNyB+_?$diMvE2T#%uXpG7aWE5I!bR4|UlbudCcMDVBQc$F_?FCg67OUse)So``6XTIr{x}(Z&iWPnKy!;2v7?TD(R- z;#>fT+PAURGQ5n1(fIpo?N9e*!?jNG8|tYA)Fv#U7~WOJ<+{acIZ%Q`y#um_Ip#hq zIA554q(8d;yqz5j{Uw`OPKPwhd6F^g)2wf=N{8ym=1F4Zv?lUm{59t74YH*MJ`8yz z`J4|jgxd;~zt+iB@5#0gH;Od^3HdDY)G6^veQGJpJb`ET$Zv;zN6xsYuL%Jq8#%xX z-}PriUJ2|cLN|qz=@)-uy9PW6WIOxa8t#iU^=K?}(Yx`)5!!S|)`gcV+URPVD8Dk2 z?OcCJc)#h-M7ivzLxEEkkRw0?X&Su^QYs%BxVMFE4AB^{PT&OHv6T(`89f4`pY zSm%+==)6rPY8AAfd=9DrLdxsia+gVayM^U;N7|lI@>~Yh0o0X)L6K?-om*O12LvCt z=35bR*<9R#eS@5qxZEF8FG8=-&E+~OGsC@N&y`;fWA72+2vaj=Va?#hY~J;Gl1{5B zWeOsL=U~oe3wMyfW@NLAh>r4R(!;wo(%=o^AeV?afg zhK>>l7@lnJ3bh!lg;i^oDA_G<&V$2Ar?17QotE5n3KhQ9HAU>x9dG!(=g%h-8vtE3 zvze<8W4L|PKTcnn2HQbN@7rH<-%F~I$R#j6yB&}z+bs@srbR&KSd6Q(n*h0UV;sJF z5_F)=uDD~~nMpfyJR~3O3vliAJXdPxW1RC{&|81xmzxZlwuX^+Xygj^$!c#c0~So^ z&cWCunlvgtaRYyF5c_d;6~;fAn_+wDR&z^o1Ba!x@j|gMN2NAgyux;} z4#u$KZA>hxIK~y&qqQ`r6>1yWcuh~7!RGulI>rP1yXxgVwQe@$wO4jP6q4IImP7X0 zGiXndDWjwZD&EW3e_+#H#DRl95}f+%C@OYfz&^^cATyX#Q-WvBP_<#V3)>1W3;SKN zJX44H7?s!4bF6@^`-ZO=>p`;YB7x-2tY}aF>7NsstZoje1)l=lI{PtFusqq_wyA0y zPsIUL(%DW-_qY*sT+Jv2IdHV2fE1ILIi|#D>fHd2siAWV63+PhYB#lOcszLD5jN~* zfF*f`u9desT6>6RcoE{{EbuRw6HCo+7&NAtYuA7co->~18_u&xL9Loft%l%n4*9%G%Le`TyFWt>#KMLduSk*+j=S?+?`uUDFBm?W=lWFsnA zSxVg}gq{KhS+B@z$KK>WZU#?%55t%kLHUrZFxRh_D44Z8!+{B;F$u>poDJ%-3TFK(!(oaLnHO zq40+}(4%)!p|)_FslpO*byoC-_SeOi&_#5DgAp=Czf!Aczm-~6kQ3aE0Guc8eH!TA z*6ppaxj7FyS2qnO+l2|5ln4B)EV z5#_y)0T2t85bc=9!lw=#rw-A};e@WNO+cBu8gu-Bm=b(ci|@>NhBV?$ExsD&GhjBQ zI85l0lJA9(-Gqg82x9}aR>3N93x>;EX08YyMXi+<0uKbcdlH-{X<_iVG>4Eql;q)U zEB88~^KAz)auQ10g%usSslxTgfAAmcfI}HsfZawT=6^r`EXYm-FaHJ>u~5%d!V$HO z1q}d?@5DPTyu)VWu9* zgdAd(-zzV6!Pa*%5!Yy{LnmQ?^4a8Cj4)9#(ePNSQqk=ra=X*8-KExJ&nR&W!>uG! zFVqpK!x$&Z!QxWSw%-jJ%pR?xImD1>&l2vO)BZVZApPnFak&ZJY9&mCO7A8=6WBF= zcG3N7dB9S|e~Ds>c&&f*+vK0cWc)oFHqv@`!-V<0z0m$J&~w%+>t47^U`sN< zkAvAxmY|6fYK4-&xzeqk!7%C-Jvon!tP=T2GMt!`*GMjOiVW3xD)G z(E1wzc_HQ}>_Go;)2%NNQw5(?b8Kr4UF99)xI{K}c0XLiu z#lRjL*Y(34GWRPeK3f^lq2_d_45vRy!l`gzY=kkM5=NA$M8?R4`KNn8D9@W`~h*H5?HQWMM>e|;bG6iQ5Rcb#Of z&Y)0dYPW#a#ehEre{HgOHARPcXyU7P?I3I@liua0Vt!?nzw1teErBbj}_2`9l z8SFd~Wve-mh#x;ZlKPpW^RO$r^gY`qju`*0zF8x>%%vSjk<&xb$KMA@U*{yZgw{i& zN346sFdrf*?Y4}UEW+V+t=tI~M;qpxZO^kD12%XK>|*Hi>105BN%d*BGZ$=9zBO71 z8;|v{qC1o2BZ5aw3f{Mo4HMb(7uiYhD2ZnKeN~c8uoI(S+kzf-LLGdbU6ClmvVG#Um^Q^N#!i|^Bnw=-99aUYI2=F%Q8WHfw?QN0 z4a9g7a!mfw*ppA0;n>5_4HwOtURlyz;cv-;^?>v-h7|B-=w%C*P$XVP*M4VN#H_x6 zq?dJb0k_@xYUs~5SSjA^CQ#tC0hOcz$4&qGJ@1i8$dTS1itozjSe_5G-?Nl5irP9B z+!)u-zYB7!EJ|}eyhlzZIz@;XjQpE9VRhsj)<3OYJ1fAXE-QivvEF4W>dFa)>$n^) zyzp%Q`S?|sfMLNT-H)ME7E9C)7RSc5FtG<1DFrhfz6PdVBF^P!bSjK6gX{PBW>&|W zvU?ABP+jonD~^~;YKq0STyG91?j z<;fn;__l|m=D{gYo#|-=>!))PPnp9PU!5!pIQv3&FQ_;x?yqD=)EUJcHK9 zpU`%~FXmBluG2x39-=0PJ(0&$Vm1zLl0S9um#8<}3Q?+cAcJY+5mgB)Y=gkIeZ+n2bxFR*o<&UqYU z&_Giv*_ruY@URa%!NyoOL9#=L_AOqT)QE1f|u4N0I*hh{j9oe+u`x6zewbp+iT~hZZ3P74$q!$ zZO|msi1{QH;^wFla2`-3(32`GbG*vEKt{^X60*i?nrFNN)_|lfz_i}}r?hVj&V&iN zjcwc3#&}}ewzIKqXXA}+JK5N_c4IuTZR_U!s&3u7zrXo0f2M0{W@@^+>s0qS`4Ye6 znr_t_J`1?ieud9uOkCzMv}M21+WCVUJeut^0)oX$iYpSgp-;9NNb8x{y-~4r5gLH4 ziCjf=-Vrq$^X0^I?b^G!iC>~0EvrA;nI7Nxb+J)*yh~v*yJ2V?#)>V#VUFefMpAo~ zJkYN9j!j&Npr;vu1F&jzL_}rDW@VC{~{MQQFQ(98;O%J{dxt~(ObMS1Yh?u_+1Zk%3m@iI(IYK&m=82yONpiLkMkSXK z`b=j9i~JKA08i=1Zw0ICzK>s-Et49YTin*jNbw!8AXZY7s0zUZOq4D(RsR>Dp@oJJ ze9yhcqPj-s5knNQ+b0L08F$7dN|jwf5OeJMgWq!;ysXQ6HrwYlL(;D2kn44^g^xD% ztb^!&DJ>q~d-!=iD?V7QrHbm;RJ3{&|A9-k#yFuufX<(#zBETpeCA|^jiohdl6K~l zsWMKI-|VRyye()Tk*s^=947TR{RjsI(;oR1b!cMwVB2N6sHWgTTYcs+%ycMXrZ^W- zSJyI>(f&dpVU}j$2vx;Pu?}OneP-JC|By;3tSpC)KYywI7UWf+02-Vj&`}@D6a&|t zDaTNZ#6GQgow+W{fz)d&d{`+c%zn!jUyj-&CqawuY62(UI$$xtCN2isp;=Rv%jEpy z%k2h5p3I@EzzcA()K|%EQMN4?p1+toxp5vM&WA~NF_3H&h^M<-j_PTRWjnN{uUG;#Pk(uE1-Fi8nKH%ZqH}Fi&#Tvx z1#Lh;bz#c9D-r58Q0{WtjXn6?GrX2$x~HzAu)B`w$;)$xr7pmdaoSu!uBoz~EZQaE zb0w3gW`&e~=OEw1zImR2?4ZRboP?J`ir={BWxjFqH!YG)Ys+8;pfla7rhy=}%~fXr z>g{IXkoaRHm&R)Qe`epa&=0E_+1UrSLPRA$?da(brZk;>h=ciO%cG=(JBwcy1jSbMh@e%1 zJ5r5c1aoE1&d3Wxed>Ktotu$=_7nbrw=M6J9z-}jyKYy?rbbu40I5=;t9_s<@y!R3 zML7B=)bl03^=YPbUBQc?2Lu%nz1lmf#qZDBq3W+4;ln`VTf$ zn-a9L-hdT~Uc0QQpQt^JzIdLx{f>T5{Dxu*o>w^twx!Y?;)><|VBTzUxu@?h&r%=* zKVq&_u%_6ndjx2)RsbHj2f_21I$Hnvkl|Yr8IA%-iV#Zc+Qne-V8DA8heXBF?*mTM z?QL!dP>G-tD^#G;99giMcA>g;*{6zT;VR%Cp2$UNq#RC?g&jwvwB;`bBHT8*n#!#` z-vu-V*2D0|oNmL*rvOq{3i!iQIIj`8ISM8E(?~z?#M1lt!`;s}r$v?M-LD>cYw~qN zD+!9Ss~;UI@ZeKm%FzQzJRf6(3yYPQ=^SH&{~b{tj5V_xOkFunEg3>F7u%ZMQgwg^ z!~m^Nfoi2oDo>|d^b}>C2eB)GC>7#Ti?B^|CoSg8rK3;gq&K3L&bgLf`WcfzF$Zbd zL>1?|kD>6mqIKKbC-o|s#$dqll^mb9Ts;ty0!L@8zcxtxO4I3a#C_b#Ra1Icw2ao6 z%w!8^fWJA^X372E`{bjdAP*mi*idI=e1)aJY6GnN-O9UUpz|Z#!vV!Sa{uswh4az& z3mN1@Cc9O7tM@b51|a6ruI4_ta~)s zi(tsrJFB!K6-gwWSY@j)1n>o%DmMNU-yGiA;GLYrs|fSCCRnjum2*bcchAJV*K-LA zTno;0Nj)*2AN1!$;kO?BEKxslFhsBHD;-w9K0V&~lPu)fs4)2XvNHpli0a|_MPF$E z$fPUhGXz4}d1*;L@xRyD0Ry%Nf)+1?Z8&(B@$jP}kVtgwnpKT>BEPwip@3NgMRaOI zN$4c>F-0CG(~lB=(W0wn*MLlX8;t6rJhK<^i ztnjP?L7SXc(d-H`B~&*NO3^~ZYEeqAbi_YT9E^s1q>3eup`PVug7coXedC9jT>X*8 z?XhhNPSV4R-B3yE%)nl$LIIQ)i8NB;n|Cjd;z`6i(TrGNaz^<2Uzg0wS6@#1Rc%PE z)L8ueLp&F5k%P(WIe2H&Bj^bP83jp~1ZytM=>r^W>%P5;?Ix1WpRC^+Cb$-p%SNyR zTBVf9_Jo_#=;v1KAz^mJ)o_f?_HB0*cR19)qSaXx!&Mu1iQNxqDrDo@DTYrqoN+~q zc7(uX+_I$wI%4h0NyWH2knp1+0s4y!L$cJE>>w{_vAQZ^j!mOv?jz8*!ftzHLP?)B zZw<`l8Z4n2^e7KbvZIsRcpd%Es_^qlH%T=JU4L?24k9LPDco-OgDc!OB?}ePFkq|r zt-vKaL5>zq_;2U{wwkJJB>*_>$CGI3rOm}3Qhc9k*}@3Wr}c`}b~4usr0&$L|h zmSTt|GdUi;!X36g*xl6yZw5i%8xNWCjdM}DFpVFsgAXO^$Gj;{8~2mXoM_H6mr9we zNSt;}(F1g)GV6xD%qE~I*)>pz?A4=kjleYKsW8e$^&`mT3g+jW|0GMO*1E1el~zq| zh3Q~D+6r?rc$ka5Q>#Fc7SLOG;uH*WdMKM^&P0dlZ{%n^;{qYlm1x8KCwFGd(F(ts zwNGBF?ArKAL_P-d38Ky8Yp&lkg#fL{$}GqD^yuF>45VFKn7*>6<-+BxyRrIq|MEse**^=B1uHVBWG&ov!3@uPSbXlJdB zut*~UVSSQ#6ouROb??7mvlQEd86!~`_E{I%&uyNp4-J~J9Pk%?e>^v=fM6~7CUC1k z$kK6b!Rdalt1!qJ3LmYIBPmSmFe@VfI1P$0#M?sr|vZyn3)RD9G>EG9~{OW`RXZMs~t^Bmt{)G8VYDx zLYY45AIsoQpU}mIb7_1p7M{FQv6R35~9tm zG}NI9)Vsi!5u5ovTkmqbF zrn2J4hesZ&(2>@@a|7HkwKHNwaH2Aihr2<)3RiIEu0(s?{hx=iJQd6ZyN088-=YXe zK|^lcAgw}HQnxZtEEP0nU{GZwyJ{=9OSC7>1z7SoY|EDm3UNrA)LNFMdd5QR&SHQy z8W$(=@RS{g2Uw+N_>KO50Vnki6F9f@C_!*-R`=Z-;9v4&)KXssjl}NS+CznORQhve zhRatc4gDq^)}TnaEy_fBf2;ox7OAUJZb`C5SeP$8O;ZLld2Xo5ZPUpzz97LN^cc9cVMbr_XK6@ z;z6In;Oxf!&}tBIW_<761mb$rjZm(qc-OxK8Kqb+Tzrz!waHdrgJ}2p0_5sly-_h- zuJ}O>1sBp{ctI5xCpdEAsnMM8#~(l2OCsNOe>L*-Ik&u2T2 zcJd8byppN1{-I8O9Z&a!@P(ui;nWstu}{4pkH|#xDf8q%ih#p-X)(%H75~kuB<%Joup!hogw26u>e>DChboRD+Jaj8l?IO& ztnRiQZvEG0beG=NBgAy|*CiLCGU1$cYeGygbwC*V?DV>@Eo8P%e=O|(l)!*@olwhG zDiGjCVdZ_^KO-0==x07-Oql)Wc!XRuu{q;ILI+_E<+tx%>@E=u*wc%umaxb3ypdG* z)1oG%)QTw4@n<@nc~TP)VQnb(@E|(dR%IZnibj!_W$;T*$cS1h>`zTsh^Ih$ z!5xw!?miv_g9y4LWQ`mP!V97HqOHSvEl#fb-Lac*ja@xni=Absa3i7FFd7w3v(Kh6 zE6VD+@=0AaUn=_-CLc`)wuv|e327Ye!qud!C%L5W@06Ont!1PCPJwh+oTWHcFn;vS z$J$c#K>q--PFpX*jUD~HhOGg(0fe;vDk|`VkbjFCsFtMn$<=j4{k(eH0?XZb3w@r{ zVQrr1c!6wdEJrIoNvX?fpFX$f`Lj_N{)l#UCVEKwbbtpxB$L=SlHVWqatmgZ;Z1d6 zyWZ_C1Z43^US}&6thJb|cK_(kGANWaS@rr6NyKfCXqN2VTAIUyo?8NlR>wjrPaD};^E3+K??Ls zeqcS0FuISi2nhS(sEii~;!aEY$G(wQSAbqYi4b7!QQzIYd}- zLTj3)wq} zHk~<0w4+9c_s&s+_SYqS=A$A(zA0Ai@q9g-=|Z65gmbShjba^KEBTaVfXa|4Tb=Q0 z0dc}U{t7ZLScdfenxkU|!FMXEMHa)He8b&Nfg693u8IO0*B9AX>V z44-7xiRL%bG;v$swvw$Zy~A?ECGx?b%P!oD)f{PT%KQvB?Ad!FTmsr0OaGP(KY2=D zi&06*+T9le2v+a-?Mi8$@M<8V70}*^4j3Ap9U`X-bBNi#%ooi?(mNM;XymC0_-ycv zh>qL$@7nk`FgwN^l|$HGJ8FW4gA&ondO@`A>LPfSLjl&D6HCMW<3?NC5E#ae$~#_oX0p^w%#k%4csJH|#xHo4TyMX!xi zoR)3_Q$;-cm;}+0P5GK>eM-CJvk2No@ZF7wlp4#K-n+1uS}-ZcI-=a=bW51kk#R>T z$FQr_7&y17mJk%KtUVscD znN)e-r#%YvQKri)?>GNwf}@~%@l5OdWg4VYl9>J!&cEe-m`paC!4(yIL0xg|Wc&om z9Wme*%#$`MPp;EB)l2a&{NAJww6eg@=;^!*?Mo|Le|UG0rz8>cs3we?tbPkAC)y?C!ojl)Jy5E^YD0l1OFlY*G~oB zTIv&&U|Y4usjAw4a(wE-V&`*uc+R#&10ry_FH6@2+}z1&h^x}DqXHQIkpcb7Xo)WC z!di|pxh{#kM%N~ogV(ayoLpd>u8|p-u9h=Jzp%} zC7&WVQ{F6aR}q(dGaWP9xB<(;U!o={Thid~46NI4lD@8Sxv zaisSjS00N8Bjxtg&&k--2q-B_JM`S4|3#nw2ADqQ5L!n9+@SuHEMn)3bM#1^he*ev zu3KET30C>2^5xLX+ZK@u7+~y%8qwvi%k@{nRSetla=$qypmc&qL5(~o5NL>|RI3VB z?KM;#{PF$#>KP|#JOAz%#j54|U;8W;K^@9Ar)9YZU<^>yTW3Cf0~!Yp%}5vqHh59& z7Jfxoah)C@SGRKPO##i`RdEwO}!x4*iQ<=PI-~7I|*@xw+r=CwS@=U*5 z_~T|qErj}3Xy>NIBom{BW7jWD;@k(V4+~6{=N~hZ#WFbGv&*#GY^r616h+|In>G?m+JS z#Qc{Bzed$asl^~0+D$yV8IXxZ9R4vfu3l!N=jeo&Y>xK+WPSE?6J|$6au^xY!I_># zU#ThVK*&jDr!^||X@tIzireOR@cJJdx1q4k-t2H9$d())`(gLU^dJ=?)L?)8 zf_|bC6+08LMmAZj%XOSuU$93Vo|n?@FHoPTdiB-QsLCqGb8!zf60VX7Gxw@frDx&< za23oqb$0XebOSurEW8GOE}jU~Z#d{(_TPRr)fg?5{~Ugx4US(F@s~VgHm+_?6Yna0 zN91kl93vPxl(62iG9uincyW@0jl^Tb65;rh*u&h{k~!#iNxhdWK_UUbYRnd{q5{9X z8PM19*`PiN5m2U_g>|EFJ{M*kWo}hAO&yjn*5lpMU#&l0Od?`dR3u*_(HyI>i4v`M zEr9KIH#PO)mC@8ndQQz`NMqghonKS z#9bRFu~rp}9g!-b^~^UFgvAYL1crtVQc zB#f__H=jB;eJd4NrdFca#vCzDoYBVQqR#?%2<514dg*DGja01OSKkc;D9{{6g@Yz| zk{@LoPgim0jjl?yL&M~}pK{sA(o{yb{@xT!2(o?(6pP03Ozua!_bp7)QFT>Kd_m1{ z!>zPLMrv-e_sDsO|0B&p5zI}EvU$msc!={@-A>G9b_6?=+OlT`-troR{YYzl0G$>Q zWFFIOqs5IiizpWt2*TP33by6AM!x5IcX57J zn_`LL7-%hpx&RRDv&Rz-2LTCroD@W1k9kUMHdU=d9*b_l&i+Q&WgCx|2DqkC!F?e* zMHqg;g&xvie!V(CZu=2wY+&8>bw2wwVL0)(F6lCeJ#!(op5tETQ#6HdAkInLdG9_! z4bI}v(#u$6i5Dym4p!2N=x%TWrS8N)D~=EviYT_*nSHGwQ4xvgEIa=e6Qkf&X;+TUx^!x-62K)y0hp z*eJA~5WTo?f+bT_=j(8=R3vs6K8aG&iV-;#5I+75xhvzLvADrhi98rs*(|)IbH&<_ z+GGG2-%SXu6q(LiWg$CYfsXhQf0c?}fSO@H>tugW=bd)qBR|mEjqv{a;Lf9c z+|?Lja~_mpe^(O%PxAMBWGdL4Fj+oKpT~%4*&PI^Ue9>BdrrJfF%b)+Ipq5Bk(m)L z0iDyPA=J%YH@4%Ro{}ks%2?-dod)JRA1TJmH01;oxrVh5;j%ElgW%)k9fgWAYb&;W z$Mafdy0}s;L|Sj5)|6H7ekpSj&1XdX{s`NZSJqNN3;u*<9s+LO{+?Zq{q0jSHvweX zVl#YgkrLQZhUma$Ox z)21M@%FG@CS&(U^j?xFa~KD@bQ2GHMk~U$h2X z21}(I2kpBw4!eAbyHm$;LS&v4QGET6NDeA#e(WWw zW%t@!fjg7&nvk+I7Z|{}{Tbg}eN+gNhhFci4bDQcSvzYmxW7f73Hr*=o~$9P{-Y*&tTZnjXQ-Qh}*Y0B~j zlm^>L2sgE>=_&e^gu(^j_7#S#QMzu@iiFka^20wJ@de`>@9z~*?5A7rb$ED_$U7Bp zJbY@nbXAXzHT*%p)Ee%L=zA7z6nZio*E|7Y*h}VOk*3G4wjB2Y9SbSo=e+`heU?7Q&3$Y!>Ig)$obyL=h$mQI>7>JS1&&oF$xoK9-y z<31u%ckC|6&>*)?yWRHS))sJ~b&k5-k9-6Pk6&CLS+D&Q+fpxjJRV}Do0TpyH0*E% z8T$0%C((HX%PN)CM=gQp2GN#-+2e+k+!QN|o7$8hr~~Ii0qv>c)`YnIPTB!?$|3MQ zQ-%XMbT4~-2&_aMc3@qv?{r+ypq!l}m>cHF+sM-AUC-8k={-uV>5m{H@$W`w0r#|u zE0jj+mvt01ER7hJT}*(zfqkJvhs;!+s9&l(=~ME4NvG~tIPoA3y#D9jWn&I`Tfgcg!s zlxvM|%;8nM(1YY#8i#UT%P&^Hf*WCqFjx?zYk(B?Hj!Nzy6hxw<2L|KC| zW;QVlYkbldRDm8O%I(PFB_dJmyJUF|{^OHRVA%Bb=7U2)FCJltXKJP5lUJ>6`Q@Cz=F z*Z@DmbV&_oo)4%ta8oph#{n z#O!|n>^|RHVDjPBXo&T3CW?qO?hfwSv5)SEuG^?|3Kl>~&kU2im9&$sWNhUAIO=|t ze`V-0_#dVQ#RpsAF%YVFO?!o?x`oK+T}6zBeJ(EV+)$39QSbMZY>9Y1h0eZ6e%~sx zUG0Ou{#Dl@M*s?XdnK~MwX>brlp?n$5)?++mL1_g9YbT#Iu3q$2}eeH=x<5PId5E( z4)0kwfsxV{OU?=axmM~e@Qfp?38hGdM1((d<1um0`$f7j^>|CDV2G}6oG=< M-~YlRhvIwwKg66PRsaA1 literal 0 HcmV?d00001 diff --git a/src/data/projects.json b/src/data/projects.json index 5f5a1f4..42984b9 100644 --- a/src/data/projects.json +++ b/src/data/projects.json @@ -1,4 +1,15 @@ [ + { + "slug": "tfnsw-train-alerts", + "comingSoon": false, + "title": "Train Alerts: a nudge before every Sydney train", + "company": "", + "description": "A departure alert service for Sydney trains built on Cloudflare Workers and Durable Objects. Pick a station, a lead time and your travel window and it pings you before each train actually leaves, following the live delays, with an \"I'm on board\" tap that keeps the rest of the window quiet.", + "tech": ["TypeScript", "Cloudflare Workers", "Durable Objects", "Cloudflare D1", "Hono", "Web Push / VAPID", "WebCrypto", "Resend", "TfNSW Open Data", "Vitest", "Playwright"], + "period": "July 2026", + "order": 1, + "content": "

Overview

\n

My train ritual has never been a proud one. When it's getting close to home time I'll open the transport app, check the next few departures, get pulled back into whatever I was doing, then look up twenty minutes later and check it all over again. Some days the timing works out. Plenty of days it doesn't and I'm either jogging up the platform stairs as the doors close or standing around forever because the train I was aiming for is running eight minutes late. All that checking, over and over, for a piece of information the network already knows. When is my train actually leaving?

\n

That question is what finally got me moving. I didn't want another app to check. I wanted the answer to come to me. A ping two minutes before my train actually leaves, following the live delays, only on the days and times I travel. That's when it clicked. I build on Cloudflare constantly and my tennis calendar is still quietly doing its thing from a Worker right now. Durable Objects have an alarm feature where a tiny stateful object can put itself to sleep and wake at an exact moment, which is precisely the shape of \"ping me two minutes before each train\". So I went and built it! Train Alerts is a small web service where you pick a station, how much notice you want, the days and window you travel in and whether you want the alert by push or email. From then on it watches the live departures and nudges you before each train you could catch. Once you're on, you tap \"I'm on board\" and it stays quiet until your next window.

\n\"The\n\n

How it works

\n

The data comes from Transport for NSW's Open Data platform. Their Trip Planner API has a Departure Monitor endpoint that hands back the upcoming departures from a stop as plain JSON with the realtime estimates already merged in, so there's no GTFS protobuf wrangling at all. A second endpoint, Stop Finder, powers the station autocomplete on the form. The free tier gives you 60,000 calls a day which is heaps for a personal tool like this.

\n\"The\n

The part I really wanted to get right is the timing. A fixed cron can't do \"two minutes before each train\" because the target never sits still. When a train runs late the alert has to slide along with it. So every alert is its own Cloudflare Durable Object with a self-rescheduling alarm. Inside your travel window it wakes, pulls the latest departures for your station, fires a notification for any train now inside its lead time and then sets its next alarm for whichever comes first, the next exact fire time or a re-poll 45 seconds later. Outside your window it does nothing. It sets a single alarm for the start of the next window and goes back to sleep. Each alert also lists the next couple of trains behind the one it's pinging you about, so if you miss the 5:12 you already know the 5:22 is coming.

\n\"Choosing\n

Schedules are Sydney wall-clock time with travel days, a start and end for the window and even fortnightly days for rosters that alternate. The subscriptions themselves live in a D1 database, which stays the source of truth. A cron fires every 15 minutes purely as a safety net, so if a Durable Object ever loses its alarm after a deploy it gets re-hydrated from its D1 row and carries on. The \"I'm on board\" tap is a proper state change too. It mutes the rest of the current window in the same transaction that arms the wake-up for the next one and the alarm loop re-checks that state after every await, so a stray notification can't slip out after you've already boarded.

\n

Delivery is push or email. Email goes through Resend with a manage link in every message so you can pause or cancel from the alert itself. If the push service ever reports a subscription as gone, the alert deactivates itself rather than pushing into the void forever. The frontend is one dependency-free page, no framework, with the autocomplete, a live preview of the alert you're building and a proper install walkthrough for iPhones, because iOS will only deliver web push to a site that's been added to the home screen. All up it's about 2,000 lines with exactly one runtime dependency, Hono for the routing.

\n\n

Sticking points

\n

The API that swore every station was invalid

\n

Early on the station search flat out refused to work. Every query came back with zero locations and a \"stop invalid\" message, even for Central. The culprit was one innocent looking parameter. Stop Finder takes a type_sf field and I'd set it to stop, because I was searching for stops. It turns out type_sf=stop actually means \"the text I'm sending is a stop ID\", so the broker was taking the word Cronulla and trying to look it up as a numeric station identifier. The fix was type_sf=any and then filtering the mixed bag of streets and points of interest down to actual stops. I only caught it because I verified the client against the live API instead of trusting the docs. The mock TfNSW server my Playwright tests drive now mirrors that exact broker behaviour, so this one can never quietly come back.

\n

Daylight saving and the missing hour

\n

Active windows are Sydney local time and my first version of \"when does today's window start\" was simple minute arithmetic from the current moment. An adversarial review pass over the whole codebase caught that the simple version drifts by exactly an hour whenever the calculation straddles the 2am daylight saving changeover, which in the worst case means a whole morning of missed alerts. The fix was to stop doing arithmetic and start constructing the actual Sydney wall time, using Intl with the Australia/Sydney timezone to check the guess and correct it. I reckon DST bugs are the sneakiest kind there is, because the broken code is provably correct for 363 days of the year.

\n

Web push from scratch, byte for byte

\n

The web push libraries everyone reaches for are built for Node and lean on APIs a Worker doesn't have. Rather than fight that, I wrote the push layer from scratch on WebCrypto. That means two RFCs. VAPID (RFC 8292), an ES256 signed JWT that proves to Apple's or Google's push service the message really came from my server. Then the payload encryption (RFC 8291), a chain of ECDH key agreement, HKDF derivations and AES-128-GCM. Hand rolling crypto is nerve-racking because the failure mode is silence, the push service just drops anything malformed and tells you nothing. The saving grace is that the encryption RFC ships a complete worked example with every intermediate value spelled out. So I made the encrypt function accept an injectable random seed and wrote a test that reproduces that worked example byte for byte. When it went green I knew the scariest part of the codebase was actually correct rather than just plausible.

\n\n

The result

\n

I couldn't be happier with how this one came out! The build took a couple of days with Claude Code alongside me and it does exactly the job I set out for. My phone buzzes two minutes before the train actually leaves, the alert already knows it's running late and one tap on \"I'm on board\" keeps everything quiet until tomorrow's window. No app open, no refreshing, no platform waits. It's all covered end to end as well, Vitest across the schedule and crypto logic and Playwright driving the real Worker against a mock TfNSW upstream, so I can keep tweaking it without holding my breath.

\n

If you're thinking about doing web push yourself, here's the tip I wish I'd had on day one. Don't test your encryption by firing real notifications and hoping. Make the random inputs injectable and verify your output against the worked example in RFC 8291 first, because the push services silently drop anything malformed and you'll get no clue why. Once that test passes, everything downstream is just plumbing. Happy coding!

" + }, { "slug": "album-tracker-cli", "comingSoon": false, @@ -7,7 +18,7 @@ "description": "A Node.js CLI that scrapes upcoming album releases from Wikipedia, ranks a Top 10 Suggestions block from your Spotify follows and syncs the picks you make to Google Calendar, Outlook or an .ics file as free 9am reminders on release day.", "tech": ["Node.js", "JavaScript", "Cheerio", "OAuth 2.0 / PKCE", "Spotify Web API", "Google Calendar API", "Microsoft Graph", "iCalendar", "node:test"], "period": "July 2026", - "order": 1, + "order": 2, "content": "

Overview

\n

Music has always been one of my main passions and keeping up with new releases is a big part of that. The annoying thing is I kept finding out about albums late. An artist I follow would drop a record and I'd only catch it weeks later when a mate mentioned it or the algorithm finally decided to show me. Wikipedia even keeps a properly maintained list of every album scheduled for the year, so the information is all out there. It just never reaches you at the right moment.

\n

That's when it hit me. I've built calendar tools before. My tennis Grand Slam feed is still quietly doing its job in my calendar right now. Album releases are the exact same shape of problem, known dates that you want to show up on your phone with zero effort, except this time the list is hundreds of albums a month and only a handful are mine. What if the CLI scraped the lot and let my own Spotify follows work out which ones I'd actually care about? So that's what I built! Album Tracker CLI is a Node.js command line tool that scrapes upcoming releases from Wikipedia, lets you pick the ones you want in an interactive terminal checklist and adds each one to your calendar as a 9am reminder on release day. Connect Spotify and it ranks a Top 10 Suggestions block from the artists you follow and listen to most.

\n\"Album\n\n

What I built

\n

The flow is straight forward. You run pnpm start, choose where the releases should go and start ticking albums.

\n\"The\n
    \n
  • The scraper pulls Wikipedia's annual album list (e.g. List of 2026 albums) and parses the tables for the current and next month with Cheerio. The tables are mostly tidy but not quite. Date headers span multiple rows and sometimes arrive squished together like \"July3\", so the parser cleans up as it walks the rows.
  • \n
  • The checklist is a searchable multi-select in the terminal. Type to filter, spacebar to toggle, enter to confirm. Nothing is ever pre-selected.
  • \n
  • Spotify personalisation is optional. Log in once with OAuth and the CLI fetches the artists you follow plus your long-term top artists, then surfaces a ranked Top 10 Suggestions block above the full A to Z list. The follows are cached locally so a normal run boots instantly without touching Spotify, re-syncing only when the cache is over a day old or you pass --refresh.
  • \n
  • Three destinations. A standard .ics file you can import anywhere, or direct sync into Google Calendar or Outlook where the CLI lists your writable calendars, lets you pick one and skips anything it's already added so re-runs never double up.
  • \n
\n\"The\n

Every release lands as a short reminder at 9am on release day, marked free so it never blocks your actual schedule. Each event carries a Spotify search link so you're one tap from listening the moment it pops up.

\n\n

Keeping it small

\n

The whole thing is plain JavaScript running on Node, about 1,900 lines across ten small modules, with exactly four dependencies. Cheerio for the parsing, prompts for the checklist, open for popping the browser during login and dotenv for config. The tests run on Node's built-in node:test runner so there isn't even a test framework installed. I wanted a tool I could come back to in a year and read top to bottom in one sitting. Keeping the dependency list that short is most of how you get that.

\n\n

Sticking points

\n

Three OAuth providers, one dance

\n

Spotify, Google and Microsoft all speak OAuth, and all slightly differently. The first cut gave each provider its own copy of the login flow, the local callback server, the browser hand-off and the token exchange. The copies drifted exactly like you'd expect. The Google copy was missing its CSRF state check entirely and two of the providers were writing the client secret into the local auth file alongside the tokens. The fix was pulling the whole dance into one shared OAuth module. A single authorizeViaBrowser helper generates and verifies the state parameter itself so no provider can forget it, and the auth file writer strips secrets on every write so the only things that ever hit disk are tokens and non-secret app IDs.

\n\n

Spotify quietly stopped accepting localhost

\n

Partway through, Spotify logins started failing with redirect URI errors that made no sense, because the exact same value had worked before. It turns out Spotify no longer accepts the localhost hostname in redirect URIs at all and requires the loopback IP literal instead. Google and Microsoft accept 127.0.0.1 happily, so every provider now shares the one loopback host and the problem can't come back.

\n\n

Suggestions, not selections

\n

The first version auto-selected every album by an artist you followed, which sounded helpful and felt awful. I follow a lot of artists and not every record they put out is one I want a reminder for, so each run started with me un-ticking things, which is backwards. The redesign flipped it. Spotify data now only ranks and labels, surfacing the Top 10 Suggestions block at the top of the list, and every actual selection is yours. I reckon recommendation belongs in the presentation, not in the selection state.

\n\n

Reminders, not blocks

\n

The first cut created all-day events. After a couple of weeks of living with it my calendar looked booked out on release days, when what I actually wanted was a nudge. So releases became 15 minute events at 9am local time, marked transparent so they never show me as busy, with the times written as floating local values so the reminder fires at 9am wherever the calendar happens to be. The event title, description and Spotify link are all built in one shared function too, because Outlook's duplicate check matches on the event subject and the fastest way to break that is letting two providers drift apart on the title format.

\n\n

The result

\n

I was really happy with the end result. I was able to spin up this simple JavaScript CLI using AI agents in only an hour and it's turned a real world problem into a once a month two minute task. What more could I want! I run it, tick the albums I'm keen on and release day mornings sort themselves out. A quiet 9am reminder, one tap into Spotify and I'm listening to a record I would've otherwise found out about three weeks late.

\n

The full source is on GitHub if you want to see how it all fits together. If you're building anything that does OAuth from a CLI, my one bit of advice is to set up a shared auth layer before you add your second provider, not after. The login dance looks provider-specific but it's 90% identical. The 10% that differs is exactly where security bugs like a missing state check hide when every provider owns its own copy. Happy coding!

" }, { @@ -18,7 +29,7 @@ "description": "Turning HTML into a good-looking PDF sounds trivial until you actually try it. So I built a full Next.js SaaS on Vercel that does it properly, with a free drag-and-drop web tool and a paid developer API for generating PDFs from your own app.", "tech": ["Next.js", "React", "Puppeteer", "Stripe", "Resend", "MongoDB", "JWT", "Vercel", "Redis", "Material UI"], "period": "Feb 2025 — Sep 2025", - "order": 5, + "order": 6, "content": "

Overview

\n

Turning HTML into a good-looking PDF is one of those jobs that sounds trivial right up until you actually try it. I wanted to take that little feature and build a proper full-stack SaaS around it, the whole thing end to end, from a free tool anyone can use to a paid API a developer can call from their own app. So that's what I did! HTML to PDF Converter is a Next.js app running on Vercel that turns HTML files into print-ready PDFs. It comes in two halves. A free web interface for quick one-off conversions and a paid developer API for generating PDFs programmatically.

\n\"Upload\n\n

What I built

\n

The web tool lets you drag and drop up to 10 HTML files, set the page format, orientation, margins, scale and output mode, then download the result as a single PDF, one merged PDF or a ZIP of separate PDFs.

\n

The developer API is the other half. It adds accounts, JWT API keys, usage tracking, Stripe subscriptions and rate limiting. You generate a key from the dashboard and call a REST endpoint to convert HTML straight from your own application.

\n\"Developer\n\n

Technical architecture

\n

Under the hood it's Next.js App Router, React, TypeScript, MongoDB, NextAuth, Stripe, Upstash Redis, Vercel Blob and Puppeteer. The one bit worth calling out is that it runs Puppeteer two different ways. In development it uses the full Puppeteer. In production it swaps to puppeteer-core with @sparticuz/chromium so Chromium can actually run inside Vercel's serverless environment.

\n

The conversion flow itself is nice and simple:

\n
    \n
  1. You upload one or more HTML files.
  2. \n
  3. The API checks the file type, the file count and the file size.
  4. \n
  5. Puppeteer renders each HTML document to PDF using the options you picked.
  6. \n
  7. Multiple PDFs get either merged with pdf-lib or archived with archiver.
  8. \n
  9. The finished file is stored behind a session-based download route.
  10. \n
  11. The UI streams progress back to you the whole time using server-sent events.
  12. \n
\n\n

Sticking points

\n

Getting Chromium to run on serverless

\n

The hardest part by a mile was making the PDF rendering reliable in production. Running Puppeteer locally is easy. Getting Chromium to run in a serverless environment is a different story, because you need a smaller runtime binary and a specific set of launch flags to fit inside Vercel's constraints. So I split the renderer in two. Local development uses Puppeteer directly. Production uses puppeteer-core with @sparticuz/chromium.

\n\n

Keeping people in the loop on long conversions

\n

A batch conversion can take a few seconds, especially when someone uploads a bunch of chunky HTML files. Rather than leaving the screen sitting there doing nothing, the web route sends back a streaming response and reports each step as it happens, whether that's rendering, merging, archiving or getting the download ready.

\n\n

Handling the three output cases cleanly

\n

The app had to cope with three different outputs. A normal single PDF, one merged PDF when there are multiple inputs or a ZIP with separate PDFs inside. I pulled the PDF creation logic out of the route handler so the rendering, merging, zipping and download metadata could all be tested on their own.

\n\n

Keeping the rendering costs under control

\n

Browser rendering isn't cheap, so both halves of the app sit behind Upstash Redis rate limits. The public web tool is limited by IP. The API is limited per user and tied to their subscription state, which keeps the whole thing safe from someone hammering it.

\n\n

The result

\n

What I ended up with is a proper working HTML-to-PDF service. A free public converter, a paid API, account management, API key controls, Stripe billing and production-ready serverless rendering, all in the one app. It took a feature that sounds trivial and turned it into a full SaaS workflow. Upload, render, download, sign in, bill and keep the system safe from abuse.

\n

The developer docs are over at htmltopdfconverter.com.au/docs. If you're thinking of building something that renders PDFs on serverless yourself, my one bit of advice is to sort out the production Chromium setup first before you build anything on top of it. It's the part most likely to bite you, so get it solid early. Happy coding!

\n\"API" }, { @@ -29,7 +40,7 @@ "description": "A React Native strength-logging app I'd had in mind for over 10 years, finally built, tested and shipped to the App Store. Half of it was generated autonomously with Claude Code using the Ralph technique, the other half hand-crafted by me.", "tech": ["Expo", "React Native", "TypeScript", "Supabase", "NativeWind", "Tailwind", "Reanimated", "React Navigation", "Biome", "Jest"], "period": "April 2026 — June 2026", - "order": 4, + "order": 5, "content": "

Gym Log

\n

Gym Log is a React Native app for logging strength workouts. Programs, sessions, personal bests, rest timers and progress charts, all running on Expo and Supabase. It's a side project I'd had knocking around in my head for well over 10 years. I finally sat down and built it.

\n\"Gym\n

It started as a personal itch. Every gym tracking app I tried either buried the actual logging of a set under three taps or wanted a subscription for the features I really wanted. So I built my own. Something straight forward that treats a workout like what it actually is. A list of exercises, each with a list of sets, each with its reps and weight. I kept it to just that until the basics felt right.

\n

Gym Log is now live on the App Store, with more over at gym-log-app.com.

\n\n

Background: Claude Code and the Ralph technique

\n

Here's the fun part. The first eighteen commits of this project, so everything from the Expo scaffold through to a working auth flow, the CRUD services, the navigation and all five core screens, weren't written by me at the keyboard. They were generated by running Claude Code inside an autonomous loop, using a workflow Geoffrey Huntley calls the Ralph technique.

\n

Ralph, at its simplest, is a Bash loop. while :; do cat PROMPT.md | claude-code; done. You write a single prompt that points the model at a backlog, in my case a numbered list of user stories (US-001, US-002 and so on), you give it the rules of the game (one story per iteration, run the tests, commit on green, mark the story done) and then you let it run. The model picks the next unchecked story, builds it, checks its own work and commits. Then the loop fires again. You go to sleep and it keeps shipping.

\n

What I found is that this works genuinely well for the boring foundational scaffold of a project, the part where most of the decisions are mechanical and the cost of a wrong call is low. It gave me a clean, typed, tested foundation in a single overnight run that would've taken me a long weekend by hand. The tidy one-commit-per-story history it left behind made the codebase really easy to move around in afterwards. Where Ralph was less useful was the second half of the project. The redesign, the placeholder UX, the dark mode retrofit. Those needed taste, actually living with the app and the kind of \"no, that feels wrong, undo it\" judgement that just doesn't fit inside a loop. So the project is genuinely a hybrid. An autonomous agent for the bones and me by hand for everything that gives it its feel. I reckon that split is the honest answer to \"how much of this did the AI write\".

\n\n

What I built

\n

The app is built around four screens that match how I actually use it at the gym.

\n\"Programs\n
    \n
  • Workouts are reusable program templates like \"Push Day A\" that you turn into a session each time you train. When you start a session it pre-fills the set fields with placeholder values from your last session of that template, so you can see what you lifted last time and either match it or beat it with a single tap.
  • \n
  • Session detail is inline editing for everything. The exercise name, the set reps, the set weight. No modals, no save buttons. It just auto-saves on blur. You can group exercises into supersets or drag them to reorder mid-session. A back-navigation guard catches you before you walk away from unsaved work. A configurable rest timer slides up between sets with haptics and a notification when it fires.
  • \n
  • Progress gives you weight progression and total-reps-per-session charts for each exercise, with a date-range selector, tap-to-view tooltips and a full-screen landscape mode for reading dense data.
  • \n
  • History and Favourites is your read-only past sessions with a \"Repeat workout\" action, plus a starred view that pins your favourite programs to the top.
  • \n
\n\"Session\n\"Workout\n\"Progress\n

On top of the core loop, the app auto-detects personal bests (I renamed these from \"PR\" to \"Personal Best\" partway through after testing it on myself, because \"PR\" read as ambiguous), exports a session as a shareable image card and can fire a daily streak reminder at a time you choose to nudge you back to the gym. Auth and storage are handled by Supabase, with row-level security so each user only ever sees their own data.

\n\"Account\n\"How\n\n

Technical architecture

\n

Stack: Expo SDK 55, React Native 0.83, React 19 and TypeScript on the front. Supabase on the back for Postgres, Auth and row-level security. NativeWind v4 with Tailwind v3 for styling, React Navigation v7 for routing, Reanimated v4 for animation and Gifted Charts for the graphs. Biome handles lint and formatting. The tests run on Jest with React Native Testing Library.

\n

The data model is four tables. profiles (which extends Supabase's auth.users), workouts, workout_exercises and workout_sets, each one protected by row-level security so a user can only ever read or write their own rows. The first cut of the schema was actually only two tables. workouts carried its exercises and sets inline as a jsonb blob, on the assumption that every read was really \"fetch the whole workout\" and a single round trip would be fastest. That held up fine until the Progress screen landed. Charting weight progression for one exercise across months of sessions meant pulling every workout, hydrating the JSON in JS and then filtering, which got noticeably laggy as my own history grew. Migrating to normalised workout_exercises and workout_sets tables (with indexes on workout_id, exercise_id and the exercise name) let those queries push the filtering and joining down into Postgres where it belongs. The per-exercise history sheet went from a visible pause to instant. Well worth the migration. Would have been worth doing up front if I'm honest.

\n

The state is layered. A SupabaseClient singleton at the bottom, then thin service modules (authService, workoutService) that own all the I/O, then React Context providers (AuthContext, WorkoutContext, ThemeContext) that hand typed hooks to the screens. No Redux, no Zustand. Context was plenty because the data fan-out is shallow and the writes are all user-initiated.

\n

Auth tokens persist through expo-secure-store (it was AsyncStorage originally, swapped out early when I realised the JWTs were sitting there in plaintext). The theme preference persists locally too, with the write deferred off the interaction frame after profiling showed the toggle felt a bit sticky on older devices.

\n\n

Sticking points

\n

The \"Programs vs Workouts\" rename

\n

The first version of the app had a single concept, a Workout, that you'd either create blank or duplicate. After living with it for a few weeks I realised what I actually wanted was a clean split between the template (the program I follow week to week) and the instance (today's session). The redesign touched nearly every screen. A two-zone Workouts list, a new \"Start\" action that turns a template into a session, a source_template_id foreign key on instantiated workouts so I could pull last session's placeholders forward and a soft-delete on empty sessions so abandoned starts didn't clutter up the history. Worth every bit of it, because the app now matches the way I actually think about my training.

\n

Placeholder UX

\n

Pre-filling the set inputs with last session's values sounds simple until you ask the real question. Are those values the user's data or just a suggestion? If they're data, then saving an untouched session double-counts last week's lift. If they're a suggestion, the user needs a clear signal that's what they are. I landed on rendering them as muted placeholder text rather than real input values, tracking edited-versus-untouched state for each set and showing a banner on instantiated sessions until the first edit lands. Empty sessions get soft-deleted on save so the history stays honest.

\n

Dark mode without a rewrite

\n

The app was built dark-first with hardcoded Tailwind colour classes all over the place. Retrofitting a theme system meant either a giant search-and-replace job or something smarter. I went with CSS custom properties. I defined the colour palette as --color-* variables in global.css, redirected every Tailwind token through var(--color-*) in the config and let NativeWind's CSS interop carry the values into the native views. A single ThemeProvider swaps the variable values at the root and every styled component re-renders with the new palette. No per-component changes needed beyond a handful of imperative colour usages (the charts, the rest timer bar) that I had to migrate by hand.

\n

Email confirmation and the App Store round-trip

\n

Signup runs through Supabase email confirmation. The confirmation link deep-links straight back into the app (gymlog://auth/callback) instead of dead-ending in a browser. Profiles get created by a Postgres trigger on auth.users rather than a client-side write, so a freshly confirmed account always lands with its profile row already in place. No race between \"account exists\" and \"profile exists\". Getting that flow watertight took a couple of App Store resubmissions. The deep-link handling and the confirm-then-return path are exactly the kind of edge cases that only really show up once a real reviewer and real users are in the mix.

\n

Shipping discipline

\n

The commit log honestly reads like two different projects stuck together. A methodical first pass (US-001 through US-018, then US-101 through US-108, every commit a single user story) and a much messier \"make it real\" phase (the redesign, lint cleanup, the DB latency migration, all the polish). The structured phase got the bones right. The messy phase is where the app actually turned into something I'd happily recommend to a mate. I'd run it the same way again.

\n\n

The result

\n

What started out as \"I'll knock up a tracker in a weekend\" turned into a proper fully featured fitness app that I now use four times a week. It's live on the App Store. Building it user story by user story up front made all the later, scrappier work (the redesign, the dark mode retrofit, the placeholder UX) far cheaper than it would've been otherwise, because the foundations never had to move. If you're keen to give the Ralph technique a go yourself, my advice is to point it at the boring scaffold and keep the taste calls for yourself. That combination is where the real magic is. Happy coding!

" }, { @@ -53,7 +64,7 @@ "Claude Code" ], "period": "June 2026", - "order": 3, + "order": 4, "content": "

Overview

\n

Back in 2015 I was off on my first Europe adventure. During that first summer I was lucky enough to visit one of the best festivals in the world, in my favourite city in the world, Dekmantel Amsterdam. It was 3 days of awesomeness that I'll never forget. The people, the music and the experience were all just 10/10, and I even got lucky with the weather and had nice sunny days. Music has always been one of my main passions, and since then I've always remained on their mailing list. Each year when the lineup drops and the timetable is announced I'm always a bit jealous and wish I could be there. This year when I saw the timetable email, I had an idea. What if I built my own MCP to turn these images into a proper festival SoundCloud playlist?

\n

So that's what I did! I'm deep in AI coding techniques and methods and use them daily at work, and I'm always watching YouTube videos from the leading voices on the best Claude Code methods and AI architecting approaches. So having that in mind, and my desire to learn more about MCPs, it was all I needed to dive into it. What I ended up with is a public 105 track playlist with one best mix per artist across the whole Dekmantel 2026 lineup, and honestly it's been such a good way to get into this year's names. Underneath it is a Model Context Protocol (MCP) server running on Cloudflare's edge that an assistant like Claude can drive to search SoundCloud, sort out the artists, rank their mixes and build the playlist for me.

\n\n

What is MCP, and why build one?

\n

If you haven't come across it before, Model Context Protocol is an open standard for handing an AI assistant a set of tools it can use. The easiest way to picture it is a typed menu of actions the model can read and call directly. Instead of writing a one off script that only ever does one job, I wrote a server that hands SoundCloud over to the model as a toolbox. Things like search_tracks, resolve_artist, find_mixes, best_mix_per_artist and create_playlist. Once it's connected the model can work out the plan and run the whole job itself, and I get to reuse the same toolbox for anything else I want to do on my account down the track.

\n

It ended up being 27 tools all up, grouped into three lots. The discovery tools for searching and resolving tracks, users and playlists. The DJ mix smarts, which is the artist resolution and mix ranking logic that makes the festival idea actually work. And the engagement tools for the writes, so liking, reposting, following, commenting and the full playlist CRUD.

\n\n

How it works

\n

The server itself is a Cloudflare Worker, and if you peel it back it's really just three layers wrapped around that list of tools.

\n
    \n
  • An OAuth provider sitting at the edge that handles the connection coming in from the AI client and sends the traffic where it needs to go. Anything hitting /mcp goes to the agent and everything else goes to the auth routes.
  • \n
  • A per user MCP agent built as a Cloudflare Durable Object. It holds the actual MCP server instance and is tied to one logged in SoundCloud user, and it speaks the Streamable HTTP transport that remote AI connectors expect.
  • \n
  • A tool registry where each tool is just a plain object with a name, a description, a Zod schema (which the SDK turns into the JSON Schema the model reads) and a handler. One loop registers all 27 of them, and every handler is thin glue sitting over a typed SoundCloudClient, so none of the actual logic lives in the tools themselves.
  • \n
\n

The bit that trips people up is that there are two separate OAuth flows going on at once. First the AI client logs in against my server, so in that handshake my server is the one minting the token the assistant carries around. Then my server logs in against SoundCloud, so now my server is the client. Connecting the tool fires both of these back to back, and the user's SoundCloud tokens get stored encrypted in Workers KV and refreshed whenever they're needed.

\n\n

The actual hard part: turning a flyer into the right artists

\n

A festival lineup is a nightmare for a computer to read. The bill doesn't say \"Shed\", it says \"Shed presents Rave Echoes\". It says \"Ben UFO & Call Super & Objekt & Pariah\", and \"DJ Sprinkles' Deeperama\", and \"Sass (Moxie & Peach & Saoirse & Shanti Celeste)\". Before you can go and find anyone's best mix you first have to work out what each of these billings even means.

\n

So the first pass is a big dedup with some clear rules. Split the back to back and collective billings out into individual artists, treat \"X presents Y\" as just the headliner X, and drop anything that isn't a DJ at all like the live bands, the panel talks and the sound systems. That got the roughly 150 raw billings down to 164 unique artists.

\n

Then every one of those names has to become an actual SoundCloud profile. The resolver scores each candidate on a handful of signals like a permalink guess, an exact name match and how dominant their follower count is, with genre only counting as a bonus when it's actually there, because real DJ mixes almost never bother tagging a genre. The one rule I really cared about was that it should never silently guess wrong. It only auto picks when it's genuinely confident, and if it isn't it hands back a ranked list of candidates and an honest reason why (unresolved, low_confidence or no_mix) and leaves the final call to me. Out of the 164 artists, 105 came back with a confident best mix. The other 59 came back with honest reasons, and a few of the big names like Jeff Mills and Ricardo Villalobos genuinely just don't post long mixes on SoundCloud.

\n\n

Sticking Points

\n

A successful write that reported itself as a failure

\n

Once it was deployed I tested the repost tool against my real account and it came back with Unexpected end of JSON input, which of course looks like it failed. Except it hadn't. The repost had actually worked and was sitting right there on my profile. It turns out SoundCloud answers some of its write requests with a completely empty 204 No Content body, and my HTTP layer was blindly calling response.json() on every 2xx response, which throws the second there's no body to parse. So every successful unlike, unfollow and unrepost was getting handed back to the user as an error. The fix was tiny, just check for the empty 204 case before trying to parse it, but I only found it because I actually hit the live endpoint rather than trusting a green test suite. All my tests were mocking full JSON bodies, so the bug was completely invisible to them.

\n\n

The rate limit cascade that lied about who exists

\n

Trying to resolve all 150 odd artists in big batches kept tripping SoundCloud's rate limiter, and the way it failed had me going for a while. Each artist resolution makes a few API calls, so when a big burst hit the limit those failed lookups were getting reported back as unresolved, which of course reads as \"this artist doesn't exist\". Ben UFO, one of the most famous DJs on the whole lineup, came back \"unresolved\" in a batch of 23 and then resolved instantly when I ran him in a batch of 3. It was a burst problem dressed up to look like a data problem. The fix came down to three things. Smaller batches, a cap on how deep the per artist track pagination is allowed to go, and an abort flag so that the moment any worker hits a 429 the rest of the batch stops instead of piling on and making it worse. When I later ran the whole branch through an adversarial code review, the reviewer landed on that exact same unbounded pagination as the root cause completely on its own, which was a nice bit of confirmation I'd fixed the right thing.

\n\n

An OAuth race I designed away

\n

The SoundCloud login uses a PKCE flow, and the first version of it stashed the PKCE verifier in Cloudflare KV keyed by the OAuth state parameter. The catch is that KV is eventually consistent, so when SoundCloud redirected straight back to me (which it does, because my account was already authorised so there's no consent screen to slow things down) the callback would sometimes try to read the verifier before the write had even landed. That gave me an intermittent \"expired or unknown state\" error that would magically fix itself on a retry. Rather than just papering over it I made the whole flow stateless. I encrypt the verifier and the original request into the state parameter itself using AES-GCM, then decrypt it again on the way back. No KV round trip, no race, and I baked a 600 second freshness check into the encrypted payload to keep the same expiry behaviour I had before.

\n\n

How it was built

\n

The whole thing was built test first and ended up with 124 tests covering the ranking logic, the resolver's confidence contract, the OAuth handlers and all the fiddly HTTP edge cases. Then I put it through a full adversarial code review before merging, which is exactly where the rate limit and empty body issues got pinned down and fixed properly under TDD. It was also a genuinely agentic build, which was half the fun. I worked through the whole thing with Claude Code, and once the server was live the agent didn't just write the code, it actually drove the tools against live SoundCloud to build the playlist. It pulled the lineup out of the festival flyer images, ran the dedup, called the resolver across all 164 names, helped me recover the tricky ambiguous headliners and then created the final playlist. Building the system and running it turned into the same loop, which was a really cool thing to watch.

\n\n

Result

\n

What I've ended up with is a live MCP server running on Cloudflare's edge and, better still, a real artifact to show for it. A 105 track Dekmantel 2026 playlist with one best mix per artist, all sequenced in festival day order. It's exactly the thing I wished was waiting in my inbox when that timetable first dropped, and it's genuinely how I'm getting to know this year's lineup now.

\n

I'd also like to say that to do this it required upgrading to SoundCloud's Artist Pro account so I had access to their API. So for those other engineers thinking of building something the same, I'd advise doing this before you jump in! Happy coding!

" }, { @@ -64,7 +75,7 @@ "description": "How a missed Grand Slam on the TV turned into a self-updating iCalendar feed built on Cloudflare Workers, Workers KV and Hono, with the date-source and timezone gotchas that make it reliable.", "tech": ["TypeScript", "Cloudflare Workers", "Workers KV", "Hono", "Cron Triggers", "iCalendar"], "period": "May 2026", - "order": 2, + "order": 3, "content": "

I wasn't planning to build anything that night. I was on the couch catching up with my mum when a Roland-Garros ad came on the TV. The French Open had already started and neither of us had a clue! Two people who genuinely love their tennis. We'd missed the opening days of a Grand Slam just because nobody told us it was on. Gutted.

\"The

That's when it hit me. I've built Icals before. This would be the perfect tool to ensure we never miss a slam event again! Four tournaments a year, always on my phone, always up to date, no app to install and no newsletter to skim. I work with Cloudflare a lot and I love it, so I knew almost straight away that Workers KV and Hono would let me do exactly that. Something light and fast that I could genuinely set and forget.

The shape of set and forget

The whole thing rests on splitting the system into two paths that never touch each other. A calendar client polls a subscription URL a lot and unpredictably, so the path that answers those requests has to be cheap and boring. A separate path does the slow, fragile work of going out to the internet to figure out the actual dates. It runs on its own schedule, not on the visitor's.

In Cloudflare terms the serve path is the Worker's fetch handler. A subscriber hits /slams.ics and it returns a pre-rendered calendar string straight from Workers KV. No parsing, no outbound calls, just a key read at the edge. The refresh path is the Worker's scheduled handler, driven by a weekly Cron Trigger. It fetches the dates, checks them, renders the calendar once and writes the result into KV. Every visitor after that just reads the cached output. The expensive, breakable work happens a handful of times a month. The cheap read happens however often clients ask.

Why Cloudflare

This is exactly the kind of problem Cloudflare's primitives are made for. It's a big part of why I reach for them so often. Workers run at the edge with no server to keep alive and no cold start to wait on. Workers KV is a globally replicated key-value store that's tuned for this exact shape, read heavy and write rarely. Subscribers read constantly, but the data only changes a few times a year. Hono is a tiny, web-standard router that gives the Worker clean endpoints without dragging in a whole framework. The whole thing fits comfortably in the free tier and ships as a single deployment.

I stuck to web-standard APIs the whole way through, fetch for the network and crypto.subtle for hashing. That meant no Node compatibility flags and a smaller, faster Worker. Fewer moving parts is the entire point of something you don't want to maintain.

The data source rabbit hole

The hard part of any set-and-forget system is never the happy path. It's what happens when the source of truth quietly changes on you. My first instinct was Wikidata, which exposes structured start and end dates through a query endpoint. It turned out to be a dead end. The tournament edition records only carry the year, with no actual start or end dates, even for tournaments that had already finished. Structured, queryable and missing the one field I actually needed!

The source that worked was the English Wikipedia infobox, read through the MediaWiki API rather than scraped from raw HTML. Those articles are heavily watched, the dates get published well in advance and the date field is structured enough to parse with confidence. There are a few quirks. The US Open article needs a disambiguator and leaves the year off its date line. Next year's articles don't exist until the dates are announced. But each of those is a known case I could handle, not a nasty surprise.

Refusing to serve bad data

Unattended scraping works right up until the source changes its markup. Then it happily serves broken dates with nobody watching. The robustness here doesn't come from the parse being clever. It comes from the harness around it. Every parsed result gets validated hard. Each event has to have a sane date range, a duration in the right ballpark for a Grand Slam and a start month in the expected window for that specific tournament. That month check is the strongest guard against silently parsing the wrong field.

If a value fails, it's never allowed to overwrite good data. The refresh merges per event. For each tournament it prefers the fresh value from Wikipedia, falls back to the last-known-good value held in KV and falls back again to a small seed dataset bundled into the Worker. That seed means the feed is never empty, even on the very first request before any refresh has run. A genuinely broken source degrades gracefully instead of corrupting the calendar. The system heals itself the moment the source recovers. A health endpoint and an optional webhook alert turn a silent break into one I can actually see.

The calendar details that quietly break feeds

iCalendar is unforgiving in small ways. Lines have to be joined with CRLF endings or clients just refuse to import the file. Each event needs a stable identifier so a changed date updates the existing entry instead of creating a duplicate, plus a sequence number that only ticks up when the details actually change. I computed a content hash per event to decide when that bump is warranted.

The detail I cared about most, given my mum and I are both parked in front of Australian TV, was timezones. A Grand Slam is an all-day, multi-day block, so each event uses a date-only value rather than a timestamp. By the spec a date-only value has no timezone at all. It represents the same calendar day everywhere. The Australian Open lands on the 18th of January whether you're in Sydney, London or New York. Reaching for a midnight-UTC timestamp instead is the classic mistake that slides all-day events onto the wrong day in eastern timezones. Using the right value type avoids the problem completely.

The result

I was stoked with the end result! It was short and sweet. A simple and easy to read TypeScript app that did the job no fuss. It's live now at grandslamcalendar.com where you subscribe once and the four Grand Slams turn up on your calendar on the right days. A weekly cron keeps them current, picks up next year's dates the moment Wikipedia publishes them and falls back safely if anything upstream breaks. No app, no account, nothing to maintain. Just four tournaments that quietly show up so neither my mum nor I miss the start of another one.

\"The

The full source is on GitHub if you want to see how the pieces fit together. If you're thinking of building something similar, my one bit of advice would be to lean on your data validation harder than your parser. The parse will break eventually. The harness around it is what keeps the feed trustworthy when it does. Happy coding!

" } ] From f89cb6e03881dbaf097dffc7c82a091300357e90 Mon Sep 17 00:00:00 2001 From: Daniel Workman Date: Thu, 23 Jul 2026 09:32:35 +1000 Subject: [PATCH 02/11] Link the live site from the Train Alerts writeup result section Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01DYxU2GnruStUUdoMh1PiiM --- src/data/projects.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/projects.json b/src/data/projects.json index 42984b9..a40b66d 100644 --- a/src/data/projects.json +++ b/src/data/projects.json @@ -8,7 +8,7 @@ "tech": ["TypeScript", "Cloudflare Workers", "Durable Objects", "Cloudflare D1", "Hono", "Web Push / VAPID", "WebCrypto", "Resend", "TfNSW Open Data", "Vitest", "Playwright"], "period": "July 2026", "order": 1, - "content": "

Overview

\n

My train ritual has never been a proud one. When it's getting close to home time I'll open the transport app, check the next few departures, get pulled back into whatever I was doing, then look up twenty minutes later and check it all over again. Some days the timing works out. Plenty of days it doesn't and I'm either jogging up the platform stairs as the doors close or standing around forever because the train I was aiming for is running eight minutes late. All that checking, over and over, for a piece of information the network already knows. When is my train actually leaving?

\n

That question is what finally got me moving. I didn't want another app to check. I wanted the answer to come to me. A ping two minutes before my train actually leaves, following the live delays, only on the days and times I travel. That's when it clicked. I build on Cloudflare constantly and my tennis calendar is still quietly doing its thing from a Worker right now. Durable Objects have an alarm feature where a tiny stateful object can put itself to sleep and wake at an exact moment, which is precisely the shape of \"ping me two minutes before each train\". So I went and built it! Train Alerts is a small web service where you pick a station, how much notice you want, the days and window you travel in and whether you want the alert by push or email. From then on it watches the live departures and nudges you before each train you could catch. Once you're on, you tap \"I'm on board\" and it stays quiet until your next window.

\n\"The\n\n

How it works

\n

The data comes from Transport for NSW's Open Data platform. Their Trip Planner API has a Departure Monitor endpoint that hands back the upcoming departures from a stop as plain JSON with the realtime estimates already merged in, so there's no GTFS protobuf wrangling at all. A second endpoint, Stop Finder, powers the station autocomplete on the form. The free tier gives you 60,000 calls a day which is heaps for a personal tool like this.

\n\"The\n

The part I really wanted to get right is the timing. A fixed cron can't do \"two minutes before each train\" because the target never sits still. When a train runs late the alert has to slide along with it. So every alert is its own Cloudflare Durable Object with a self-rescheduling alarm. Inside your travel window it wakes, pulls the latest departures for your station, fires a notification for any train now inside its lead time and then sets its next alarm for whichever comes first, the next exact fire time or a re-poll 45 seconds later. Outside your window it does nothing. It sets a single alarm for the start of the next window and goes back to sleep. Each alert also lists the next couple of trains behind the one it's pinging you about, so if you miss the 5:12 you already know the 5:22 is coming.

\n\"Choosing\n

Schedules are Sydney wall-clock time with travel days, a start and end for the window and even fortnightly days for rosters that alternate. The subscriptions themselves live in a D1 database, which stays the source of truth. A cron fires every 15 minutes purely as a safety net, so if a Durable Object ever loses its alarm after a deploy it gets re-hydrated from its D1 row and carries on. The \"I'm on board\" tap is a proper state change too. It mutes the rest of the current window in the same transaction that arms the wake-up for the next one and the alarm loop re-checks that state after every await, so a stray notification can't slip out after you've already boarded.

\n

Delivery is push or email. Email goes through Resend with a manage link in every message so you can pause or cancel from the alert itself. If the push service ever reports a subscription as gone, the alert deactivates itself rather than pushing into the void forever. The frontend is one dependency-free page, no framework, with the autocomplete, a live preview of the alert you're building and a proper install walkthrough for iPhones, because iOS will only deliver web push to a site that's been added to the home screen. All up it's about 2,000 lines with exactly one runtime dependency, Hono for the routing.

\n\n

Sticking points

\n

The API that swore every station was invalid

\n

Early on the station search flat out refused to work. Every query came back with zero locations and a \"stop invalid\" message, even for Central. The culprit was one innocent looking parameter. Stop Finder takes a type_sf field and I'd set it to stop, because I was searching for stops. It turns out type_sf=stop actually means \"the text I'm sending is a stop ID\", so the broker was taking the word Cronulla and trying to look it up as a numeric station identifier. The fix was type_sf=any and then filtering the mixed bag of streets and points of interest down to actual stops. I only caught it because I verified the client against the live API instead of trusting the docs. The mock TfNSW server my Playwright tests drive now mirrors that exact broker behaviour, so this one can never quietly come back.

\n

Daylight saving and the missing hour

\n

Active windows are Sydney local time and my first version of \"when does today's window start\" was simple minute arithmetic from the current moment. An adversarial review pass over the whole codebase caught that the simple version drifts by exactly an hour whenever the calculation straddles the 2am daylight saving changeover, which in the worst case means a whole morning of missed alerts. The fix was to stop doing arithmetic and start constructing the actual Sydney wall time, using Intl with the Australia/Sydney timezone to check the guess and correct it. I reckon DST bugs are the sneakiest kind there is, because the broken code is provably correct for 363 days of the year.

\n

Web push from scratch, byte for byte

\n

The web push libraries everyone reaches for are built for Node and lean on APIs a Worker doesn't have. Rather than fight that, I wrote the push layer from scratch on WebCrypto. That means two RFCs. VAPID (RFC 8292), an ES256 signed JWT that proves to Apple's or Google's push service the message really came from my server. Then the payload encryption (RFC 8291), a chain of ECDH key agreement, HKDF derivations and AES-128-GCM. Hand rolling crypto is nerve-racking because the failure mode is silence, the push service just drops anything malformed and tells you nothing. The saving grace is that the encryption RFC ships a complete worked example with every intermediate value spelled out. So I made the encrypt function accept an injectable random seed and wrote a test that reproduces that worked example byte for byte. When it went green I knew the scariest part of the codebase was actually correct rather than just plausible.

\n\n

The result

\n

I couldn't be happier with how this one came out! The build took a couple of days with Claude Code alongside me and it does exactly the job I set out for. My phone buzzes two minutes before the train actually leaves, the alert already knows it's running late and one tap on \"I'm on board\" keeps everything quiet until tomorrow's window. No app open, no refreshing, no platform waits. It's all covered end to end as well, Vitest across the schedule and crypto logic and Playwright driving the real Worker against a mock TfNSW upstream, so I can keep tweaking it without holding my breath.

\n

If you're thinking about doing web push yourself, here's the tip I wish I'd had on day one. Don't test your encryption by firing real notifications and hoping. Make the random inputs injectable and verify your output against the worked example in RFC 8291 first, because the push services silently drop anything malformed and you'll get no clue why. Once that test passes, everything downstream is just plumbing. Happy coding!

" + "content": "

Overview

\n

My train ritual has never been a proud one. When it's getting close to home time I'll open the transport app, check the next few departures, get pulled back into whatever I was doing, then look up twenty minutes later and check it all over again. Some days the timing works out. Plenty of days it doesn't and I'm either jogging up the platform stairs as the doors close or standing around forever because the train I was aiming for is running eight minutes late. All that checking, over and over, for a piece of information the network already knows. When is my train actually leaving?

\n

That question is what finally got me moving. I didn't want another app to check. I wanted the answer to come to me. A ping two minutes before my train actually leaves, following the live delays, only on the days and times I travel. That's when it clicked. I build on Cloudflare constantly and my tennis calendar is still quietly doing its thing from a Worker right now. Durable Objects have an alarm feature where a tiny stateful object can put itself to sleep and wake at an exact moment, which is precisely the shape of \"ping me two minutes before each train\". So I went and built it! Train Alerts is a small web service where you pick a station, how much notice you want, the days and window you travel in and whether you want the alert by push or email. From then on it watches the live departures and nudges you before each train you could catch. Once you're on, you tap \"I'm on board\" and it stays quiet until your next window.

\n\"The\n\n

How it works

\n

The data comes from Transport for NSW's Open Data platform. Their Trip Planner API has a Departure Monitor endpoint that hands back the upcoming departures from a stop as plain JSON with the realtime estimates already merged in, so there's no GTFS protobuf wrangling at all. A second endpoint, Stop Finder, powers the station autocomplete on the form. The free tier gives you 60,000 calls a day which is heaps for a personal tool like this.

\n\"The\n

The part I really wanted to get right is the timing. A fixed cron can't do \"two minutes before each train\" because the target never sits still. When a train runs late the alert has to slide along with it. So every alert is its own Cloudflare Durable Object with a self-rescheduling alarm. Inside your travel window it wakes, pulls the latest departures for your station, fires a notification for any train now inside its lead time and then sets its next alarm for whichever comes first, the next exact fire time or a re-poll 45 seconds later. Outside your window it does nothing. It sets a single alarm for the start of the next window and goes back to sleep. Each alert also lists the next couple of trains behind the one it's pinging you about, so if you miss the 5:12 you already know the 5:22 is coming.

\n\"Choosing\n

Schedules are Sydney wall-clock time with travel days, a start and end for the window and even fortnightly days for rosters that alternate. The subscriptions themselves live in a D1 database, which stays the source of truth. A cron fires every 15 minutes purely as a safety net, so if a Durable Object ever loses its alarm after a deploy it gets re-hydrated from its D1 row and carries on. The \"I'm on board\" tap is a proper state change too. It mutes the rest of the current window in the same transaction that arms the wake-up for the next one and the alarm loop re-checks that state after every await, so a stray notification can't slip out after you've already boarded.

\n

Delivery is push or email. Email goes through Resend with a manage link in every message so you can pause or cancel from the alert itself. If the push service ever reports a subscription as gone, the alert deactivates itself rather than pushing into the void forever. The frontend is one dependency-free page, no framework, with the autocomplete, a live preview of the alert you're building and a proper install walkthrough for iPhones, because iOS will only deliver web push to a site that's been added to the home screen. All up it's about 2,000 lines with exactly one runtime dependency, Hono for the routing.

\n\n

Sticking points

\n

The API that swore every station was invalid

\n

Early on the station search flat out refused to work. Every query came back with zero locations and a \"stop invalid\" message, even for Central. The culprit was one innocent looking parameter. Stop Finder takes a type_sf field and I'd set it to stop, because I was searching for stops. It turns out type_sf=stop actually means \"the text I'm sending is a stop ID\", so the broker was taking the word Cronulla and trying to look it up as a numeric station identifier. The fix was type_sf=any and then filtering the mixed bag of streets and points of interest down to actual stops. I only caught it because I verified the client against the live API instead of trusting the docs. The mock TfNSW server my Playwright tests drive now mirrors that exact broker behaviour, so this one can never quietly come back.

\n

Daylight saving and the missing hour

\n

Active windows are Sydney local time and my first version of \"when does today's window start\" was simple minute arithmetic from the current moment. An adversarial review pass over the whole codebase caught that the simple version drifts by exactly an hour whenever the calculation straddles the 2am daylight saving changeover, which in the worst case means a whole morning of missed alerts. The fix was to stop doing arithmetic and start constructing the actual Sydney wall time, using Intl with the Australia/Sydney timezone to check the guess and correct it. I reckon DST bugs are the sneakiest kind there is, because the broken code is provably correct for 363 days of the year.

\n

Web push from scratch, byte for byte

\n

The web push libraries everyone reaches for are built for Node and lean on APIs a Worker doesn't have. Rather than fight that, I wrote the push layer from scratch on WebCrypto. That means two RFCs. VAPID (RFC 8292), an ES256 signed JWT that proves to Apple's or Google's push service the message really came from my server. Then the payload encryption (RFC 8291), a chain of ECDH key agreement, HKDF derivations and AES-128-GCM. Hand rolling crypto is nerve-racking because the failure mode is silence, the push service just drops anything malformed and tells you nothing. The saving grace is that the encryption RFC ships a complete worked example with every intermediate value spelled out. So I made the encrypt function accept an injectable random seed and wrote a test that reproduces that worked example byte for byte. When it went green I knew the scariest part of the codebase was actually correct rather than just plausible.

\n\n

The result

\n

I couldn't be happier with how this one came out! The build took a couple of days with Claude Code alongside me and it does exactly the job I set out for. My phone buzzes two minutes before the train actually leaves, the alert already knows it's running late and one tap on \"I'm on board\" keeps everything quiet until tomorrow's window. No app open, no refreshing, no platform waits. It's live at sydneytrainalerts.com too, so if you're a Sydney commuter you're welcome to set up an alert of your own. It's all covered end to end as well, Vitest across the schedule and crypto logic and Playwright driving the real Worker against a mock TfNSW upstream, so I can keep tweaking it without holding my breath.

\n

If you're thinking about doing web push yourself, here's the tip I wish I'd had on day one. Don't test your encryption by firing real notifications and hoping. Make the random inputs injectable and verify your output against the worked example in RFC 8291 first, because the push services silently drop anything malformed and you'll get no clue why. Once that test passes, everything downstream is just plumbing. Happy coding!

" }, { "slug": "album-tracker-cli", From 63356b8dc23e8e2143e9a5e94576b801b56ff271 Mon Sep 17 00:00:00 2001 From: Daniel Workman Date: Sat, 25 Jul 2026 10:11:49 +1000 Subject: [PATCH 03/11] Update Train Alerts writeup: add "The Cool Nerd Part" and reflect the app's evolution - New "The Cool Nerd Part" section: why Durable Objects are the perfect fit (moving-target alarms, id-as-address, one-thing-at-a-time serialized execution) plus the honest story of the mid-flight abort guards that bail out when a cancel or "I'm on board" lands during a poll. - How it works: trimmed the deep DO detail (now in the new section), added the TfNSW API-budget hardening (per-stop cache, rate-limit backoff, wake jitter) and a screenshot of the management-page pause action. - New sticking point: the fail-open-then-fail-closed direction filter fix. - Result: reflects it being live and hardened since launch, plus the blog. - Verified: adversarial voice + technical-accuracy passes (all DO claims checked against shipped main; WIP horizon fix not claimed as shipped). Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01DYxU2GnruStUUdoMh1PiiM --- .../tfnsw-train-alerts/manage-pause.webp | Bin 0 -> 85678 bytes src/data/projects.json | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 public/projects/tfnsw-train-alerts/manage-pause.webp diff --git a/public/projects/tfnsw-train-alerts/manage-pause.webp b/public/projects/tfnsw-train-alerts/manage-pause.webp new file mode 100644 index 0000000000000000000000000000000000000000..9ab605c07a757d173d646066cd0a3967af0fc00f GIT binary patch literal 85678 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

6bR4(H=^@N1ibBl`EgjzEq$~scj_n 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 literal 0 HcmV?d00001 diff --git a/src/data/projects.json b/src/data/projects.json index a40b66d..7063b55 100644 --- a/src/data/projects.json +++ b/src/data/projects.json @@ -5,10 +5,10 @@ "title": "Train Alerts: a nudge before every Sydney train", "company": "", "description": "A departure alert service for Sydney trains built on Cloudflare Workers and Durable Objects. Pick a station, a lead time and your travel window and it pings you before each train actually leaves, following the live delays, with an \"I'm on board\" tap that keeps the rest of the window quiet.", - "tech": ["TypeScript", "Cloudflare Workers", "Durable Objects", "Cloudflare D1", "Hono", "Web Push / VAPID", "WebCrypto", "Resend", "TfNSW Open Data", "Vitest", "Playwright"], + "tech": ["TypeScript", "Cloudflare Workers", "Durable Objects", "Cloudflare D1", "Cloudflare Cache API", "Hono", "Web Push / VAPID", "WebCrypto", "Resend", "TfNSW Open Data", "Vitest", "Playwright"], "period": "July 2026", "order": 1, - "content": "

Overview

\n

My train ritual has never been a proud one. When it's getting close to home time I'll open the transport app, check the next few departures, get pulled back into whatever I was doing, then look up twenty minutes later and check it all over again. Some days the timing works out. Plenty of days it doesn't and I'm either jogging up the platform stairs as the doors close or standing around forever because the train I was aiming for is running eight minutes late. All that checking, over and over, for a piece of information the network already knows. When is my train actually leaving?

\n

That question is what finally got me moving. I didn't want another app to check. I wanted the answer to come to me. A ping two minutes before my train actually leaves, following the live delays, only on the days and times I travel. That's when it clicked. I build on Cloudflare constantly and my tennis calendar is still quietly doing its thing from a Worker right now. Durable Objects have an alarm feature where a tiny stateful object can put itself to sleep and wake at an exact moment, which is precisely the shape of \"ping me two minutes before each train\". So I went and built it! Train Alerts is a small web service where you pick a station, how much notice you want, the days and window you travel in and whether you want the alert by push or email. From then on it watches the live departures and nudges you before each train you could catch. Once you're on, you tap \"I'm on board\" and it stays quiet until your next window.

\n\"The\n\n

How it works

\n

The data comes from Transport for NSW's Open Data platform. Their Trip Planner API has a Departure Monitor endpoint that hands back the upcoming departures from a stop as plain JSON with the realtime estimates already merged in, so there's no GTFS protobuf wrangling at all. A second endpoint, Stop Finder, powers the station autocomplete on the form. The free tier gives you 60,000 calls a day which is heaps for a personal tool like this.

\n\"The\n

The part I really wanted to get right is the timing. A fixed cron can't do \"two minutes before each train\" because the target never sits still. When a train runs late the alert has to slide along with it. So every alert is its own Cloudflare Durable Object with a self-rescheduling alarm. Inside your travel window it wakes, pulls the latest departures for your station, fires a notification for any train now inside its lead time and then sets its next alarm for whichever comes first, the next exact fire time or a re-poll 45 seconds later. Outside your window it does nothing. It sets a single alarm for the start of the next window and goes back to sleep. Each alert also lists the next couple of trains behind the one it's pinging you about, so if you miss the 5:12 you already know the 5:22 is coming.

\n\"Choosing\n

Schedules are Sydney wall-clock time with travel days, a start and end for the window and even fortnightly days for rosters that alternate. The subscriptions themselves live in a D1 database, which stays the source of truth. A cron fires every 15 minutes purely as a safety net, so if a Durable Object ever loses its alarm after a deploy it gets re-hydrated from its D1 row and carries on. The \"I'm on board\" tap is a proper state change too. It mutes the rest of the current window in the same transaction that arms the wake-up for the next one and the alarm loop re-checks that state after every await, so a stray notification can't slip out after you've already boarded.

\n

Delivery is push or email. Email goes through Resend with a manage link in every message so you can pause or cancel from the alert itself. If the push service ever reports a subscription as gone, the alert deactivates itself rather than pushing into the void forever. The frontend is one dependency-free page, no framework, with the autocomplete, a live preview of the alert you're building and a proper install walkthrough for iPhones, because iOS will only deliver web push to a site that's been added to the home screen. All up it's about 2,000 lines with exactly one runtime dependency, Hono for the routing.

\n\n

Sticking points

\n

The API that swore every station was invalid

\n

Early on the station search flat out refused to work. Every query came back with zero locations and a \"stop invalid\" message, even for Central. The culprit was one innocent looking parameter. Stop Finder takes a type_sf field and I'd set it to stop, because I was searching for stops. It turns out type_sf=stop actually means \"the text I'm sending is a stop ID\", so the broker was taking the word Cronulla and trying to look it up as a numeric station identifier. The fix was type_sf=any and then filtering the mixed bag of streets and points of interest down to actual stops. I only caught it because I verified the client against the live API instead of trusting the docs. The mock TfNSW server my Playwright tests drive now mirrors that exact broker behaviour, so this one can never quietly come back.

\n

Daylight saving and the missing hour

\n

Active windows are Sydney local time and my first version of \"when does today's window start\" was simple minute arithmetic from the current moment. An adversarial review pass over the whole codebase caught that the simple version drifts by exactly an hour whenever the calculation straddles the 2am daylight saving changeover, which in the worst case means a whole morning of missed alerts. The fix was to stop doing arithmetic and start constructing the actual Sydney wall time, using Intl with the Australia/Sydney timezone to check the guess and correct it. I reckon DST bugs are the sneakiest kind there is, because the broken code is provably correct for 363 days of the year.

\n

Web push from scratch, byte for byte

\n

The web push libraries everyone reaches for are built for Node and lean on APIs a Worker doesn't have. Rather than fight that, I wrote the push layer from scratch on WebCrypto. That means two RFCs. VAPID (RFC 8292), an ES256 signed JWT that proves to Apple's or Google's push service the message really came from my server. Then the payload encryption (RFC 8291), a chain of ECDH key agreement, HKDF derivations and AES-128-GCM. Hand rolling crypto is nerve-racking because the failure mode is silence, the push service just drops anything malformed and tells you nothing. The saving grace is that the encryption RFC ships a complete worked example with every intermediate value spelled out. So I made the encrypt function accept an injectable random seed and wrote a test that reproduces that worked example byte for byte. When it went green I knew the scariest part of the codebase was actually correct rather than just plausible.

\n\n

The result

\n

I couldn't be happier with how this one came out! The build took a couple of days with Claude Code alongside me and it does exactly the job I set out for. My phone buzzes two minutes before the train actually leaves, the alert already knows it's running late and one tap on \"I'm on board\" keeps everything quiet until tomorrow's window. No app open, no refreshing, no platform waits. It's live at sydneytrainalerts.com too, so if you're a Sydney commuter you're welcome to set up an alert of your own. It's all covered end to end as well, Vitest across the schedule and crypto logic and Playwright driving the real Worker against a mock TfNSW upstream, so I can keep tweaking it without holding my breath.

\n

If you're thinking about doing web push yourself, here's the tip I wish I'd had on day one. Don't test your encryption by firing real notifications and hoping. Make the random inputs injectable and verify your output against the worked example in RFC 8291 first, because the push services silently drop anything malformed and you'll get no clue why. Once that test passes, everything downstream is just plumbing. Happy coding!

" + "content": "

Overview

\n

My train ritual has never been a proud one. When it's getting close to home time I'll open the transport app, check the next few departures, get pulled back into whatever I was doing, then look up twenty minutes later and check it all over again. Some days the timing works out. Plenty of days it doesn't and I'm either jogging up the platform stairs as the doors close or standing around forever because the train I was aiming for is running eight minutes late. All that checking, over and over, for a piece of information the network already knows. When is my train actually leaving?

\n

That question is what finally got me moving. I didn't want another app to check. I wanted the answer to come to me. A ping two minutes before my train actually leaves, following the live delays, only on the days and times I travel. That's when it clicked. I build on Cloudflare constantly and my tennis calendar is still quietly doing its thing from a Worker right now. Durable Objects have an alarm feature where a tiny stateful object can put itself to sleep and wake at an exact moment, which is precisely the shape of \"ping me two minutes before each train\". So I went and built it! Train Alerts is a small web service where you pick a station, how much notice you want, the days and window you travel in and whether you want the alert by push or email. From then on it watches the live departures and nudges you before each train you could catch. Once you're on, you tap \"I'm on board\" and it stays quiet until your next window.

\n\"The\n\n

How it works

\n

The data comes from Transport for NSW's Open Data platform. Their Trip Planner API has a Departure Monitor endpoint that hands back the upcoming departures from a stop as plain JSON with the realtime estimates already merged in, so there's no GTFS protobuf wrangling at all. A second endpoint, Stop Finder, powers the station autocomplete on the form. The free tier gives you 60,000 calls a day which is heaps for a personal tool like this.

\n\"The\n

The part I really wanted to get right is the timing. A fixed cron can't do \"two minutes before each train\" because the target never sits still. When a train runs late the alert has to slide along with it. So every alert is its own little Cloudflare Durable Object that watches your station through your travel window, follows the live delays and nudges you before each train you could catch. Once it's pinged you about one it lists the next couple behind it too, so if you miss the 5:12 you already know the 5:22 is coming. There's a fair bit going on under that timing and I've pulled it out into its own section below.

\n\"Choosing\n

Schedules are Sydney wall-clock time with travel days, a start and end for the window and even fortnightly days for rosters that alternate. The subscriptions themselves live in a D1 database, which stays the source of truth. A cron fires every 15 minutes purely as a safety net so nothing gets stranded after a deploy. Once you're on your train you tap \"I'm on board\" and it goes quiet for the rest of that window, then picks back up on your next travel day. That pause sits on the manage page for every alert as well as on the notification itself.

\n\"The\n

One thing I had to be careful about is staying inside that free tier once more than a handful of people are using it. TfNSW's free plan also caps you at about 5 calls a second. It starts knocking requests back the moment you push past that. So the departure lookups run through Cloudflare's cache, keyed on the station and the minute. If ten people are all watching Town Hall at 5:31 they mostly land on the same cached answer instead of making ten separate calls. If TfNSW ever does rate-limit me the alerts back off and give it room instead of hammering away. I also sprinkle a bit of random jitter on the wake-ups so a deploy doesn't fire every alert at the same second and trip the limit myself. None of it is glamorous but it's what lets one free key quietly handle a proper crowd of commuters.

\n

Delivery is push or email. Email goes through Resend with a manage link in every message so you can pause or cancel from the alert itself. If the push service ever reports a subscription as gone, the alert deactivates itself rather than pushing into the void forever. The frontend is one dependency-free page, no framework, with the autocomplete, a live preview of the alert you're building and a proper install walkthrough for iPhones, because iOS will only deliver web push to a site that's been added to the home screen. All up it's about 2,000 lines with exactly one runtime dependency, Hono for the routing.

\n\n

The Cool Nerd Part

\n

Right, this is the bit I actually get excited about. Before I committed to building it this way I sat down and properly grilled the design, because \"give every single alert its own little server that never really sleeps\" sounds mad when you first say it out loud. The more I poked at it though the more Durable Objects turned out to be the perfect fit, so let me walk through why.

\n

Start with the timing again. Your train is meant to leave at 5:12 so a two minute warning should fire at 5:10. Then it runs four minutes late and the warning needs to fire at 5:14 instead. The target keeps moving all afternoon. A Durable Object has this thing called an alarm, which is basically a single built-in timer the object can point at any exact moment in the future. Better still, it survives restarts and deploys. So every time the alert wakes up and checks the live departures it works out the new fire time from the latest estimate and re-points its own alarm at it. The 5:12 slips to 5:14 and the alarm slides right along with it. It's an alert that reschedules itself off the delay. A plain cron can't do that. The finest a cron will give you is once a minute, so you'd be waking every minute, pulling every subscription and still landing up to a minute wide of a target that won't sit still.

\n

Then there's the fact that each alert is its own object with its own name. The trick with Durable Objects is that you address one by a string. I just use the subscription's id as that string. So wherever I am in the code, whether it's the subscribe request, the cancel button, the \"I'm on board\" tap or the safety-net cron, I hand it the same id and I'm always talking to the exact same object holding that one alert's state. No lookup table, no routing, no working out which server a subscription lives on. The id is the address. And because each object only ever holds its own alert, one person's alert going sideways can't touch anyone else's.

\n

Here's the one that really sold me though. A Durable Object only ever does one thing at a time. It won't run two requests against the same object at once, it queues them up. That sounds like a small detail but it's the whole reason the tricky part of this app is even doable, which brings me to the messy bit.

\n

A single wake-up isn't instant. The object has to go out over the network to TfNSW for the live departures. Then it has to actually send your push or your email. Both of those take real time. Now picture the alert half way through that, sitting there waiting on the departures to come back. Right at that moment you tap \"cancel\" or \"I'm on board\". Because the object does one thing at a time your tap can't barge in mid-step, but it does get its turn the instant the alert pauses to wait on the network. So it flips the state out from under the rest of the run. The alert wakes back up thinking everything's fine and, if I'm not careful, cheerfully sends you a notification for an alert you just cancelled.

\n

The honest answer to that is a lot of little if-checks. The wake-up loop is peppered with early exits that all ask the same question in different spots: has this been cancelled or paused while I wasn't looking? Bail out if it has. There's one on the very first line before it does anything. There's one straight after the departures come back from TfNSW. There's one right before it sends each notification and another right after. There's even one tucked inside the final save, so if a cancel landed while the alert was mid-flight, the moment it goes to write \"all done, wake me in 45 seconds\" it checks one last time and just doesn't, because arming a fresh timer on an alert you've killed is exactly how you end up haunted by a cancelled alert that won't die. Cancelling also leaves a little durable marker behind, a tombstone, so the safety-net cron can't wander past a minute later, spot an alert with no timer set and helpfully resurrect the very thing you just deleted.

\n

It's not the most elegant code I've ever written and there are more of those guards than I'd like. But every single one of them is a real race I could sit and describe to you. And the reason they get to be simple little if-blocks instead of a knot of locks is that one-thing-at-a-time promise. Without Durable Objects I'd be running one big loop over everyone's alerts, all of them fighting over the same database rows with a proper lock bolted on so a cancel and a poll couldn't clobber each other. With a Durable Object the isolation is the lock. Each alert ends up as a little self-winding alarm clock, chasing the train's live delay, costing nothing while it's asleep between your commutes and never fighting anyone for its own state, because it's the only thing that ever touches it.

\n\n

Sticking points

\n

The API that swore every station was invalid

\n

Early on the station search flat out refused to work. Every query came back with zero locations and a \"stop invalid\" message, even for Central. The culprit was one innocent looking parameter. Stop Finder takes a type_sf field and I'd set it to stop, because I was searching for stops. It turns out type_sf=stop actually means \"the text I'm sending is a stop ID\", so the broker was taking the word Cronulla and trying to look it up as a numeric station identifier. The fix was type_sf=any and then filtering the mixed bag of streets and points of interest down to actual stops. I only caught it because I verified the client against the live API instead of trusting the docs. The mock TfNSW server my Playwright tests drive now mirrors that exact broker behaviour, so this one can never quietly come back.

\n

Daylight saving and the missing hour

\n

Active windows are Sydney local time and my first version of \"when does today's window start\" was simple minute arithmetic from the current moment. An adversarial review pass over the whole codebase caught that the simple version drifts by exactly an hour whenever the calculation straddles the 2am daylight saving changeover, which in the worst case means a whole morning of missed alerts. The fix was to stop doing arithmetic and start constructing the actual Sydney wall time, using Intl with the Australia/Sydney timezone to check the guess and correct it. I reckon DST bugs are the sneakiest kind there is, because the broken code is provably correct for 363 days of the year.

\n

The alert that sent people the wrong way

\n

The \"Towards\" box is meant to be a light direction filter. Say you're leaving Town Hall heading home to Cronulla, you pop Cronulla in and the alert only bothers you about trains actually going your way. The catch is that a train only really tells you its final stop, so a service that passes through your destination on the way to somewhere else doesn't always say your stop's name. The filter would match nothing. My first version handled that by failing open, so when nothing matched it just alerted you about everything, on the theory that too many alerts beats none. That turned out to be genuinely annoying. On a quiet poll with no Cronulla train in sight it would happily ping me about trains heading the complete opposite way, up to Berowra and out to Emu Plains. The fix was to give the filter a memory. The first time a real matching train shows up it quietly notes that this direction does work here. From then on a poll with no match means go quiet, not alert about everything. It only falls back to the noisy everything-goes behaviour for a destination that has never once matched, which is the genuine can't-tell case. Failing open was the right instinct and the wrong default.

\n

Web push from scratch, byte for byte

\n

The web push libraries everyone reaches for are built for Node and lean on APIs a Worker doesn't have. Rather than fight that, I wrote the push layer from scratch on WebCrypto. That means two RFCs. VAPID (RFC 8292), an ES256 signed JWT that proves to Apple's or Google's push service the message really came from my server. Then the payload encryption (RFC 8291), a chain of ECDH key agreement, HKDF derivations and AES-128-GCM. Hand rolling crypto is nerve-racking because the failure mode is silence, the push service just drops anything malformed and tells you nothing. The saving grace is that the encryption RFC ships a complete worked example with every intermediate value spelled out. So I made the encrypt function accept an injectable random seed and wrote a test that reproduces that worked example byte for byte. When it went green I knew the scariest part of the codebase was actually correct rather than just plausible.

\n\n

The result

\n

I couldn't be happier with how this one came out! The first build took a couple of days with Claude Code alongside me and it's only got better since it went live. My phone buzzes two minutes before the train actually leaves, the alert already knows it's running late and one tap on \"I'm on board\" keeps everything quiet until my next travel day. I don't open the app, I don't refresh anything and I've stopped standing on platforms waiting. It's live at sydneytrainalerts.com, so if you're a Sydney commuter you're welcome to set one up of your own. I've also popped a little blog on there with a few pieces to help people find it. It's grown up a fair bit since then too. The caching that keeps it inside the free tier and the fix for those wrong-way alerts both landed after real people started using it, which is the best way to find that sort of thing. It's all covered end to end, Vitest across the schedule and crypto logic and Playwright driving the real Worker against a mock TfNSW upstream, so I can keep tweaking it without holding my breath.

\n

If you're thinking about doing web push yourself, here's the tip I wish I'd had on day one. Don't test your encryption by firing real notifications and hoping. Make the random inputs injectable and verify your output against the worked example in RFC 8291 first, because the push services silently drop anything malformed and you'll get no clue why. Once that test passes, everything downstream is just plumbing. Happy coding!

" }, { "slug": "album-tracker-cli", From 37bf4b76935baf3c97d7218c10ca01e51024b7a6 Mon Sep 17 00:00:00 2001 From: Daniel Workman Date: Sat, 25 Jul 2026 13:41:11 +1000 Subject: [PATCH 04/11] Fix stack label: "Cache API" not "Cloudflare Cache API" The code uses caches.default (the Cache API in the Workers runtime), which is not a product called "Cloudflare Cache API". Rename to the accurate name, consistent with how WebCrypto/Web Push are listed. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01DYxU2GnruStUUdoMh1PiiM --- src/data/projects.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/projects.json b/src/data/projects.json index 7063b55..d71c73f 100644 --- a/src/data/projects.json +++ b/src/data/projects.json @@ -5,7 +5,7 @@ "title": "Train Alerts: a nudge before every Sydney train", "company": "", "description": "A departure alert service for Sydney trains built on Cloudflare Workers and Durable Objects. Pick a station, a lead time and your travel window and it pings you before each train actually leaves, following the live delays, with an \"I'm on board\" tap that keeps the rest of the window quiet.", - "tech": ["TypeScript", "Cloudflare Workers", "Durable Objects", "Cloudflare D1", "Cloudflare Cache API", "Hono", "Web Push / VAPID", "WebCrypto", "Resend", "TfNSW Open Data", "Vitest", "Playwright"], + "tech": ["TypeScript", "Cloudflare Workers", "Durable Objects", "Cloudflare D1", "Cache API", "Hono", "Web Push / VAPID", "WebCrypto", "Resend", "TfNSW Open Data", "Vitest", "Playwright"], "period": "July 2026", "order": 1, "content": "

Overview

\n

My train ritual has never been a proud one. When it's getting close to home time I'll open the transport app, check the next few departures, get pulled back into whatever I was doing, then look up twenty minutes later and check it all over again. Some days the timing works out. Plenty of days it doesn't and I'm either jogging up the platform stairs as the doors close or standing around forever because the train I was aiming for is running eight minutes late. All that checking, over and over, for a piece of information the network already knows. When is my train actually leaving?

\n

That question is what finally got me moving. I didn't want another app to check. I wanted the answer to come to me. A ping two minutes before my train actually leaves, following the live delays, only on the days and times I travel. That's when it clicked. I build on Cloudflare constantly and my tennis calendar is still quietly doing its thing from a Worker right now. Durable Objects have an alarm feature where a tiny stateful object can put itself to sleep and wake at an exact moment, which is precisely the shape of \"ping me two minutes before each train\". So I went and built it! Train Alerts is a small web service where you pick a station, how much notice you want, the days and window you travel in and whether you want the alert by push or email. From then on it watches the live departures and nudges you before each train you could catch. Once you're on, you tap \"I'm on board\" and it stays quiet until your next window.

\n\"The\n\n

How it works

\n

The data comes from Transport for NSW's Open Data platform. Their Trip Planner API has a Departure Monitor endpoint that hands back the upcoming departures from a stop as plain JSON with the realtime estimates already merged in, so there's no GTFS protobuf wrangling at all. A second endpoint, Stop Finder, powers the station autocomplete on the form. The free tier gives you 60,000 calls a day which is heaps for a personal tool like this.

\n\"The\n

The part I really wanted to get right is the timing. A fixed cron can't do \"two minutes before each train\" because the target never sits still. When a train runs late the alert has to slide along with it. So every alert is its own little Cloudflare Durable Object that watches your station through your travel window, follows the live delays and nudges you before each train you could catch. Once it's pinged you about one it lists the next couple behind it too, so if you miss the 5:12 you already know the 5:22 is coming. There's a fair bit going on under that timing and I've pulled it out into its own section below.

\n\"Choosing\n

Schedules are Sydney wall-clock time with travel days, a start and end for the window and even fortnightly days for rosters that alternate. The subscriptions themselves live in a D1 database, which stays the source of truth. A cron fires every 15 minutes purely as a safety net so nothing gets stranded after a deploy. Once you're on your train you tap \"I'm on board\" and it goes quiet for the rest of that window, then picks back up on your next travel day. That pause sits on the manage page for every alert as well as on the notification itself.

\n\"The\n

One thing I had to be careful about is staying inside that free tier once more than a handful of people are using it. TfNSW's free plan also caps you at about 5 calls a second. It starts knocking requests back the moment you push past that. So the departure lookups run through Cloudflare's cache, keyed on the station and the minute. If ten people are all watching Town Hall at 5:31 they mostly land on the same cached answer instead of making ten separate calls. If TfNSW ever does rate-limit me the alerts back off and give it room instead of hammering away. I also sprinkle a bit of random jitter on the wake-ups so a deploy doesn't fire every alert at the same second and trip the limit myself. None of it is glamorous but it's what lets one free key quietly handle a proper crowd of commuters.

\n

Delivery is push or email. Email goes through Resend with a manage link in every message so you can pause or cancel from the alert itself. If the push service ever reports a subscription as gone, the alert deactivates itself rather than pushing into the void forever. The frontend is one dependency-free page, no framework, with the autocomplete, a live preview of the alert you're building and a proper install walkthrough for iPhones, because iOS will only deliver web push to a site that's been added to the home screen. All up it's about 2,000 lines with exactly one runtime dependency, Hono for the routing.

\n\n

The Cool Nerd Part

\n

Right, this is the bit I actually get excited about. Before I committed to building it this way I sat down and properly grilled the design, because \"give every single alert its own little server that never really sleeps\" sounds mad when you first say it out loud. The more I poked at it though the more Durable Objects turned out to be the perfect fit, so let me walk through why.

\n

Start with the timing again. Your train is meant to leave at 5:12 so a two minute warning should fire at 5:10. Then it runs four minutes late and the warning needs to fire at 5:14 instead. The target keeps moving all afternoon. A Durable Object has this thing called an alarm, which is basically a single built-in timer the object can point at any exact moment in the future. Better still, it survives restarts and deploys. So every time the alert wakes up and checks the live departures it works out the new fire time from the latest estimate and re-points its own alarm at it. The 5:12 slips to 5:14 and the alarm slides right along with it. It's an alert that reschedules itself off the delay. A plain cron can't do that. The finest a cron will give you is once a minute, so you'd be waking every minute, pulling every subscription and still landing up to a minute wide of a target that won't sit still.

\n

Then there's the fact that each alert is its own object with its own name. The trick with Durable Objects is that you address one by a string. I just use the subscription's id as that string. So wherever I am in the code, whether it's the subscribe request, the cancel button, the \"I'm on board\" tap or the safety-net cron, I hand it the same id and I'm always talking to the exact same object holding that one alert's state. No lookup table, no routing, no working out which server a subscription lives on. The id is the address. And because each object only ever holds its own alert, one person's alert going sideways can't touch anyone else's.

\n

Here's the one that really sold me though. A Durable Object only ever does one thing at a time. It won't run two requests against the same object at once, it queues them up. That sounds like a small detail but it's the whole reason the tricky part of this app is even doable, which brings me to the messy bit.

\n

A single wake-up isn't instant. The object has to go out over the network to TfNSW for the live departures. Then it has to actually send your push or your email. Both of those take real time. Now picture the alert half way through that, sitting there waiting on the departures to come back. Right at that moment you tap \"cancel\" or \"I'm on board\". Because the object does one thing at a time your tap can't barge in mid-step, but it does get its turn the instant the alert pauses to wait on the network. So it flips the state out from under the rest of the run. The alert wakes back up thinking everything's fine and, if I'm not careful, cheerfully sends you a notification for an alert you just cancelled.

\n

The honest answer to that is a lot of little if-checks. The wake-up loop is peppered with early exits that all ask the same question in different spots: has this been cancelled or paused while I wasn't looking? Bail out if it has. There's one on the very first line before it does anything. There's one straight after the departures come back from TfNSW. There's one right before it sends each notification and another right after. There's even one tucked inside the final save, so if a cancel landed while the alert was mid-flight, the moment it goes to write \"all done, wake me in 45 seconds\" it checks one last time and just doesn't, because arming a fresh timer on an alert you've killed is exactly how you end up haunted by a cancelled alert that won't die. Cancelling also leaves a little durable marker behind, a tombstone, so the safety-net cron can't wander past a minute later, spot an alert with no timer set and helpfully resurrect the very thing you just deleted.

\n

It's not the most elegant code I've ever written and there are more of those guards than I'd like. But every single one of them is a real race I could sit and describe to you. And the reason they get to be simple little if-blocks instead of a knot of locks is that one-thing-at-a-time promise. Without Durable Objects I'd be running one big loop over everyone's alerts, all of them fighting over the same database rows with a proper lock bolted on so a cancel and a poll couldn't clobber each other. With a Durable Object the isolation is the lock. Each alert ends up as a little self-winding alarm clock, chasing the train's live delay, costing nothing while it's asleep between your commutes and never fighting anyone for its own state, because it's the only thing that ever touches it.

\n\n

Sticking points

\n

The API that swore every station was invalid

\n

Early on the station search flat out refused to work. Every query came back with zero locations and a \"stop invalid\" message, even for Central. The culprit was one innocent looking parameter. Stop Finder takes a type_sf field and I'd set it to stop, because I was searching for stops. It turns out type_sf=stop actually means \"the text I'm sending is a stop ID\", so the broker was taking the word Cronulla and trying to look it up as a numeric station identifier. The fix was type_sf=any and then filtering the mixed bag of streets and points of interest down to actual stops. I only caught it because I verified the client against the live API instead of trusting the docs. The mock TfNSW server my Playwright tests drive now mirrors that exact broker behaviour, so this one can never quietly come back.

\n

Daylight saving and the missing hour

\n

Active windows are Sydney local time and my first version of \"when does today's window start\" was simple minute arithmetic from the current moment. An adversarial review pass over the whole codebase caught that the simple version drifts by exactly an hour whenever the calculation straddles the 2am daylight saving changeover, which in the worst case means a whole morning of missed alerts. The fix was to stop doing arithmetic and start constructing the actual Sydney wall time, using Intl with the Australia/Sydney timezone to check the guess and correct it. I reckon DST bugs are the sneakiest kind there is, because the broken code is provably correct for 363 days of the year.

\n

The alert that sent people the wrong way

\n

The \"Towards\" box is meant to be a light direction filter. Say you're leaving Town Hall heading home to Cronulla, you pop Cronulla in and the alert only bothers you about trains actually going your way. The catch is that a train only really tells you its final stop, so a service that passes through your destination on the way to somewhere else doesn't always say your stop's name. The filter would match nothing. My first version handled that by failing open, so when nothing matched it just alerted you about everything, on the theory that too many alerts beats none. That turned out to be genuinely annoying. On a quiet poll with no Cronulla train in sight it would happily ping me about trains heading the complete opposite way, up to Berowra and out to Emu Plains. The fix was to give the filter a memory. The first time a real matching train shows up it quietly notes that this direction does work here. From then on a poll with no match means go quiet, not alert about everything. It only falls back to the noisy everything-goes behaviour for a destination that has never once matched, which is the genuine can't-tell case. Failing open was the right instinct and the wrong default.

\n

Web push from scratch, byte for byte

\n

The web push libraries everyone reaches for are built for Node and lean on APIs a Worker doesn't have. Rather than fight that, I wrote the push layer from scratch on WebCrypto. That means two RFCs. VAPID (RFC 8292), an ES256 signed JWT that proves to Apple's or Google's push service the message really came from my server. Then the payload encryption (RFC 8291), a chain of ECDH key agreement, HKDF derivations and AES-128-GCM. Hand rolling crypto is nerve-racking because the failure mode is silence, the push service just drops anything malformed and tells you nothing. The saving grace is that the encryption RFC ships a complete worked example with every intermediate value spelled out. So I made the encrypt function accept an injectable random seed and wrote a test that reproduces that worked example byte for byte. When it went green I knew the scariest part of the codebase was actually correct rather than just plausible.

\n\n

The result

\n

I couldn't be happier with how this one came out! The first build took a couple of days with Claude Code alongside me and it's only got better since it went live. My phone buzzes two minutes before the train actually leaves, the alert already knows it's running late and one tap on \"I'm on board\" keeps everything quiet until my next travel day. I don't open the app, I don't refresh anything and I've stopped standing on platforms waiting. It's live at sydneytrainalerts.com, so if you're a Sydney commuter you're welcome to set one up of your own. I've also popped a little blog on there with a few pieces to help people find it. It's grown up a fair bit since then too. The caching that keeps it inside the free tier and the fix for those wrong-way alerts both landed after real people started using it, which is the best way to find that sort of thing. It's all covered end to end, Vitest across the schedule and crypto logic and Playwright driving the real Worker against a mock TfNSW upstream, so I can keep tweaking it without holding my breath.

\n

If you're thinking about doing web push yourself, here's the tip I wish I'd had on day one. Don't test your encryption by firing real notifications and hoping. Make the random inputs injectable and verify your output against the worked example in RFC 8291 first, because the push services silently drop anything malformed and you'll get no clue why. Once that test passes, everything downstream is just plumbing. Happy coding!

" From 8eba3f5967fe333bec8f1a7bf6b764d4207f8cd9 Mon Sep 17 00:00:00 2001 From: Daniel Workman Date: Sat, 25 Jul 2026 14:04:16 +1000 Subject: [PATCH 05/11] Tighten the Train Alerts writeup: voice fixes and a cron contradiction Fixes found reviewing the writeup against the daniel-voice profile and the branch code review: - Rework the opening lightbulb paragraph. "That's when it clicked" was a near-lift of the tennis calendar piece's "That's when it hit me", and the surrounding "I didn't want X. I wanted Y." run was staccato. - Stop explaining the moving-target timing problem twice. "How it works" now teases and hands off; "The Cool Nerd Part" owns the explanation. - Resolve the cron cadence contradiction. "How it works" says the safety-net cron fires every 15 minutes, the tombstone paragraph said it came past "a minute later". Drop the second cadence. - Replace the "right instinct and the wrong default" inversion with plain phrasing. - Reorder "The result" so "since then" has a referent, merge the doubled "got better since it went live" and end the paragraph on the CTA. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_016MErHQ9Enru8SwzFagcGAs --- src/data/projects.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/projects.json b/src/data/projects.json index d71c73f..df9f347 100644 --- a/src/data/projects.json +++ b/src/data/projects.json @@ -8,7 +8,7 @@ "tech": ["TypeScript", "Cloudflare Workers", "Durable Objects", "Cloudflare D1", "Cache API", "Hono", "Web Push / VAPID", "WebCrypto", "Resend", "TfNSW Open Data", "Vitest", "Playwright"], "period": "July 2026", "order": 1, - "content": "

Overview

\n

My train ritual has never been a proud one. When it's getting close to home time I'll open the transport app, check the next few departures, get pulled back into whatever I was doing, then look up twenty minutes later and check it all over again. Some days the timing works out. Plenty of days it doesn't and I'm either jogging up the platform stairs as the doors close or standing around forever because the train I was aiming for is running eight minutes late. All that checking, over and over, for a piece of information the network already knows. When is my train actually leaving?

\n

That question is what finally got me moving. I didn't want another app to check. I wanted the answer to come to me. A ping two minutes before my train actually leaves, following the live delays, only on the days and times I travel. That's when it clicked. I build on Cloudflare constantly and my tennis calendar is still quietly doing its thing from a Worker right now. Durable Objects have an alarm feature where a tiny stateful object can put itself to sleep and wake at an exact moment, which is precisely the shape of \"ping me two minutes before each train\". So I went and built it! Train Alerts is a small web service where you pick a station, how much notice you want, the days and window you travel in and whether you want the alert by push or email. From then on it watches the live departures and nudges you before each train you could catch. Once you're on, you tap \"I'm on board\" and it stays quiet until your next window.

\n\"The\n\n

How it works

\n

The data comes from Transport for NSW's Open Data platform. Their Trip Planner API has a Departure Monitor endpoint that hands back the upcoming departures from a stop as plain JSON with the realtime estimates already merged in, so there's no GTFS protobuf wrangling at all. A second endpoint, Stop Finder, powers the station autocomplete on the form. The free tier gives you 60,000 calls a day which is heaps for a personal tool like this.

\n\"The\n

The part I really wanted to get right is the timing. A fixed cron can't do \"two minutes before each train\" because the target never sits still. When a train runs late the alert has to slide along with it. So every alert is its own little Cloudflare Durable Object that watches your station through your travel window, follows the live delays and nudges you before each train you could catch. Once it's pinged you about one it lists the next couple behind it too, so if you miss the 5:12 you already know the 5:22 is coming. There's a fair bit going on under that timing and I've pulled it out into its own section below.

\n\"Choosing\n

Schedules are Sydney wall-clock time with travel days, a start and end for the window and even fortnightly days for rosters that alternate. The subscriptions themselves live in a D1 database, which stays the source of truth. A cron fires every 15 minutes purely as a safety net so nothing gets stranded after a deploy. Once you're on your train you tap \"I'm on board\" and it goes quiet for the rest of that window, then picks back up on your next travel day. That pause sits on the manage page for every alert as well as on the notification itself.

\n\"The\n

One thing I had to be careful about is staying inside that free tier once more than a handful of people are using it. TfNSW's free plan also caps you at about 5 calls a second. It starts knocking requests back the moment you push past that. So the departure lookups run through Cloudflare's cache, keyed on the station and the minute. If ten people are all watching Town Hall at 5:31 they mostly land on the same cached answer instead of making ten separate calls. If TfNSW ever does rate-limit me the alerts back off and give it room instead of hammering away. I also sprinkle a bit of random jitter on the wake-ups so a deploy doesn't fire every alert at the same second and trip the limit myself. None of it is glamorous but it's what lets one free key quietly handle a proper crowd of commuters.

\n

Delivery is push or email. Email goes through Resend with a manage link in every message so you can pause or cancel from the alert itself. If the push service ever reports a subscription as gone, the alert deactivates itself rather than pushing into the void forever. The frontend is one dependency-free page, no framework, with the autocomplete, a live preview of the alert you're building and a proper install walkthrough for iPhones, because iOS will only deliver web push to a site that's been added to the home screen. All up it's about 2,000 lines with exactly one runtime dependency, Hono for the routing.

\n\n

The Cool Nerd Part

\n

Right, this is the bit I actually get excited about. Before I committed to building it this way I sat down and properly grilled the design, because \"give every single alert its own little server that never really sleeps\" sounds mad when you first say it out loud. The more I poked at it though the more Durable Objects turned out to be the perfect fit, so let me walk through why.

\n

Start with the timing again. Your train is meant to leave at 5:12 so a two minute warning should fire at 5:10. Then it runs four minutes late and the warning needs to fire at 5:14 instead. The target keeps moving all afternoon. A Durable Object has this thing called an alarm, which is basically a single built-in timer the object can point at any exact moment in the future. Better still, it survives restarts and deploys. So every time the alert wakes up and checks the live departures it works out the new fire time from the latest estimate and re-points its own alarm at it. The 5:12 slips to 5:14 and the alarm slides right along with it. It's an alert that reschedules itself off the delay. A plain cron can't do that. The finest a cron will give you is once a minute, so you'd be waking every minute, pulling every subscription and still landing up to a minute wide of a target that won't sit still.

\n

Then there's the fact that each alert is its own object with its own name. The trick with Durable Objects is that you address one by a string. I just use the subscription's id as that string. So wherever I am in the code, whether it's the subscribe request, the cancel button, the \"I'm on board\" tap or the safety-net cron, I hand it the same id and I'm always talking to the exact same object holding that one alert's state. No lookup table, no routing, no working out which server a subscription lives on. The id is the address. And because each object only ever holds its own alert, one person's alert going sideways can't touch anyone else's.

\n

Here's the one that really sold me though. A Durable Object only ever does one thing at a time. It won't run two requests against the same object at once, it queues them up. That sounds like a small detail but it's the whole reason the tricky part of this app is even doable, which brings me to the messy bit.

\n

A single wake-up isn't instant. The object has to go out over the network to TfNSW for the live departures. Then it has to actually send your push or your email. Both of those take real time. Now picture the alert half way through that, sitting there waiting on the departures to come back. Right at that moment you tap \"cancel\" or \"I'm on board\". Because the object does one thing at a time your tap can't barge in mid-step, but it does get its turn the instant the alert pauses to wait on the network. So it flips the state out from under the rest of the run. The alert wakes back up thinking everything's fine and, if I'm not careful, cheerfully sends you a notification for an alert you just cancelled.

\n

The honest answer to that is a lot of little if-checks. The wake-up loop is peppered with early exits that all ask the same question in different spots: has this been cancelled or paused while I wasn't looking? Bail out if it has. There's one on the very first line before it does anything. There's one straight after the departures come back from TfNSW. There's one right before it sends each notification and another right after. There's even one tucked inside the final save, so if a cancel landed while the alert was mid-flight, the moment it goes to write \"all done, wake me in 45 seconds\" it checks one last time and just doesn't, because arming a fresh timer on an alert you've killed is exactly how you end up haunted by a cancelled alert that won't die. Cancelling also leaves a little durable marker behind, a tombstone, so the safety-net cron can't wander past a minute later, spot an alert with no timer set and helpfully resurrect the very thing you just deleted.

\n

It's not the most elegant code I've ever written and there are more of those guards than I'd like. But every single one of them is a real race I could sit and describe to you. And the reason they get to be simple little if-blocks instead of a knot of locks is that one-thing-at-a-time promise. Without Durable Objects I'd be running one big loop over everyone's alerts, all of them fighting over the same database rows with a proper lock bolted on so a cancel and a poll couldn't clobber each other. With a Durable Object the isolation is the lock. Each alert ends up as a little self-winding alarm clock, chasing the train's live delay, costing nothing while it's asleep between your commutes and never fighting anyone for its own state, because it's the only thing that ever touches it.

\n\n

Sticking points

\n

The API that swore every station was invalid

\n

Early on the station search flat out refused to work. Every query came back with zero locations and a \"stop invalid\" message, even for Central. The culprit was one innocent looking parameter. Stop Finder takes a type_sf field and I'd set it to stop, because I was searching for stops. It turns out type_sf=stop actually means \"the text I'm sending is a stop ID\", so the broker was taking the word Cronulla and trying to look it up as a numeric station identifier. The fix was type_sf=any and then filtering the mixed bag of streets and points of interest down to actual stops. I only caught it because I verified the client against the live API instead of trusting the docs. The mock TfNSW server my Playwright tests drive now mirrors that exact broker behaviour, so this one can never quietly come back.

\n

Daylight saving and the missing hour

\n

Active windows are Sydney local time and my first version of \"when does today's window start\" was simple minute arithmetic from the current moment. An adversarial review pass over the whole codebase caught that the simple version drifts by exactly an hour whenever the calculation straddles the 2am daylight saving changeover, which in the worst case means a whole morning of missed alerts. The fix was to stop doing arithmetic and start constructing the actual Sydney wall time, using Intl with the Australia/Sydney timezone to check the guess and correct it. I reckon DST bugs are the sneakiest kind there is, because the broken code is provably correct for 363 days of the year.

\n

The alert that sent people the wrong way

\n

The \"Towards\" box is meant to be a light direction filter. Say you're leaving Town Hall heading home to Cronulla, you pop Cronulla in and the alert only bothers you about trains actually going your way. The catch is that a train only really tells you its final stop, so a service that passes through your destination on the way to somewhere else doesn't always say your stop's name. The filter would match nothing. My first version handled that by failing open, so when nothing matched it just alerted you about everything, on the theory that too many alerts beats none. That turned out to be genuinely annoying. On a quiet poll with no Cronulla train in sight it would happily ping me about trains heading the complete opposite way, up to Berowra and out to Emu Plains. The fix was to give the filter a memory. The first time a real matching train shows up it quietly notes that this direction does work here. From then on a poll with no match means go quiet, not alert about everything. It only falls back to the noisy everything-goes behaviour for a destination that has never once matched, which is the genuine can't-tell case. Failing open was the right instinct and the wrong default.

\n

Web push from scratch, byte for byte

\n

The web push libraries everyone reaches for are built for Node and lean on APIs a Worker doesn't have. Rather than fight that, I wrote the push layer from scratch on WebCrypto. That means two RFCs. VAPID (RFC 8292), an ES256 signed JWT that proves to Apple's or Google's push service the message really came from my server. Then the payload encryption (RFC 8291), a chain of ECDH key agreement, HKDF derivations and AES-128-GCM. Hand rolling crypto is nerve-racking because the failure mode is silence, the push service just drops anything malformed and tells you nothing. The saving grace is that the encryption RFC ships a complete worked example with every intermediate value spelled out. So I made the encrypt function accept an injectable random seed and wrote a test that reproduces that worked example byte for byte. When it went green I knew the scariest part of the codebase was actually correct rather than just plausible.

\n\n

The result

\n

I couldn't be happier with how this one came out! The first build took a couple of days with Claude Code alongside me and it's only got better since it went live. My phone buzzes two minutes before the train actually leaves, the alert already knows it's running late and one tap on \"I'm on board\" keeps everything quiet until my next travel day. I don't open the app, I don't refresh anything and I've stopped standing on platforms waiting. It's live at sydneytrainalerts.com, so if you're a Sydney commuter you're welcome to set one up of your own. I've also popped a little blog on there with a few pieces to help people find it. It's grown up a fair bit since then too. The caching that keeps it inside the free tier and the fix for those wrong-way alerts both landed after real people started using it, which is the best way to find that sort of thing. It's all covered end to end, Vitest across the schedule and crypto logic and Playwright driving the real Worker against a mock TfNSW upstream, so I can keep tweaking it without holding my breath.

\n

If you're thinking about doing web push yourself, here's the tip I wish I'd had on day one. Don't test your encryption by firing real notifications and hoping. Make the random inputs injectable and verify your output against the worked example in RFC 8291 first, because the push services silently drop anything malformed and you'll get no clue why. Once that test passes, everything downstream is just plumbing. Happy coding!

" + "content": "

Overview

\n

My train ritual has never been a proud one. When it's getting close to home time I'll open the transport app, check the next few departures, get pulled back into whatever I was doing, then look up twenty minutes later and check it all over again. Some days the timing works out. Plenty of days it doesn't and I'm either jogging up the platform stairs as the doors close or standing around forever because the train I was aiming for is running eight minutes late. All that checking, over and over, for a piece of information the network already knows. When is my train actually leaving?

\n

That question is what finally got me moving. What I really wanted was for the answer to come to me instead of me going out and checking for it, a ping two minutes before my train actually leaves that follows the live delays and only bothers me on the days and times I travel. I build on Cloudflare constantly and my tennis calendar is still quietly doing its thing from a Worker right now, so I had a fair idea of what was sitting there already. Then I remembered the alarm on Durable Objects, where a tiny stateful object can put itself to sleep and wake at an exact moment. That's precisely the shape of \"ping me two minutes before each train\". So I went and built it! Train Alerts is a small web service where you pick a station, how much notice you want, the days and window you travel in and whether you want the alert by push or email. From then on it watches the live departures and nudges you before each train you could catch. Once you're on, you tap \"I'm on board\" and it stays quiet until your next window.

\n\"The\n\n

How it works

\n

The data comes from Transport for NSW's Open Data platform. Their Trip Planner API has a Departure Monitor endpoint that hands back the upcoming departures from a stop as plain JSON with the realtime estimates already merged in, so there's no GTFS protobuf wrangling at all. A second endpoint, Stop Finder, powers the station autocomplete on the form. The free tier gives you 60,000 calls a day which is heaps for a personal tool like this.

\n\"The\n

The part I really wanted to get right is the timing. Every alert is its own little Cloudflare Durable Object that watches your station through your travel window, follows the live delays and nudges you before each train you could catch. Once it's pinged you about one it lists the next couple behind it too, so if you miss the 5:12 you already know the 5:22 is coming. Getting that to actually work is the most interesting thing in the whole build, so I've given it its own section below.

\n\"Choosing\n

Schedules are Sydney wall-clock time with travel days, a start and end for the window and even fortnightly days for rosters that alternate. The subscriptions themselves live in a D1 database, which stays the source of truth. A cron fires every 15 minutes purely as a safety net so nothing gets stranded after a deploy. Once you're on your train you tap \"I'm on board\" and it goes quiet for the rest of that window, then picks back up on your next travel day. That pause sits on the manage page for every alert as well as on the notification itself.

\n\"The\n

One thing I had to be careful about is staying inside that free tier once more than a handful of people are using it. TfNSW's free plan also caps you at about 5 calls a second. It starts knocking requests back the moment you push past that. So the departure lookups run through Cloudflare's cache, keyed on the station and the minute. If ten people are all watching Town Hall at 5:31 they mostly land on the same cached answer instead of making ten separate calls. If TfNSW ever does rate-limit me the alerts back off and give it room instead of hammering away. I also sprinkle a bit of random jitter on the wake-ups so a deploy doesn't fire every alert at the same second and trip the limit myself. None of it is glamorous but it's what lets one free key quietly handle a proper crowd of commuters.

\n

Delivery is push or email. Email goes through Resend with a manage link in every message so you can pause or cancel from the alert itself. If the push service ever reports a subscription as gone, the alert deactivates itself rather than pushing into the void forever. The frontend is one dependency-free page, no framework, with the autocomplete, a live preview of the alert you're building and a proper install walkthrough for iPhones, because iOS will only deliver web push to a site that's been added to the home screen. All up it's about 2,000 lines with exactly one runtime dependency, Hono for the routing.

\n\n

The Cool Nerd Part

\n

Right, this is the bit I actually get excited about. Before I committed to building it this way I sat down and properly grilled the design, because \"give every single alert its own little server that never really sleeps\" sounds mad when you first say it out loud. The more I poked at it though the more Durable Objects turned out to be the perfect fit, so let me walk through why.

\n

The timing first. Your train is meant to leave at 5:12 so a two minute warning should fire at 5:10. Then it runs four minutes late and the warning needs to fire at 5:14 instead. The target keeps moving all afternoon. A Durable Object has this thing called an alarm, which is basically a single built-in timer the object can point at any exact moment in the future. Better still, it survives restarts and deploys. So every time the alert wakes up and checks the live departures it works out the new fire time from the latest estimate and re-points its own alarm at it. The 5:12 slips to 5:14 and the alarm slides right along with it. It's an alert that reschedules itself off the delay. A plain cron can't do that. The finest a cron will give you is once a minute, so you'd be waking every minute, pulling every subscription and still landing up to a minute wide of a target that won't sit still.

\n

Then there's the fact that each alert is its own object with its own name. The trick with Durable Objects is that you address one by a string. I just use the subscription's id as that string. So wherever I am in the code, whether it's the subscribe request, the cancel button, the \"I'm on board\" tap or the safety-net cron, I hand it the same id and I'm always talking to the exact same object holding that one alert's state. No lookup table, no routing, no working out which server a subscription lives on. The id is the address. And because each object only ever holds its own alert, one person's alert going sideways can't touch anyone else's.

\n

Here's the one that really sold me though. A Durable Object only ever does one thing at a time. It won't run two requests against the same object at once, it queues them up. That sounds like a small detail but it's the whole reason the tricky part of this app is even doable, which brings me to the messy bit.

\n

A single wake-up isn't instant. The object has to go out over the network to TfNSW for the live departures. Then it has to actually send your push or your email. Both of those take real time. Now picture the alert half way through that, sitting there waiting on the departures to come back. Right at that moment you tap \"cancel\" or \"I'm on board\". Because the object does one thing at a time your tap can't barge in mid-step, but it does get its turn the instant the alert pauses to wait on the network. So it flips the state out from under the rest of the run. The alert wakes back up thinking everything's fine and, if I'm not careful, cheerfully sends you a notification for an alert you just cancelled.

\n

The honest answer to that is a lot of little if-checks. The wake-up loop is peppered with early exits that all ask the same question in different spots: has this been cancelled or paused while I wasn't looking? Bail out if it has. There's one on the very first line before it does anything. There's one straight after the departures come back from TfNSW. There's one right before it sends each notification and another right after. There's even one tucked inside the final save, so if a cancel landed while the alert was mid-flight, the moment it goes to write \"all done, wake me in 45 seconds\" it checks one last time and just doesn't, because arming a fresh timer on an alert you've killed is exactly how you end up haunted by a cancelled alert that won't die. Cancelling also leaves a little durable marker behind, a tombstone, so the safety-net cron can't come past later, spot an alert with no timer set and helpfully resurrect the very thing you just deleted.

\n

It's not the most elegant code I've ever written and there are more of those guards than I'd like. But every single one of them is a real race I could sit and describe to you. And the reason they get to be simple little if-blocks instead of a knot of locks is that one-thing-at-a-time promise. Without Durable Objects I'd be running one big loop over everyone's alerts, all of them fighting over the same database rows with a proper lock bolted on so a cancel and a poll couldn't clobber each other. With a Durable Object the isolation is the lock. Each alert ends up as a little self-winding alarm clock, chasing the train's live delay, costing nothing while it's asleep between your commutes and never fighting anyone for its own state, because it's the only thing that ever touches it.

\n\n

Sticking points

\n

The API that swore every station was invalid

\n

Early on the station search flat out refused to work. Every query came back with zero locations and a \"stop invalid\" message, even for Central. The culprit was one innocent looking parameter. Stop Finder takes a type_sf field and I'd set it to stop, because I was searching for stops. It turns out type_sf=stop actually means \"the text I'm sending is a stop ID\", so the broker was taking the word Cronulla and trying to look it up as a numeric station identifier. The fix was type_sf=any and then filtering the mixed bag of streets and points of interest down to actual stops. I only caught it because I verified the client against the live API instead of trusting the docs. The mock TfNSW server my Playwright tests drive now mirrors that exact broker behaviour, so this one can never quietly come back.

\n

Daylight saving and the missing hour

\n

Active windows are Sydney local time and my first version of \"when does today's window start\" was simple minute arithmetic from the current moment. An adversarial review pass over the whole codebase caught that the simple version drifts by exactly an hour whenever the calculation straddles the 2am daylight saving changeover, which in the worst case means a whole morning of missed alerts. The fix was to stop doing arithmetic and start constructing the actual Sydney wall time, using Intl with the Australia/Sydney timezone to check the guess and correct it. I reckon DST bugs are the sneakiest kind there is, because the broken code is provably correct for 363 days of the year.

\n

The alert that sent people the wrong way

\n

The \"Towards\" box is meant to be a light direction filter. Say you're leaving Town Hall heading home to Cronulla, you pop Cronulla in and the alert only bothers you about trains actually going your way. The catch is that a train only really tells you its final stop, so a service that passes through your destination on the way to somewhere else doesn't always say your stop's name. The filter would match nothing. My first version handled that by failing open, so when nothing matched it just alerted you about everything, on the theory that too many alerts beats none. That turned out to be genuinely annoying. On a quiet poll with no Cronulla train in sight it would happily ping me about trains heading the complete opposite way, up to Berowra and out to Emu Plains. The fix was to give the filter a memory. The first time a real matching train shows up it quietly notes that this direction does work here. From then on a poll with no match means go quiet, not alert about everything. It only falls back to the noisy everything-goes behaviour for a destination that has never once matched, which is the genuine can't-tell case. I still reckon failing open was the right instinct. It just shouldn't have been what happens every time.

\n

Web push from scratch, byte for byte

\n

The web push libraries everyone reaches for are built for Node and lean on APIs a Worker doesn't have. Rather than fight that, I wrote the push layer from scratch on WebCrypto. That means two RFCs. VAPID (RFC 8292), an ES256 signed JWT that proves to Apple's or Google's push service the message really came from my server. Then the payload encryption (RFC 8291), a chain of ECDH key agreement, HKDF derivations and AES-128-GCM. Hand rolling crypto is nerve-racking because the failure mode is silence, the push service just drops anything malformed and tells you nothing. The saving grace is that the encryption RFC ships a complete worked example with every intermediate value spelled out. So I made the encrypt function accept an injectable random seed and wrote a test that reproduces that worked example byte for byte. When it went green I knew the scariest part of the codebase was actually correct rather than just plausible.

\n\n

The result

\n

I couldn't be happier with how this one came out! The first build took a couple of days with Claude Code alongside me and it's grown up a fair bit since it went live. The caching that keeps it inside the free tier and the fix for those wrong-way alerts both landed after real people started using it, which is the best way to find that sort of thing. My phone buzzes two minutes before the train actually leaves, the alert already knows it's running late and one tap on \"I'm on board\" keeps everything quiet until my next travel day. I don't open the app, I don't refresh anything and I've stopped standing on platforms waiting. It's all covered end to end, Vitest across the schedule and crypto logic and Playwright driving the real Worker against a mock TfNSW upstream, so I can keep tweaking it without holding my breath. It's live at sydneytrainalerts.com, so if you're a Sydney commuter you're welcome to set one up of your own. I've also popped a little blog on there with a few pieces to help people find it.

\n

If you're thinking about doing web push yourself, here's the tip I wish I'd had on day one. Don't test your encryption by firing real notifications and hoping. Make the random inputs injectable and verify your output against the worked example in RFC 8291 first, because the push services silently drop anything malformed and you'll get no clue why. Once that test passes, everything downstream is just plumbing. Happy coding!

" }, { "slug": "album-tracker-cli", From e99291a339d75d9cb20f18b7de91459a10cbe456 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 28 Jul 2026 08:13:55 +0000 Subject: [PATCH 06/11] Rewrite the Train Alerts overview around the Wynyard origin story Replaces the app-checking-ritual opener with Daniel's own account of the idea landing on the platform at Wynyard, keeping his wording verbatim and only correcting typos and grammar against the daniel-voice skill (Australian English, no em-dashes, no semicolons, no comma before "and"/"or", no filler). The second paragraph drops the now redundant problem restatement and picks up straight from the Durable Objects lightbulb into what the service does. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01AdKpQxJJZEHt3YZKRT4ueu --- src/data/projects.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/projects.json b/src/data/projects.json index df9f347..ad7b3f6 100644 --- a/src/data/projects.json +++ b/src/data/projects.json @@ -8,7 +8,7 @@ "tech": ["TypeScript", "Cloudflare Workers", "Durable Objects", "Cloudflare D1", "Cache API", "Hono", "Web Push / VAPID", "WebCrypto", "Resend", "TfNSW Open Data", "Vitest", "Playwright"], "period": "July 2026", "order": 1, - "content": "

Overview

\n

My train ritual has never been a proud one. When it's getting close to home time I'll open the transport app, check the next few departures, get pulled back into whatever I was doing, then look up twenty minutes later and check it all over again. Some days the timing works out. Plenty of days it doesn't and I'm either jogging up the platform stairs as the doors close or standing around forever because the train I was aiming for is running eight minutes late. All that checking, over and over, for a piece of information the network already knows. When is my train actually leaving?

\n

That question is what finally got me moving. What I really wanted was for the answer to come to me instead of me going out and checking for it, a ping two minutes before my train actually leaves that follows the live delays and only bothers me on the days and times I travel. I build on Cloudflare constantly and my tennis calendar is still quietly doing its thing from a Worker right now, so I had a fair idea of what was sitting there already. Then I remembered the alarm on Durable Objects, where a tiny stateful object can put itself to sleep and wake at an exact moment. That's precisely the shape of \"ping me two minutes before each train\". So I went and built it! Train Alerts is a small web service where you pick a station, how much notice you want, the days and window you travel in and whether you want the alert by push or email. From then on it watches the live departures and nudges you before each train you could catch. Once you're on, you tap \"I'm on board\" and it stays quiet until your next window.

\n\"The\n\n

How it works

\n

The data comes from Transport for NSW's Open Data platform. Their Trip Planner API has a Departure Monitor endpoint that hands back the upcoming departures from a stop as plain JSON with the realtime estimates already merged in, so there's no GTFS protobuf wrangling at all. A second endpoint, Stop Finder, powers the station autocomplete on the form. The free tier gives you 60,000 calls a day which is heaps for a personal tool like this.

\n\"The\n

The part I really wanted to get right is the timing. Every alert is its own little Cloudflare Durable Object that watches your station through your travel window, follows the live delays and nudges you before each train you could catch. Once it's pinged you about one it lists the next couple behind it too, so if you miss the 5:12 you already know the 5:22 is coming. Getting that to actually work is the most interesting thing in the whole build, so I've given it its own section below.

\n\"Choosing\n

Schedules are Sydney wall-clock time with travel days, a start and end for the window and even fortnightly days for rosters that alternate. The subscriptions themselves live in a D1 database, which stays the source of truth. A cron fires every 15 minutes purely as a safety net so nothing gets stranded after a deploy. Once you're on your train you tap \"I'm on board\" and it goes quiet for the rest of that window, then picks back up on your next travel day. That pause sits on the manage page for every alert as well as on the notification itself.

\n\"The\n

One thing I had to be careful about is staying inside that free tier once more than a handful of people are using it. TfNSW's free plan also caps you at about 5 calls a second. It starts knocking requests back the moment you push past that. So the departure lookups run through Cloudflare's cache, keyed on the station and the minute. If ten people are all watching Town Hall at 5:31 they mostly land on the same cached answer instead of making ten separate calls. If TfNSW ever does rate-limit me the alerts back off and give it room instead of hammering away. I also sprinkle a bit of random jitter on the wake-ups so a deploy doesn't fire every alert at the same second and trip the limit myself. None of it is glamorous but it's what lets one free key quietly handle a proper crowd of commuters.

\n

Delivery is push or email. Email goes through Resend with a manage link in every message so you can pause or cancel from the alert itself. If the push service ever reports a subscription as gone, the alert deactivates itself rather than pushing into the void forever. The frontend is one dependency-free page, no framework, with the autocomplete, a live preview of the alert you're building and a proper install walkthrough for iPhones, because iOS will only deliver web push to a site that's been added to the home screen. All up it's about 2,000 lines with exactly one runtime dependency, Hono for the routing.

\n\n

The Cool Nerd Part

\n

Right, this is the bit I actually get excited about. Before I committed to building it this way I sat down and properly grilled the design, because \"give every single alert its own little server that never really sleeps\" sounds mad when you first say it out loud. The more I poked at it though the more Durable Objects turned out to be the perfect fit, so let me walk through why.

\n

The timing first. Your train is meant to leave at 5:12 so a two minute warning should fire at 5:10. Then it runs four minutes late and the warning needs to fire at 5:14 instead. The target keeps moving all afternoon. A Durable Object has this thing called an alarm, which is basically a single built-in timer the object can point at any exact moment in the future. Better still, it survives restarts and deploys. So every time the alert wakes up and checks the live departures it works out the new fire time from the latest estimate and re-points its own alarm at it. The 5:12 slips to 5:14 and the alarm slides right along with it. It's an alert that reschedules itself off the delay. A plain cron can't do that. The finest a cron will give you is once a minute, so you'd be waking every minute, pulling every subscription and still landing up to a minute wide of a target that won't sit still.

\n

Then there's the fact that each alert is its own object with its own name. The trick with Durable Objects is that you address one by a string. I just use the subscription's id as that string. So wherever I am in the code, whether it's the subscribe request, the cancel button, the \"I'm on board\" tap or the safety-net cron, I hand it the same id and I'm always talking to the exact same object holding that one alert's state. No lookup table, no routing, no working out which server a subscription lives on. The id is the address. And because each object only ever holds its own alert, one person's alert going sideways can't touch anyone else's.

\n

Here's the one that really sold me though. A Durable Object only ever does one thing at a time. It won't run two requests against the same object at once, it queues them up. That sounds like a small detail but it's the whole reason the tricky part of this app is even doable, which brings me to the messy bit.

\n

A single wake-up isn't instant. The object has to go out over the network to TfNSW for the live departures. Then it has to actually send your push or your email. Both of those take real time. Now picture the alert half way through that, sitting there waiting on the departures to come back. Right at that moment you tap \"cancel\" or \"I'm on board\". Because the object does one thing at a time your tap can't barge in mid-step, but it does get its turn the instant the alert pauses to wait on the network. So it flips the state out from under the rest of the run. The alert wakes back up thinking everything's fine and, if I'm not careful, cheerfully sends you a notification for an alert you just cancelled.

\n

The honest answer to that is a lot of little if-checks. The wake-up loop is peppered with early exits that all ask the same question in different spots: has this been cancelled or paused while I wasn't looking? Bail out if it has. There's one on the very first line before it does anything. There's one straight after the departures come back from TfNSW. There's one right before it sends each notification and another right after. There's even one tucked inside the final save, so if a cancel landed while the alert was mid-flight, the moment it goes to write \"all done, wake me in 45 seconds\" it checks one last time and just doesn't, because arming a fresh timer on an alert you've killed is exactly how you end up haunted by a cancelled alert that won't die. Cancelling also leaves a little durable marker behind, a tombstone, so the safety-net cron can't come past later, spot an alert with no timer set and helpfully resurrect the very thing you just deleted.

\n

It's not the most elegant code I've ever written and there are more of those guards than I'd like. But every single one of them is a real race I could sit and describe to you. And the reason they get to be simple little if-blocks instead of a knot of locks is that one-thing-at-a-time promise. Without Durable Objects I'd be running one big loop over everyone's alerts, all of them fighting over the same database rows with a proper lock bolted on so a cancel and a poll couldn't clobber each other. With a Durable Object the isolation is the lock. Each alert ends up as a little self-winding alarm clock, chasing the train's live delay, costing nothing while it's asleep between your commutes and never fighting anyone for its own state, because it's the only thing that ever touches it.

\n\n

Sticking points

\n

The API that swore every station was invalid

\n

Early on the station search flat out refused to work. Every query came back with zero locations and a \"stop invalid\" message, even for Central. The culprit was one innocent looking parameter. Stop Finder takes a type_sf field and I'd set it to stop, because I was searching for stops. It turns out type_sf=stop actually means \"the text I'm sending is a stop ID\", so the broker was taking the word Cronulla and trying to look it up as a numeric station identifier. The fix was type_sf=any and then filtering the mixed bag of streets and points of interest down to actual stops. I only caught it because I verified the client against the live API instead of trusting the docs. The mock TfNSW server my Playwright tests drive now mirrors that exact broker behaviour, so this one can never quietly come back.

\n

Daylight saving and the missing hour

\n

Active windows are Sydney local time and my first version of \"when does today's window start\" was simple minute arithmetic from the current moment. An adversarial review pass over the whole codebase caught that the simple version drifts by exactly an hour whenever the calculation straddles the 2am daylight saving changeover, which in the worst case means a whole morning of missed alerts. The fix was to stop doing arithmetic and start constructing the actual Sydney wall time, using Intl with the Australia/Sydney timezone to check the guess and correct it. I reckon DST bugs are the sneakiest kind there is, because the broken code is provably correct for 363 days of the year.

\n

The alert that sent people the wrong way

\n

The \"Towards\" box is meant to be a light direction filter. Say you're leaving Town Hall heading home to Cronulla, you pop Cronulla in and the alert only bothers you about trains actually going your way. The catch is that a train only really tells you its final stop, so a service that passes through your destination on the way to somewhere else doesn't always say your stop's name. The filter would match nothing. My first version handled that by failing open, so when nothing matched it just alerted you about everything, on the theory that too many alerts beats none. That turned out to be genuinely annoying. On a quiet poll with no Cronulla train in sight it would happily ping me about trains heading the complete opposite way, up to Berowra and out to Emu Plains. The fix was to give the filter a memory. The first time a real matching train shows up it quietly notes that this direction does work here. From then on a poll with no match means go quiet, not alert about everything. It only falls back to the noisy everything-goes behaviour for a destination that has never once matched, which is the genuine can't-tell case. I still reckon failing open was the right instinct. It just shouldn't have been what happens every time.

\n

Web push from scratch, byte for byte

\n

The web push libraries everyone reaches for are built for Node and lean on APIs a Worker doesn't have. Rather than fight that, I wrote the push layer from scratch on WebCrypto. That means two RFCs. VAPID (RFC 8292), an ES256 signed JWT that proves to Apple's or Google's push service the message really came from my server. Then the payload encryption (RFC 8291), a chain of ECDH key agreement, HKDF derivations and AES-128-GCM. Hand rolling crypto is nerve-racking because the failure mode is silence, the push service just drops anything malformed and tells you nothing. The saving grace is that the encryption RFC ships a complete worked example with every intermediate value spelled out. So I made the encrypt function accept an injectable random seed and wrote a test that reproduces that worked example byte for byte. When it went green I knew the scariest part of the codebase was actually correct rather than just plausible.

\n\n

The result

\n

I couldn't be happier with how this one came out! The first build took a couple of days with Claude Code alongside me and it's grown up a fair bit since it went live. The caching that keeps it inside the free tier and the fix for those wrong-way alerts both landed after real people started using it, which is the best way to find that sort of thing. My phone buzzes two minutes before the train actually leaves, the alert already knows it's running late and one tap on \"I'm on board\" keeps everything quiet until my next travel day. I don't open the app, I don't refresh anything and I've stopped standing on platforms waiting. It's all covered end to end, Vitest across the schedule and crypto logic and Playwright driving the real Worker against a mock TfNSW upstream, so I can keep tweaking it without holding my breath. It's live at sydneytrainalerts.com, so if you're a Sydney commuter you're welcome to set one up of your own. I've also popped a little blog on there with a few pieces to help people find it.

\n

If you're thinking about doing web push yourself, here's the tip I wish I'd had on day one. Don't test your encryption by firing real notifications and hoping. Make the random inputs injectable and verify your output against the worked example in RFC 8291 first, because the push services silently drop anything malformed and you'll get no clue why. Once that test passes, everything downstream is just plumbing. Happy coding!

" + "content": "

Overview

\n

It's been nothing short of an exceptional first two months at Gyoza Flights! Jumping into the world of aviation has been super interesting and it has shifted my thinking as both an engineer and person. A recent example of this was the other day I was standing at Wynyard Station waiting for a train to Town Hall. My train of thought immediately drifted to origins and destinations. How my destination of Cronulla was all I really cared about and knowing when the next couple of trains would be departing. Sure I could go onto one of the various apps available and check but with my engineering brain I questioned if that was actually necessary. And did it cover what I really wanted? Sometimes I work back in the office, sometimes I leave early. I'm only in the office a few days a week and some of those are fortnightly. So as I stood and waited for my train an idea started to percolate. What if I just had a website where people could just choose an origin and destination, set an alert from time, a window to receive the alerts, a type of alert (email or push notification) and then set and forget. Then they'd automatically get these alerts to their device and would be able to proactively determine when they should leave their office to make a train that suits what time they were thinking of leaving on that day. And that is how sydneytrainalerts.com was born.

\n

I build on Cloudflare constantly and my tennis calendar is still quietly doing its thing from a Worker right now, so I had a fair idea of what was sitting there already. Then I remembered the alarm on Durable Objects, where a tiny stateful object can put itself to sleep and wake at an exact moment. That's precisely the shape of \"ping me two minutes before each train\", so that's what I built it on. You pick a station, how much notice you want, the days and window you travel in and whether you want the alert by push or email. From then on it watches the live departures and nudges you before each train you could catch. Once you're on, you tap \"I'm on board\" and it stays quiet until your next window.

\n\"The\n\n

How it works

\n

The data comes from Transport for NSW's Open Data platform. Their Trip Planner API has a Departure Monitor endpoint that hands back the upcoming departures from a stop as plain JSON with the realtime estimates already merged in, so there's no GTFS protobuf wrangling at all. A second endpoint, Stop Finder, powers the station autocomplete on the form. The free tier gives you 60,000 calls a day which is heaps for a personal tool like this.

\n\"The\n

The part I really wanted to get right is the timing. Every alert is its own little Cloudflare Durable Object that watches your station through your travel window, follows the live delays and nudges you before each train you could catch. Once it's pinged you about one it lists the next couple behind it too, so if you miss the 5:12 you already know the 5:22 is coming. Getting that to actually work is the most interesting thing in the whole build, so I've given it its own section below.

\n\"Choosing\n

Schedules are Sydney wall-clock time with travel days, a start and end for the window and even fortnightly days for rosters that alternate. The subscriptions themselves live in a D1 database, which stays the source of truth. A cron fires every 15 minutes purely as a safety net so nothing gets stranded after a deploy. Once you're on your train you tap \"I'm on board\" and it goes quiet for the rest of that window, then picks back up on your next travel day. That pause sits on the manage page for every alert as well as on the notification itself.

\n\"The\n

One thing I had to be careful about is staying inside that free tier once more than a handful of people are using it. TfNSW's free plan also caps you at about 5 calls a second. It starts knocking requests back the moment you push past that. So the departure lookups run through Cloudflare's cache, keyed on the station and the minute. If ten people are all watching Town Hall at 5:31 they mostly land on the same cached answer instead of making ten separate calls. If TfNSW ever does rate-limit me the alerts back off and give it room instead of hammering away. I also sprinkle a bit of random jitter on the wake-ups so a deploy doesn't fire every alert at the same second and trip the limit myself. None of it is glamorous but it's what lets one free key quietly handle a proper crowd of commuters.

\n

Delivery is push or email. Email goes through Resend with a manage link in every message so you can pause or cancel from the alert itself. If the push service ever reports a subscription as gone, the alert deactivates itself rather than pushing into the void forever. The frontend is one dependency-free page, no framework, with the autocomplete, a live preview of the alert you're building and a proper install walkthrough for iPhones, because iOS will only deliver web push to a site that's been added to the home screen. All up it's about 2,000 lines with exactly one runtime dependency, Hono for the routing.

\n\n

The Cool Nerd Part

\n

Right, this is the bit I actually get excited about. Before I committed to building it this way I sat down and properly grilled the design, because \"give every single alert its own little server that never really sleeps\" sounds mad when you first say it out loud. The more I poked at it though the more Durable Objects turned out to be the perfect fit, so let me walk through why.

\n

The timing first. Your train is meant to leave at 5:12 so a two minute warning should fire at 5:10. Then it runs four minutes late and the warning needs to fire at 5:14 instead. The target keeps moving all afternoon. A Durable Object has this thing called an alarm, which is basically a single built-in timer the object can point at any exact moment in the future. Better still, it survives restarts and deploys. So every time the alert wakes up and checks the live departures it works out the new fire time from the latest estimate and re-points its own alarm at it. The 5:12 slips to 5:14 and the alarm slides right along with it. It's an alert that reschedules itself off the delay. A plain cron can't do that. The finest a cron will give you is once a minute, so you'd be waking every minute, pulling every subscription and still landing up to a minute wide of a target that won't sit still.

\n

Then there's the fact that each alert is its own object with its own name. The trick with Durable Objects is that you address one by a string. I just use the subscription's id as that string. So wherever I am in the code, whether it's the subscribe request, the cancel button, the \"I'm on board\" tap or the safety-net cron, I hand it the same id and I'm always talking to the exact same object holding that one alert's state. No lookup table, no routing, no working out which server a subscription lives on. The id is the address. And because each object only ever holds its own alert, one person's alert going sideways can't touch anyone else's.

\n

Here's the one that really sold me though. A Durable Object only ever does one thing at a time. It won't run two requests against the same object at once, it queues them up. That sounds like a small detail but it's the whole reason the tricky part of this app is even doable, which brings me to the messy bit.

\n

A single wake-up isn't instant. The object has to go out over the network to TfNSW for the live departures. Then it has to actually send your push or your email. Both of those take real time. Now picture the alert half way through that, sitting there waiting on the departures to come back. Right at that moment you tap \"cancel\" or \"I'm on board\". Because the object does one thing at a time your tap can't barge in mid-step, but it does get its turn the instant the alert pauses to wait on the network. So it flips the state out from under the rest of the run. The alert wakes back up thinking everything's fine and, if I'm not careful, cheerfully sends you a notification for an alert you just cancelled.

\n

The honest answer to that is a lot of little if-checks. The wake-up loop is peppered with early exits that all ask the same question in different spots: has this been cancelled or paused while I wasn't looking? Bail out if it has. There's one on the very first line before it does anything. There's one straight after the departures come back from TfNSW. There's one right before it sends each notification and another right after. There's even one tucked inside the final save, so if a cancel landed while the alert was mid-flight, the moment it goes to write \"all done, wake me in 45 seconds\" it checks one last time and just doesn't, because arming a fresh timer on an alert you've killed is exactly how you end up haunted by a cancelled alert that won't die. Cancelling also leaves a little durable marker behind, a tombstone, so the safety-net cron can't come past later, spot an alert with no timer set and helpfully resurrect the very thing you just deleted.

\n

It's not the most elegant code I've ever written and there are more of those guards than I'd like. But every single one of them is a real race I could sit and describe to you. And the reason they get to be simple little if-blocks instead of a knot of locks is that one-thing-at-a-time promise. Without Durable Objects I'd be running one big loop over everyone's alerts, all of them fighting over the same database rows with a proper lock bolted on so a cancel and a poll couldn't clobber each other. With a Durable Object the isolation is the lock. Each alert ends up as a little self-winding alarm clock, chasing the train's live delay, costing nothing while it's asleep between your commutes and never fighting anyone for its own state, because it's the only thing that ever touches it.

\n\n

Sticking points

\n

The API that swore every station was invalid

\n

Early on the station search flat out refused to work. Every query came back with zero locations and a \"stop invalid\" message, even for Central. The culprit was one innocent looking parameter. Stop Finder takes a type_sf field and I'd set it to stop, because I was searching for stops. It turns out type_sf=stop actually means \"the text I'm sending is a stop ID\", so the broker was taking the word Cronulla and trying to look it up as a numeric station identifier. The fix was type_sf=any and then filtering the mixed bag of streets and points of interest down to actual stops. I only caught it because I verified the client against the live API instead of trusting the docs. The mock TfNSW server my Playwright tests drive now mirrors that exact broker behaviour, so this one can never quietly come back.

\n

Daylight saving and the missing hour

\n

Active windows are Sydney local time and my first version of \"when does today's window start\" was simple minute arithmetic from the current moment. An adversarial review pass over the whole codebase caught that the simple version drifts by exactly an hour whenever the calculation straddles the 2am daylight saving changeover, which in the worst case means a whole morning of missed alerts. The fix was to stop doing arithmetic and start constructing the actual Sydney wall time, using Intl with the Australia/Sydney timezone to check the guess and correct it. I reckon DST bugs are the sneakiest kind there is, because the broken code is provably correct for 363 days of the year.

\n

The alert that sent people the wrong way

\n

The \"Towards\" box is meant to be a light direction filter. Say you're leaving Town Hall heading home to Cronulla, you pop Cronulla in and the alert only bothers you about trains actually going your way. The catch is that a train only really tells you its final stop, so a service that passes through your destination on the way to somewhere else doesn't always say your stop's name. The filter would match nothing. My first version handled that by failing open, so when nothing matched it just alerted you about everything, on the theory that too many alerts beats none. That turned out to be genuinely annoying. On a quiet poll with no Cronulla train in sight it would happily ping me about trains heading the complete opposite way, up to Berowra and out to Emu Plains. The fix was to give the filter a memory. The first time a real matching train shows up it quietly notes that this direction does work here. From then on a poll with no match means go quiet, not alert about everything. It only falls back to the noisy everything-goes behaviour for a destination that has never once matched, which is the genuine can't-tell case. I still reckon failing open was the right instinct. It just shouldn't have been what happens every time.

\n

Web push from scratch, byte for byte

\n

The web push libraries everyone reaches for are built for Node and lean on APIs a Worker doesn't have. Rather than fight that, I wrote the push layer from scratch on WebCrypto. That means two RFCs. VAPID (RFC 8292), an ES256 signed JWT that proves to Apple's or Google's push service the message really came from my server. Then the payload encryption (RFC 8291), a chain of ECDH key agreement, HKDF derivations and AES-128-GCM. Hand rolling crypto is nerve-racking because the failure mode is silence, the push service just drops anything malformed and tells you nothing. The saving grace is that the encryption RFC ships a complete worked example with every intermediate value spelled out. So I made the encrypt function accept an injectable random seed and wrote a test that reproduces that worked example byte for byte. When it went green I knew the scariest part of the codebase was actually correct rather than just plausible.

\n\n

The result

\n

I couldn't be happier with how this one came out! The first build took a couple of days with Claude Code alongside me and it's grown up a fair bit since it went live. The caching that keeps it inside the free tier and the fix for those wrong-way alerts both landed after real people started using it, which is the best way to find that sort of thing. My phone buzzes two minutes before the train actually leaves, the alert already knows it's running late and one tap on \"I'm on board\" keeps everything quiet until my next travel day. I don't open the app, I don't refresh anything and I've stopped standing on platforms waiting. It's all covered end to end, Vitest across the schedule and crypto logic and Playwright driving the real Worker against a mock TfNSW upstream, so I can keep tweaking it without holding my breath. It's live at sydneytrainalerts.com, so if you're a Sydney commuter you're welcome to set one up of your own. I've also popped a little blog on there with a few pieces to help people find it.

\n

If you're thinking about doing web push yourself, here's the tip I wish I'd had on day one. Don't test your encryption by firing real notifications and hoping. Make the random inputs injectable and verify your output against the worked example in RFC 8291 first, because the push services silently drop anything malformed and you'll get no clue why. Once that test passes, everything downstream is just plumbing. Happy coding!

" }, { "slug": "album-tracker-cli", From 0d7a0e53f17ba8630d1e67496f83e0a11627e337 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 28 Jul 2026 08:33:13 +0000 Subject: [PATCH 07/11] Clarify the alert setup as a lead time in the overview "set an alert from time" read ambiguously. It means the notice you want before departure, so say lead time. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01AdKpQxJJZEHt3YZKRT4ueu --- src/data/projects.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/projects.json b/src/data/projects.json index ad7b3f6..2e5fab3 100644 --- a/src/data/projects.json +++ b/src/data/projects.json @@ -8,7 +8,7 @@ "tech": ["TypeScript", "Cloudflare Workers", "Durable Objects", "Cloudflare D1", "Cache API", "Hono", "Web Push / VAPID", "WebCrypto", "Resend", "TfNSW Open Data", "Vitest", "Playwright"], "period": "July 2026", "order": 1, - "content": "

Overview

\n

It's been nothing short of an exceptional first two months at Gyoza Flights! Jumping into the world of aviation has been super interesting and it has shifted my thinking as both an engineer and person. A recent example of this was the other day I was standing at Wynyard Station waiting for a train to Town Hall. My train of thought immediately drifted to origins and destinations. How my destination of Cronulla was all I really cared about and knowing when the next couple of trains would be departing. Sure I could go onto one of the various apps available and check but with my engineering brain I questioned if that was actually necessary. And did it cover what I really wanted? Sometimes I work back in the office, sometimes I leave early. I'm only in the office a few days a week and some of those are fortnightly. So as I stood and waited for my train an idea started to percolate. What if I just had a website where people could just choose an origin and destination, set an alert from time, a window to receive the alerts, a type of alert (email or push notification) and then set and forget. Then they'd automatically get these alerts to their device and would be able to proactively determine when they should leave their office to make a train that suits what time they were thinking of leaving on that day. And that is how sydneytrainalerts.com was born.

\n

I build on Cloudflare constantly and my tennis calendar is still quietly doing its thing from a Worker right now, so I had a fair idea of what was sitting there already. Then I remembered the alarm on Durable Objects, where a tiny stateful object can put itself to sleep and wake at an exact moment. That's precisely the shape of \"ping me two minutes before each train\", so that's what I built it on. You pick a station, how much notice you want, the days and window you travel in and whether you want the alert by push or email. From then on it watches the live departures and nudges you before each train you could catch. Once you're on, you tap \"I'm on board\" and it stays quiet until your next window.

\n\"The\n\n

How it works

\n

The data comes from Transport for NSW's Open Data platform. Their Trip Planner API has a Departure Monitor endpoint that hands back the upcoming departures from a stop as plain JSON with the realtime estimates already merged in, so there's no GTFS protobuf wrangling at all. A second endpoint, Stop Finder, powers the station autocomplete on the form. The free tier gives you 60,000 calls a day which is heaps for a personal tool like this.

\n\"The\n

The part I really wanted to get right is the timing. Every alert is its own little Cloudflare Durable Object that watches your station through your travel window, follows the live delays and nudges you before each train you could catch. Once it's pinged you about one it lists the next couple behind it too, so if you miss the 5:12 you already know the 5:22 is coming. Getting that to actually work is the most interesting thing in the whole build, so I've given it its own section below.

\n\"Choosing\n

Schedules are Sydney wall-clock time with travel days, a start and end for the window and even fortnightly days for rosters that alternate. The subscriptions themselves live in a D1 database, which stays the source of truth. A cron fires every 15 minutes purely as a safety net so nothing gets stranded after a deploy. Once you're on your train you tap \"I'm on board\" and it goes quiet for the rest of that window, then picks back up on your next travel day. That pause sits on the manage page for every alert as well as on the notification itself.

\n\"The\n

One thing I had to be careful about is staying inside that free tier once more than a handful of people are using it. TfNSW's free plan also caps you at about 5 calls a second. It starts knocking requests back the moment you push past that. So the departure lookups run through Cloudflare's cache, keyed on the station and the minute. If ten people are all watching Town Hall at 5:31 they mostly land on the same cached answer instead of making ten separate calls. If TfNSW ever does rate-limit me the alerts back off and give it room instead of hammering away. I also sprinkle a bit of random jitter on the wake-ups so a deploy doesn't fire every alert at the same second and trip the limit myself. None of it is glamorous but it's what lets one free key quietly handle a proper crowd of commuters.

\n

Delivery is push or email. Email goes through Resend with a manage link in every message so you can pause or cancel from the alert itself. If the push service ever reports a subscription as gone, the alert deactivates itself rather than pushing into the void forever. The frontend is one dependency-free page, no framework, with the autocomplete, a live preview of the alert you're building and a proper install walkthrough for iPhones, because iOS will only deliver web push to a site that's been added to the home screen. All up it's about 2,000 lines with exactly one runtime dependency, Hono for the routing.

\n\n

The Cool Nerd Part

\n

Right, this is the bit I actually get excited about. Before I committed to building it this way I sat down and properly grilled the design, because \"give every single alert its own little server that never really sleeps\" sounds mad when you first say it out loud. The more I poked at it though the more Durable Objects turned out to be the perfect fit, so let me walk through why.

\n

The timing first. Your train is meant to leave at 5:12 so a two minute warning should fire at 5:10. Then it runs four minutes late and the warning needs to fire at 5:14 instead. The target keeps moving all afternoon. A Durable Object has this thing called an alarm, which is basically a single built-in timer the object can point at any exact moment in the future. Better still, it survives restarts and deploys. So every time the alert wakes up and checks the live departures it works out the new fire time from the latest estimate and re-points its own alarm at it. The 5:12 slips to 5:14 and the alarm slides right along with it. It's an alert that reschedules itself off the delay. A plain cron can't do that. The finest a cron will give you is once a minute, so you'd be waking every minute, pulling every subscription and still landing up to a minute wide of a target that won't sit still.

\n

Then there's the fact that each alert is its own object with its own name. The trick with Durable Objects is that you address one by a string. I just use the subscription's id as that string. So wherever I am in the code, whether it's the subscribe request, the cancel button, the \"I'm on board\" tap or the safety-net cron, I hand it the same id and I'm always talking to the exact same object holding that one alert's state. No lookup table, no routing, no working out which server a subscription lives on. The id is the address. And because each object only ever holds its own alert, one person's alert going sideways can't touch anyone else's.

\n

Here's the one that really sold me though. A Durable Object only ever does one thing at a time. It won't run two requests against the same object at once, it queues them up. That sounds like a small detail but it's the whole reason the tricky part of this app is even doable, which brings me to the messy bit.

\n

A single wake-up isn't instant. The object has to go out over the network to TfNSW for the live departures. Then it has to actually send your push or your email. Both of those take real time. Now picture the alert half way through that, sitting there waiting on the departures to come back. Right at that moment you tap \"cancel\" or \"I'm on board\". Because the object does one thing at a time your tap can't barge in mid-step, but it does get its turn the instant the alert pauses to wait on the network. So it flips the state out from under the rest of the run. The alert wakes back up thinking everything's fine and, if I'm not careful, cheerfully sends you a notification for an alert you just cancelled.

\n

The honest answer to that is a lot of little if-checks. The wake-up loop is peppered with early exits that all ask the same question in different spots: has this been cancelled or paused while I wasn't looking? Bail out if it has. There's one on the very first line before it does anything. There's one straight after the departures come back from TfNSW. There's one right before it sends each notification and another right after. There's even one tucked inside the final save, so if a cancel landed while the alert was mid-flight, the moment it goes to write \"all done, wake me in 45 seconds\" it checks one last time and just doesn't, because arming a fresh timer on an alert you've killed is exactly how you end up haunted by a cancelled alert that won't die. Cancelling also leaves a little durable marker behind, a tombstone, so the safety-net cron can't come past later, spot an alert with no timer set and helpfully resurrect the very thing you just deleted.

\n

It's not the most elegant code I've ever written and there are more of those guards than I'd like. But every single one of them is a real race I could sit and describe to you. And the reason they get to be simple little if-blocks instead of a knot of locks is that one-thing-at-a-time promise. Without Durable Objects I'd be running one big loop over everyone's alerts, all of them fighting over the same database rows with a proper lock bolted on so a cancel and a poll couldn't clobber each other. With a Durable Object the isolation is the lock. Each alert ends up as a little self-winding alarm clock, chasing the train's live delay, costing nothing while it's asleep between your commutes and never fighting anyone for its own state, because it's the only thing that ever touches it.

\n\n

Sticking points

\n

The API that swore every station was invalid

\n

Early on the station search flat out refused to work. Every query came back with zero locations and a \"stop invalid\" message, even for Central. The culprit was one innocent looking parameter. Stop Finder takes a type_sf field and I'd set it to stop, because I was searching for stops. It turns out type_sf=stop actually means \"the text I'm sending is a stop ID\", so the broker was taking the word Cronulla and trying to look it up as a numeric station identifier. The fix was type_sf=any and then filtering the mixed bag of streets and points of interest down to actual stops. I only caught it because I verified the client against the live API instead of trusting the docs. The mock TfNSW server my Playwright tests drive now mirrors that exact broker behaviour, so this one can never quietly come back.

\n

Daylight saving and the missing hour

\n

Active windows are Sydney local time and my first version of \"when does today's window start\" was simple minute arithmetic from the current moment. An adversarial review pass over the whole codebase caught that the simple version drifts by exactly an hour whenever the calculation straddles the 2am daylight saving changeover, which in the worst case means a whole morning of missed alerts. The fix was to stop doing arithmetic and start constructing the actual Sydney wall time, using Intl with the Australia/Sydney timezone to check the guess and correct it. I reckon DST bugs are the sneakiest kind there is, because the broken code is provably correct for 363 days of the year.

\n

The alert that sent people the wrong way

\n

The \"Towards\" box is meant to be a light direction filter. Say you're leaving Town Hall heading home to Cronulla, you pop Cronulla in and the alert only bothers you about trains actually going your way. The catch is that a train only really tells you its final stop, so a service that passes through your destination on the way to somewhere else doesn't always say your stop's name. The filter would match nothing. My first version handled that by failing open, so when nothing matched it just alerted you about everything, on the theory that too many alerts beats none. That turned out to be genuinely annoying. On a quiet poll with no Cronulla train in sight it would happily ping me about trains heading the complete opposite way, up to Berowra and out to Emu Plains. The fix was to give the filter a memory. The first time a real matching train shows up it quietly notes that this direction does work here. From then on a poll with no match means go quiet, not alert about everything. It only falls back to the noisy everything-goes behaviour for a destination that has never once matched, which is the genuine can't-tell case. I still reckon failing open was the right instinct. It just shouldn't have been what happens every time.

\n

Web push from scratch, byte for byte

\n

The web push libraries everyone reaches for are built for Node and lean on APIs a Worker doesn't have. Rather than fight that, I wrote the push layer from scratch on WebCrypto. That means two RFCs. VAPID (RFC 8292), an ES256 signed JWT that proves to Apple's or Google's push service the message really came from my server. Then the payload encryption (RFC 8291), a chain of ECDH key agreement, HKDF derivations and AES-128-GCM. Hand rolling crypto is nerve-racking because the failure mode is silence, the push service just drops anything malformed and tells you nothing. The saving grace is that the encryption RFC ships a complete worked example with every intermediate value spelled out. So I made the encrypt function accept an injectable random seed and wrote a test that reproduces that worked example byte for byte. When it went green I knew the scariest part of the codebase was actually correct rather than just plausible.

\n\n

The result

\n

I couldn't be happier with how this one came out! The first build took a couple of days with Claude Code alongside me and it's grown up a fair bit since it went live. The caching that keeps it inside the free tier and the fix for those wrong-way alerts both landed after real people started using it, which is the best way to find that sort of thing. My phone buzzes two minutes before the train actually leaves, the alert already knows it's running late and one tap on \"I'm on board\" keeps everything quiet until my next travel day. I don't open the app, I don't refresh anything and I've stopped standing on platforms waiting. It's all covered end to end, Vitest across the schedule and crypto logic and Playwright driving the real Worker against a mock TfNSW upstream, so I can keep tweaking it without holding my breath. It's live at sydneytrainalerts.com, so if you're a Sydney commuter you're welcome to set one up of your own. I've also popped a little blog on there with a few pieces to help people find it.

\n

If you're thinking about doing web push yourself, here's the tip I wish I'd had on day one. Don't test your encryption by firing real notifications and hoping. Make the random inputs injectable and verify your output against the worked example in RFC 8291 first, because the push services silently drop anything malformed and you'll get no clue why. Once that test passes, everything downstream is just plumbing. Happy coding!

" + "content": "

Overview

\n

It's been nothing short of an exceptional first two months at Gyoza Flights! Jumping into the world of aviation has been super interesting and it has shifted my thinking as both an engineer and person. A recent example of this was the other day I was standing at Wynyard Station waiting for a train to Town Hall. My train of thought immediately drifted to origins and destinations. How my destination of Cronulla was all I really cared about and knowing when the next couple of trains would be departing. Sure I could go onto one of the various apps available and check but with my engineering brain I questioned if that was actually necessary. And did it cover what I really wanted? Sometimes I work back in the office, sometimes I leave early. I'm only in the office a few days a week and some of those are fortnightly. So as I stood and waited for my train an idea started to percolate. What if I just had a website where people could just choose an origin and destination, set a lead time, a window to receive the alerts, a type of alert (email or push notification) and then set and forget. Then they'd automatically get these alerts to their device and would be able to proactively determine when they should leave their office to make a train that suits what time they were thinking of leaving on that day. And that is how sydneytrainalerts.com was born.

\n

I build on Cloudflare constantly and my tennis calendar is still quietly doing its thing from a Worker right now, so I had a fair idea of what was sitting there already. Then I remembered the alarm on Durable Objects, where a tiny stateful object can put itself to sleep and wake at an exact moment. That's precisely the shape of \"ping me two minutes before each train\", so that's what I built it on. You pick a station, how much notice you want, the days and window you travel in and whether you want the alert by push or email. From then on it watches the live departures and nudges you before each train you could catch. Once you're on, you tap \"I'm on board\" and it stays quiet until your next window.

\n\"The\n\n

How it works

\n

The data comes from Transport for NSW's Open Data platform. Their Trip Planner API has a Departure Monitor endpoint that hands back the upcoming departures from a stop as plain JSON with the realtime estimates already merged in, so there's no GTFS protobuf wrangling at all. A second endpoint, Stop Finder, powers the station autocomplete on the form. The free tier gives you 60,000 calls a day which is heaps for a personal tool like this.

\n\"The\n

The part I really wanted to get right is the timing. Every alert is its own little Cloudflare Durable Object that watches your station through your travel window, follows the live delays and nudges you before each train you could catch. Once it's pinged you about one it lists the next couple behind it too, so if you miss the 5:12 you already know the 5:22 is coming. Getting that to actually work is the most interesting thing in the whole build, so I've given it its own section below.

\n\"Choosing\n

Schedules are Sydney wall-clock time with travel days, a start and end for the window and even fortnightly days for rosters that alternate. The subscriptions themselves live in a D1 database, which stays the source of truth. A cron fires every 15 minutes purely as a safety net so nothing gets stranded after a deploy. Once you're on your train you tap \"I'm on board\" and it goes quiet for the rest of that window, then picks back up on your next travel day. That pause sits on the manage page for every alert as well as on the notification itself.

\n\"The\n

One thing I had to be careful about is staying inside that free tier once more than a handful of people are using it. TfNSW's free plan also caps you at about 5 calls a second. It starts knocking requests back the moment you push past that. So the departure lookups run through Cloudflare's cache, keyed on the station and the minute. If ten people are all watching Town Hall at 5:31 they mostly land on the same cached answer instead of making ten separate calls. If TfNSW ever does rate-limit me the alerts back off and give it room instead of hammering away. I also sprinkle a bit of random jitter on the wake-ups so a deploy doesn't fire every alert at the same second and trip the limit myself. None of it is glamorous but it's what lets one free key quietly handle a proper crowd of commuters.

\n

Delivery is push or email. Email goes through Resend with a manage link in every message so you can pause or cancel from the alert itself. If the push service ever reports a subscription as gone, the alert deactivates itself rather than pushing into the void forever. The frontend is one dependency-free page, no framework, with the autocomplete, a live preview of the alert you're building and a proper install walkthrough for iPhones, because iOS will only deliver web push to a site that's been added to the home screen. All up it's about 2,000 lines with exactly one runtime dependency, Hono for the routing.

\n\n

The Cool Nerd Part

\n

Right, this is the bit I actually get excited about. Before I committed to building it this way I sat down and properly grilled the design, because \"give every single alert its own little server that never really sleeps\" sounds mad when you first say it out loud. The more I poked at it though the more Durable Objects turned out to be the perfect fit, so let me walk through why.

\n

The timing first. Your train is meant to leave at 5:12 so a two minute warning should fire at 5:10. Then it runs four minutes late and the warning needs to fire at 5:14 instead. The target keeps moving all afternoon. A Durable Object has this thing called an alarm, which is basically a single built-in timer the object can point at any exact moment in the future. Better still, it survives restarts and deploys. So every time the alert wakes up and checks the live departures it works out the new fire time from the latest estimate and re-points its own alarm at it. The 5:12 slips to 5:14 and the alarm slides right along with it. It's an alert that reschedules itself off the delay. A plain cron can't do that. The finest a cron will give you is once a minute, so you'd be waking every minute, pulling every subscription and still landing up to a minute wide of a target that won't sit still.

\n

Then there's the fact that each alert is its own object with its own name. The trick with Durable Objects is that you address one by a string. I just use the subscription's id as that string. So wherever I am in the code, whether it's the subscribe request, the cancel button, the \"I'm on board\" tap or the safety-net cron, I hand it the same id and I'm always talking to the exact same object holding that one alert's state. No lookup table, no routing, no working out which server a subscription lives on. The id is the address. And because each object only ever holds its own alert, one person's alert going sideways can't touch anyone else's.

\n

Here's the one that really sold me though. A Durable Object only ever does one thing at a time. It won't run two requests against the same object at once, it queues them up. That sounds like a small detail but it's the whole reason the tricky part of this app is even doable, which brings me to the messy bit.

\n

A single wake-up isn't instant. The object has to go out over the network to TfNSW for the live departures. Then it has to actually send your push or your email. Both of those take real time. Now picture the alert half way through that, sitting there waiting on the departures to come back. Right at that moment you tap \"cancel\" or \"I'm on board\". Because the object does one thing at a time your tap can't barge in mid-step, but it does get its turn the instant the alert pauses to wait on the network. So it flips the state out from under the rest of the run. The alert wakes back up thinking everything's fine and, if I'm not careful, cheerfully sends you a notification for an alert you just cancelled.

\n

The honest answer to that is a lot of little if-checks. The wake-up loop is peppered with early exits that all ask the same question in different spots: has this been cancelled or paused while I wasn't looking? Bail out if it has. There's one on the very first line before it does anything. There's one straight after the departures come back from TfNSW. There's one right before it sends each notification and another right after. There's even one tucked inside the final save, so if a cancel landed while the alert was mid-flight, the moment it goes to write \"all done, wake me in 45 seconds\" it checks one last time and just doesn't, because arming a fresh timer on an alert you've killed is exactly how you end up haunted by a cancelled alert that won't die. Cancelling also leaves a little durable marker behind, a tombstone, so the safety-net cron can't come past later, spot an alert with no timer set and helpfully resurrect the very thing you just deleted.

\n

It's not the most elegant code I've ever written and there are more of those guards than I'd like. But every single one of them is a real race I could sit and describe to you. And the reason they get to be simple little if-blocks instead of a knot of locks is that one-thing-at-a-time promise. Without Durable Objects I'd be running one big loop over everyone's alerts, all of them fighting over the same database rows with a proper lock bolted on so a cancel and a poll couldn't clobber each other. With a Durable Object the isolation is the lock. Each alert ends up as a little self-winding alarm clock, chasing the train's live delay, costing nothing while it's asleep between your commutes and never fighting anyone for its own state, because it's the only thing that ever touches it.

\n\n

Sticking points

\n

The API that swore every station was invalid

\n

Early on the station search flat out refused to work. Every query came back with zero locations and a \"stop invalid\" message, even for Central. The culprit was one innocent looking parameter. Stop Finder takes a type_sf field and I'd set it to stop, because I was searching for stops. It turns out type_sf=stop actually means \"the text I'm sending is a stop ID\", so the broker was taking the word Cronulla and trying to look it up as a numeric station identifier. The fix was type_sf=any and then filtering the mixed bag of streets and points of interest down to actual stops. I only caught it because I verified the client against the live API instead of trusting the docs. The mock TfNSW server my Playwright tests drive now mirrors that exact broker behaviour, so this one can never quietly come back.

\n

Daylight saving and the missing hour

\n

Active windows are Sydney local time and my first version of \"when does today's window start\" was simple minute arithmetic from the current moment. An adversarial review pass over the whole codebase caught that the simple version drifts by exactly an hour whenever the calculation straddles the 2am daylight saving changeover, which in the worst case means a whole morning of missed alerts. The fix was to stop doing arithmetic and start constructing the actual Sydney wall time, using Intl with the Australia/Sydney timezone to check the guess and correct it. I reckon DST bugs are the sneakiest kind there is, because the broken code is provably correct for 363 days of the year.

\n

The alert that sent people the wrong way

\n

The \"Towards\" box is meant to be a light direction filter. Say you're leaving Town Hall heading home to Cronulla, you pop Cronulla in and the alert only bothers you about trains actually going your way. The catch is that a train only really tells you its final stop, so a service that passes through your destination on the way to somewhere else doesn't always say your stop's name. The filter would match nothing. My first version handled that by failing open, so when nothing matched it just alerted you about everything, on the theory that too many alerts beats none. That turned out to be genuinely annoying. On a quiet poll with no Cronulla train in sight it would happily ping me about trains heading the complete opposite way, up to Berowra and out to Emu Plains. The fix was to give the filter a memory. The first time a real matching train shows up it quietly notes that this direction does work here. From then on a poll with no match means go quiet, not alert about everything. It only falls back to the noisy everything-goes behaviour for a destination that has never once matched, which is the genuine can't-tell case. I still reckon failing open was the right instinct. It just shouldn't have been what happens every time.

\n

Web push from scratch, byte for byte

\n

The web push libraries everyone reaches for are built for Node and lean on APIs a Worker doesn't have. Rather than fight that, I wrote the push layer from scratch on WebCrypto. That means two RFCs. VAPID (RFC 8292), an ES256 signed JWT that proves to Apple's or Google's push service the message really came from my server. Then the payload encryption (RFC 8291), a chain of ECDH key agreement, HKDF derivations and AES-128-GCM. Hand rolling crypto is nerve-racking because the failure mode is silence, the push service just drops anything malformed and tells you nothing. The saving grace is that the encryption RFC ships a complete worked example with every intermediate value spelled out. So I made the encrypt function accept an injectable random seed and wrote a test that reproduces that worked example byte for byte. When it went green I knew the scariest part of the codebase was actually correct rather than just plausible.

\n\n

The result

\n

I couldn't be happier with how this one came out! The first build took a couple of days with Claude Code alongside me and it's grown up a fair bit since it went live. The caching that keeps it inside the free tier and the fix for those wrong-way alerts both landed after real people started using it, which is the best way to find that sort of thing. My phone buzzes two minutes before the train actually leaves, the alert already knows it's running late and one tap on \"I'm on board\" keeps everything quiet until my next travel day. I don't open the app, I don't refresh anything and I've stopped standing on platforms waiting. It's all covered end to end, Vitest across the schedule and crypto logic and Playwright driving the real Worker against a mock TfNSW upstream, so I can keep tweaking it without holding my breath. It's live at sydneytrainalerts.com, so if you're a Sydney commuter you're welcome to set one up of your own. I've also popped a little blog on there with a few pieces to help people find it.

\n

If you're thinking about doing web push yourself, here's the tip I wish I'd had on day one. Don't test your encryption by firing real notifications and hoping. Make the random inputs injectable and verify your output against the worked example in RFC 8291 first, because the push services silently drop anything malformed and you'll get no clue why. Once that test passes, everything downstream is just plumbing. Happy coding!

" }, { "slug": "album-tracker-cli", From de80679d63680c0756dfeb9e755e8bac03bdbc00 Mon Sep 17 00:00:00 2001 From: Daniel Workman Date: Tue, 4 Aug 2026 08:34:35 +1000 Subject: [PATCH 08/11] Add "The plot twist" to the Train Alerts writeup and refresh it from the app's history The direction-filter story now runs to its real ending. The existing sticking point covered the fail-open to fail-closed fix; the new section covers what that fix hid. A headsign names a terminus, so the fail-closed branch was silently dropping trains people could catch (Parramatta 4 of 80 matched against 20 that actually call there, Hurstville 5 against 15) while the fail-open branch pinged Redfern and Wynyard subscribers 79 times a window. Both branches wrong, same wrong signal. Then the fix: the gtfsTripId to route_id reconstruction, per-origin pattern shards built off-platform into Workers KV, block_id stitching for through-running services and the "absence is never a no" rule. States honestly that the matcher is merged but inert and the switch-on is still gated on a weekday peak-window acceptance run. Also refreshed from the app's git history since the last writeup pass: tiered TfNSW quota alerts at 50/75/90/100%, push as the default delivery channel with the email fallback for uninstalled iOS Safari, the ferry site served off the same worker by Host header, nine blog posts per site and the current line count. Period extended to August 2026 and the tech list picks up Workers KV and GTFS. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01UiyQtesPiN66UTFxb222Zu --- src/data/projects.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/data/projects.json b/src/data/projects.json index 2e5fab3..22d7b10 100644 --- a/src/data/projects.json +++ b/src/data/projects.json @@ -5,10 +5,10 @@ "title": "Train Alerts: a nudge before every Sydney train", "company": "", "description": "A departure alert service for Sydney trains built on Cloudflare Workers and Durable Objects. Pick a station, a lead time and your travel window and it pings you before each train actually leaves, following the live delays, with an \"I'm on board\" tap that keeps the rest of the window quiet.", - "tech": ["TypeScript", "Cloudflare Workers", "Durable Objects", "Cloudflare D1", "Cache API", "Hono", "Web Push / VAPID", "WebCrypto", "Resend", "TfNSW Open Data", "Vitest", "Playwright"], - "period": "July 2026", + "tech": ["TypeScript", "Cloudflare Workers", "Durable Objects", "Cloudflare D1", "Workers KV", "Cache API", "Hono", "Web Push / VAPID", "WebCrypto", "Resend", "TfNSW Open Data", "GTFS", "Vitest", "Playwright"], + "period": "July 2026 — August 2026", "order": 1, - "content": "

Overview

\n

It's been nothing short of an exceptional first two months at Gyoza Flights! Jumping into the world of aviation has been super interesting and it has shifted my thinking as both an engineer and person. A recent example of this was the other day I was standing at Wynyard Station waiting for a train to Town Hall. My train of thought immediately drifted to origins and destinations. How my destination of Cronulla was all I really cared about and knowing when the next couple of trains would be departing. Sure I could go onto one of the various apps available and check but with my engineering brain I questioned if that was actually necessary. And did it cover what I really wanted? Sometimes I work back in the office, sometimes I leave early. I'm only in the office a few days a week and some of those are fortnightly. So as I stood and waited for my train an idea started to percolate. What if I just had a website where people could just choose an origin and destination, set a lead time, a window to receive the alerts, a type of alert (email or push notification) and then set and forget. Then they'd automatically get these alerts to their device and would be able to proactively determine when they should leave their office to make a train that suits what time they were thinking of leaving on that day. And that is how sydneytrainalerts.com was born.

\n

I build on Cloudflare constantly and my tennis calendar is still quietly doing its thing from a Worker right now, so I had a fair idea of what was sitting there already. Then I remembered the alarm on Durable Objects, where a tiny stateful object can put itself to sleep and wake at an exact moment. That's precisely the shape of \"ping me two minutes before each train\", so that's what I built it on. You pick a station, how much notice you want, the days and window you travel in and whether you want the alert by push or email. From then on it watches the live departures and nudges you before each train you could catch. Once you're on, you tap \"I'm on board\" and it stays quiet until your next window.

\n\"The\n\n

How it works

\n

The data comes from Transport for NSW's Open Data platform. Their Trip Planner API has a Departure Monitor endpoint that hands back the upcoming departures from a stop as plain JSON with the realtime estimates already merged in, so there's no GTFS protobuf wrangling at all. A second endpoint, Stop Finder, powers the station autocomplete on the form. The free tier gives you 60,000 calls a day which is heaps for a personal tool like this.

\n\"The\n

The part I really wanted to get right is the timing. Every alert is its own little Cloudflare Durable Object that watches your station through your travel window, follows the live delays and nudges you before each train you could catch. Once it's pinged you about one it lists the next couple behind it too, so if you miss the 5:12 you already know the 5:22 is coming. Getting that to actually work is the most interesting thing in the whole build, so I've given it its own section below.

\n\"Choosing\n

Schedules are Sydney wall-clock time with travel days, a start and end for the window and even fortnightly days for rosters that alternate. The subscriptions themselves live in a D1 database, which stays the source of truth. A cron fires every 15 minutes purely as a safety net so nothing gets stranded after a deploy. Once you're on your train you tap \"I'm on board\" and it goes quiet for the rest of that window, then picks back up on your next travel day. That pause sits on the manage page for every alert as well as on the notification itself.

\n\"The\n

One thing I had to be careful about is staying inside that free tier once more than a handful of people are using it. TfNSW's free plan also caps you at about 5 calls a second. It starts knocking requests back the moment you push past that. So the departure lookups run through Cloudflare's cache, keyed on the station and the minute. If ten people are all watching Town Hall at 5:31 they mostly land on the same cached answer instead of making ten separate calls. If TfNSW ever does rate-limit me the alerts back off and give it room instead of hammering away. I also sprinkle a bit of random jitter on the wake-ups so a deploy doesn't fire every alert at the same second and trip the limit myself. None of it is glamorous but it's what lets one free key quietly handle a proper crowd of commuters.

\n

Delivery is push or email. Email goes through Resend with a manage link in every message so you can pause or cancel from the alert itself. If the push service ever reports a subscription as gone, the alert deactivates itself rather than pushing into the void forever. The frontend is one dependency-free page, no framework, with the autocomplete, a live preview of the alert you're building and a proper install walkthrough for iPhones, because iOS will only deliver web push to a site that's been added to the home screen. All up it's about 2,000 lines with exactly one runtime dependency, Hono for the routing.

\n\n

The Cool Nerd Part

\n

Right, this is the bit I actually get excited about. Before I committed to building it this way I sat down and properly grilled the design, because \"give every single alert its own little server that never really sleeps\" sounds mad when you first say it out loud. The more I poked at it though the more Durable Objects turned out to be the perfect fit, so let me walk through why.

\n

The timing first. Your train is meant to leave at 5:12 so a two minute warning should fire at 5:10. Then it runs four minutes late and the warning needs to fire at 5:14 instead. The target keeps moving all afternoon. A Durable Object has this thing called an alarm, which is basically a single built-in timer the object can point at any exact moment in the future. Better still, it survives restarts and deploys. So every time the alert wakes up and checks the live departures it works out the new fire time from the latest estimate and re-points its own alarm at it. The 5:12 slips to 5:14 and the alarm slides right along with it. It's an alert that reschedules itself off the delay. A plain cron can't do that. The finest a cron will give you is once a minute, so you'd be waking every minute, pulling every subscription and still landing up to a minute wide of a target that won't sit still.

\n

Then there's the fact that each alert is its own object with its own name. The trick with Durable Objects is that you address one by a string. I just use the subscription's id as that string. So wherever I am in the code, whether it's the subscribe request, the cancel button, the \"I'm on board\" tap or the safety-net cron, I hand it the same id and I'm always talking to the exact same object holding that one alert's state. No lookup table, no routing, no working out which server a subscription lives on. The id is the address. And because each object only ever holds its own alert, one person's alert going sideways can't touch anyone else's.

\n

Here's the one that really sold me though. A Durable Object only ever does one thing at a time. It won't run two requests against the same object at once, it queues them up. That sounds like a small detail but it's the whole reason the tricky part of this app is even doable, which brings me to the messy bit.

\n

A single wake-up isn't instant. The object has to go out over the network to TfNSW for the live departures. Then it has to actually send your push or your email. Both of those take real time. Now picture the alert half way through that, sitting there waiting on the departures to come back. Right at that moment you tap \"cancel\" or \"I'm on board\". Because the object does one thing at a time your tap can't barge in mid-step, but it does get its turn the instant the alert pauses to wait on the network. So it flips the state out from under the rest of the run. The alert wakes back up thinking everything's fine and, if I'm not careful, cheerfully sends you a notification for an alert you just cancelled.

\n

The honest answer to that is a lot of little if-checks. The wake-up loop is peppered with early exits that all ask the same question in different spots: has this been cancelled or paused while I wasn't looking? Bail out if it has. There's one on the very first line before it does anything. There's one straight after the departures come back from TfNSW. There's one right before it sends each notification and another right after. There's even one tucked inside the final save, so if a cancel landed while the alert was mid-flight, the moment it goes to write \"all done, wake me in 45 seconds\" it checks one last time and just doesn't, because arming a fresh timer on an alert you've killed is exactly how you end up haunted by a cancelled alert that won't die. Cancelling also leaves a little durable marker behind, a tombstone, so the safety-net cron can't come past later, spot an alert with no timer set and helpfully resurrect the very thing you just deleted.

\n

It's not the most elegant code I've ever written and there are more of those guards than I'd like. But every single one of them is a real race I could sit and describe to you. And the reason they get to be simple little if-blocks instead of a knot of locks is that one-thing-at-a-time promise. Without Durable Objects I'd be running one big loop over everyone's alerts, all of them fighting over the same database rows with a proper lock bolted on so a cancel and a poll couldn't clobber each other. With a Durable Object the isolation is the lock. Each alert ends up as a little self-winding alarm clock, chasing the train's live delay, costing nothing while it's asleep between your commutes and never fighting anyone for its own state, because it's the only thing that ever touches it.

\n\n

Sticking points

\n

The API that swore every station was invalid

\n

Early on the station search flat out refused to work. Every query came back with zero locations and a \"stop invalid\" message, even for Central. The culprit was one innocent looking parameter. Stop Finder takes a type_sf field and I'd set it to stop, because I was searching for stops. It turns out type_sf=stop actually means \"the text I'm sending is a stop ID\", so the broker was taking the word Cronulla and trying to look it up as a numeric station identifier. The fix was type_sf=any and then filtering the mixed bag of streets and points of interest down to actual stops. I only caught it because I verified the client against the live API instead of trusting the docs. The mock TfNSW server my Playwright tests drive now mirrors that exact broker behaviour, so this one can never quietly come back.

\n

Daylight saving and the missing hour

\n

Active windows are Sydney local time and my first version of \"when does today's window start\" was simple minute arithmetic from the current moment. An adversarial review pass over the whole codebase caught that the simple version drifts by exactly an hour whenever the calculation straddles the 2am daylight saving changeover, which in the worst case means a whole morning of missed alerts. The fix was to stop doing arithmetic and start constructing the actual Sydney wall time, using Intl with the Australia/Sydney timezone to check the guess and correct it. I reckon DST bugs are the sneakiest kind there is, because the broken code is provably correct for 363 days of the year.

\n

The alert that sent people the wrong way

\n

The \"Towards\" box is meant to be a light direction filter. Say you're leaving Town Hall heading home to Cronulla, you pop Cronulla in and the alert only bothers you about trains actually going your way. The catch is that a train only really tells you its final stop, so a service that passes through your destination on the way to somewhere else doesn't always say your stop's name. The filter would match nothing. My first version handled that by failing open, so when nothing matched it just alerted you about everything, on the theory that too many alerts beats none. That turned out to be genuinely annoying. On a quiet poll with no Cronulla train in sight it would happily ping me about trains heading the complete opposite way, up to Berowra and out to Emu Plains. The fix was to give the filter a memory. The first time a real matching train shows up it quietly notes that this direction does work here. From then on a poll with no match means go quiet, not alert about everything. It only falls back to the noisy everything-goes behaviour for a destination that has never once matched, which is the genuine can't-tell case. I still reckon failing open was the right instinct. It just shouldn't have been what happens every time.

\n

Web push from scratch, byte for byte

\n

The web push libraries everyone reaches for are built for Node and lean on APIs a Worker doesn't have. Rather than fight that, I wrote the push layer from scratch on WebCrypto. That means two RFCs. VAPID (RFC 8292), an ES256 signed JWT that proves to Apple's or Google's push service the message really came from my server. Then the payload encryption (RFC 8291), a chain of ECDH key agreement, HKDF derivations and AES-128-GCM. Hand rolling crypto is nerve-racking because the failure mode is silence, the push service just drops anything malformed and tells you nothing. The saving grace is that the encryption RFC ships a complete worked example with every intermediate value spelled out. So I made the encrypt function accept an injectable random seed and wrote a test that reproduces that worked example byte for byte. When it went green I knew the scariest part of the codebase was actually correct rather than just plausible.

\n\n

The result

\n

I couldn't be happier with how this one came out! The first build took a couple of days with Claude Code alongside me and it's grown up a fair bit since it went live. The caching that keeps it inside the free tier and the fix for those wrong-way alerts both landed after real people started using it, which is the best way to find that sort of thing. My phone buzzes two minutes before the train actually leaves, the alert already knows it's running late and one tap on \"I'm on board\" keeps everything quiet until my next travel day. I don't open the app, I don't refresh anything and I've stopped standing on platforms waiting. It's all covered end to end, Vitest across the schedule and crypto logic and Playwright driving the real Worker against a mock TfNSW upstream, so I can keep tweaking it without holding my breath. It's live at sydneytrainalerts.com, so if you're a Sydney commuter you're welcome to set one up of your own. I've also popped a little blog on there with a few pieces to help people find it.

\n

If you're thinking about doing web push yourself, here's the tip I wish I'd had on day one. Don't test your encryption by firing real notifications and hoping. Make the random inputs injectable and verify your output against the worked example in RFC 8291 first, because the push services silently drop anything malformed and you'll get no clue why. Once that test passes, everything downstream is just plumbing. Happy coding!

" + "content": "

Overview

\n

It's been nothing short of an exceptional first two months at Gyoza Flights! Jumping into the world of aviation has been super interesting and it has shifted my thinking as both an engineer and person. A recent example of this was the other day I was standing at Wynyard Station waiting for a train to Town Hall. My train of thought immediately drifted to origins and destinations. How my destination of Cronulla was all I really cared about and knowing when the next couple of trains would be departing. Sure I could go onto one of the various apps available and check but with my engineering brain I questioned if that was actually necessary. And did it cover what I really wanted? Sometimes I work back in the office, sometimes I leave early. I'm only in the office a few days a week and some of those are fortnightly. So as I stood and waited for my train an idea started to percolate. What if I just had a website where people could just choose an origin and destination, set a lead time, a window to receive the alerts, a type of alert (email or push notification) and then set and forget. Then they'd automatically get these alerts to their device and would be able to proactively determine when they should leave their office to make a train that suits what time they were thinking of leaving on that day. And that is how sydneytrainalerts.com was born.

\n

I build on Cloudflare constantly and my tennis calendar is still quietly doing its thing from a Worker right now, so I had a fair idea of what was sitting there already. Then I remembered the alarm on Durable Objects, where a tiny stateful object can put itself to sleep and wake at an exact moment. That's precisely the shape of \"ping me two minutes before each train\", so that's what I built it on. You pick a station, how much notice you want, the days and window you travel in and whether you want the alert by push or email. From then on it watches the live departures and nudges you before each train you could catch. Once you're on, you tap \"I'm on board\" and it stays quiet until your next window.

\n\"The\n\n

How it works

\n

The data comes from Transport for NSW's Open Data platform. Their Trip Planner API has a Departure Monitor endpoint that hands back the upcoming departures from a stop as plain JSON with the realtime estimates already merged in, so there's no GTFS protobuf wrangling at all. A second endpoint, Stop Finder, powers the station autocomplete on the form. The free tier gives you 60,000 calls a day which is heaps for a personal tool like this.

\n\"The\n

The part I really wanted to get right is the timing. Every alert is its own little Cloudflare Durable Object that watches your station through your travel window, follows the live delays and nudges you before each train you could catch. Once it's pinged you about one it lists the next couple behind it too, so if you miss the 5:12 you already know the 5:22 is coming. Getting that to actually work is the most interesting thing in the whole build, so I've given it its own section below.

\n\"Choosing\n

Schedules are Sydney wall-clock time with travel days, a start and end for the window and even fortnightly days for rosters that alternate. The subscriptions themselves live in a D1 database, which stays the source of truth. A cron fires every 15 minutes purely as a safety net so nothing gets stranded after a deploy. Once you're on your train you tap \"I'm on board\" and it goes quiet for the rest of that window, then picks back up on your next travel day. That pause sits on the manage page for every alert as well as on the notification itself.

\n\"The\n

One thing I had to be careful about is staying inside that free tier once more than a handful of people are using it. TfNSW's free plan also caps you at about 5 calls a second. It starts knocking requests back the moment you push past that. So the departure lookups run through Cloudflare's cache, keyed on the station and the minute. If ten people are all watching Town Hall at 5:31 they mostly land on the same cached answer instead of making ten separate calls. If TfNSW ever does rate-limit me the alerts back off and give it room instead of hammering away. I also sprinkle a bit of random jitter on the wake-ups so a deploy doesn't fire every alert at the same second and trip the limit myself. Since then I've added a counter in D1 that tallies every call that genuinely leaves for TfNSW, keyed on the Sydney day the quota actually resets on rather than UTC. It emails me as I cross 50, 75, 90 and 100% of the daily allowance. I'd rather find out I'm running hot from my inbox in the morning than from a window of alerts that quietly didn't go out. None of it is glamorous but it's what lets one free key quietly handle a proper crowd of commuters.

\n

Delivery is push or email. Push is the default now and that swap was worth doing. An email subscriber costs a send for every matching train against a provider cap I share across both sites, where a push message costs nothing and skips the confirmation email entirely, because granting the browser permission is already proof you own the device. Push started out as the second radio button and unticked, so almost everyone was taking the expensive channel without ever thinking about it. The form still flips itself back to email on any browser where push isn't a single tap, uninstalled iPhone Safari especially, so nobody lands on an Add to Home Screen wall when they were just trying to fill in a form. Email goes through Resend with a manage link in every message so you can pause or cancel from the alert itself. If the push service ever reports a subscription as gone, the alert deactivates itself rather than pushing into the void forever. The frontend is one dependency-free page, no framework, with the autocomplete, a live preview of the alert you're building and a proper install walkthrough for iPhones, because iOS will only deliver web push to a site that's been added to the home screen. It started at about 2,000 lines and it's around 6,500 now, still with exactly one runtime dependency, Hono for the routing.

\n

The thing I never planned on building was a second site. Ferries turn out to be the exact same problem with different nouns, so instead of forking the lot I made the mode a property of the request. The Worker reads the Host header, picks up the config for that brand and serves sydneyferryalerts.com off the same code, the same database and the same deploy as the trains site. That mode then gets stamped onto each subscription, because a Durable Object waking up on an alarm hours later has no request sitting there to read a hostname off. Everything that differs between the two lives in one config object, the brand and the homepage copy and whether you're waiting on a platform or a wharf. A third mode is one more entry in it.

\n\n

The Cool Nerd Part

\n

Right, this is the bit I actually get excited about. Before I committed to building it this way I sat down and properly grilled the design, because \"give every single alert its own little server that never really sleeps\" sounds mad when you first say it out loud. The more I poked at it though the more Durable Objects turned out to be the perfect fit, so let me walk through why.

\n

The timing first. Your train is meant to leave at 5:12 so a two minute warning should fire at 5:10. Then it runs four minutes late and the warning needs to fire at 5:14 instead. The target keeps moving all afternoon. A Durable Object has this thing called an alarm, which is basically a single built-in timer the object can point at any exact moment in the future. Better still, it survives restarts and deploys. So every time the alert wakes up and checks the live departures it works out the new fire time from the latest estimate and re-points its own alarm at it. The 5:12 slips to 5:14 and the alarm slides right along with it. It's an alert that reschedules itself off the delay. A plain cron can't do that. The finest a cron will give you is once a minute, so you'd be waking every minute, pulling every subscription and still landing up to a minute wide of a target that won't sit still.

\n

Then there's the fact that each alert is its own object with its own name. The trick with Durable Objects is that you address one by a string. I just use the subscription's id as that string. So wherever I am in the code, whether it's the subscribe request, the cancel button, the \"I'm on board\" tap or the safety-net cron, I hand it the same id and I'm always talking to the exact same object holding that one alert's state. No lookup table, no routing, no working out which server a subscription lives on. The id is the address. And because each object only ever holds its own alert, one person's alert going sideways can't touch anyone else's.

\n

Here's the one that really sold me though. A Durable Object only ever does one thing at a time. It won't run two requests against the same object at once, it queues them up. That sounds like a small detail but it's the whole reason the tricky part of this app is even doable, which brings me to the messy bit.

\n

A single wake-up isn't instant. The object has to go out over the network to TfNSW for the live departures. Then it has to actually send your push or your email. Both of those take real time. Now picture the alert half way through that, sitting there waiting on the departures to come back. Right at that moment you tap \"cancel\" or \"I'm on board\". Because the object does one thing at a time your tap can't barge in mid-step, but it does get its turn the instant the alert pauses to wait on the network. So it flips the state out from under the rest of the run. The alert wakes back up thinking everything's fine and, if I'm not careful, cheerfully sends you a notification for an alert you just cancelled.

\n

The honest answer to that is a lot of little if-checks. The wake-up loop is peppered with early exits that all ask the same question in different spots: has this been cancelled or paused while I wasn't looking? Bail out if it has. There's one on the very first line before it does anything. There's one straight after the departures come back from TfNSW. There's one right before it sends each notification and another right after. There's even one tucked inside the final save, so if a cancel landed while the alert was mid-flight, the moment it goes to write \"all done, wake me in 45 seconds\" it checks one last time and just doesn't, because arming a fresh timer on an alert you've killed is exactly how you end up haunted by a cancelled alert that won't die. Cancelling also leaves a little durable marker behind, a tombstone, so the safety-net cron can't come past later, spot an alert with no timer set and helpfully resurrect the very thing you just deleted.

\n

It's not the most elegant code I've ever written and there are more of those guards than I'd like. But every single one of them is a real race I could sit and describe to you. And the reason they get to be simple little if-blocks instead of a knot of locks is that one-thing-at-a-time promise. Without Durable Objects I'd be running one big loop over everyone's alerts, all of them fighting over the same database rows with a proper lock bolted on so a cancel and a poll couldn't clobber each other. With a Durable Object the isolation is the lock. Each alert ends up as a little self-winding alarm clock, chasing the train's live delay, costing nothing while it's asleep between your commutes and never fighting anyone for its own state, because it's the only thing that ever touches it.

\n\n

Sticking points

\n

The API that swore every station was invalid

\n

Early on the station search flat out refused to work. Every query came back with zero locations and a \"stop invalid\" message, even for Central. The culprit was one innocent looking parameter. Stop Finder takes a type_sf field and I'd set it to stop, because I was searching for stops. It turns out type_sf=stop actually means \"the text I'm sending is a stop ID\", so the broker was taking the word Cronulla and trying to look it up as a numeric station identifier. The fix was type_sf=any and then filtering the mixed bag of streets and points of interest down to actual stops. I only caught it because I verified the client against the live API instead of trusting the docs. The mock TfNSW server my Playwright tests drive now mirrors that exact broker behaviour, so this one can never quietly come back.

\n

Daylight saving and the missing hour

\n

Active windows are Sydney local time and my first version of \"when does today's window start\" was simple minute arithmetic from the current moment. An adversarial review pass over the whole codebase caught that the simple version drifts by exactly an hour whenever the calculation straddles the 2am daylight saving changeover, which in the worst case means a whole morning of missed alerts. The fix was to stop doing arithmetic and start constructing the actual Sydney wall time, using Intl with the Australia/Sydney timezone to check the guess and correct it. I reckon DST bugs are the sneakiest kind there is, because the broken code is provably correct for 363 days of the year.

\n

The alert that sent people the wrong way

\n

The \"Towards\" box is meant to be a light direction filter. Say you're leaving Town Hall heading home to Cronulla, you pop Cronulla in and the alert only bothers you about trains actually going your way. The catch is that a train only really tells you its final stop, so a service that passes through your destination on the way to somewhere else doesn't always say your stop's name. The filter would match nothing. My first version handled that by failing open, so when nothing matched it just alerted you about everything, on the theory that too many alerts beats none. That turned out to be genuinely annoying. On a quiet poll with no Cronulla train in sight it would happily ping me about trains heading the complete opposite way, up to Berowra and out to Emu Plains. The fix was to give the filter a memory. The first time a real matching train shows up it quietly notes that this direction does work here. From then on a poll with no match means go quiet, not alert about everything. It only falls back to the noisy everything-goes behaviour for a destination that has never once matched, which is the genuine can't-tell case. I still reckon failing open was the right instinct. It just shouldn't have been what happens every time. That felt like the end of it. It really wasn't.

\n

Web push from scratch, byte for byte

\n

The web push libraries everyone reaches for are built for Node and lean on APIs a Worker doesn't have. Rather than fight that, I wrote the push layer from scratch on WebCrypto. That means two RFCs. VAPID (RFC 8292), an ES256 signed JWT that proves to Apple's or Google's push service the message really came from my server. Then the payload encryption (RFC 8291), a chain of ECDH key agreement, HKDF derivations and AES-128-GCM. Hand rolling crypto is nerve-racking because the failure mode is silence, the push service just drops anything malformed and tells you nothing. The saving grace is that the encryption RFC ships a complete worked example with every intermediate value spelled out. So I made the encrypt function accept an injectable random seed and wrote a test that reproduces that worked example byte for byte. When it went green I knew the scariest part of the codebase was actually correct rather than just plausible.

\n\n

The plot twist

\n

So I shipped that direction fix and moved on, pretty pleased with myself. A few weeks later I finally sat down to put a number on how often the filter actually matched, which I'd never once measured. I pulled the real departure board for Town Hall on a Monday afternoon, 80 trains across the shipped default window, then checked it against six destinations a normal person would genuinely type in. Here's the twist. A headsign only ever tells you where a train finishes up. It says nothing at all about the twenty odd stations it calls at on the way there. Only 4 of those 80 trains terminate at Parramatta. 20 of them actually stop at Parramatta. So a Parramatta subscriber was being told about 4 trains a window and never hearing a whisper about the other 16. Hurstville was 5 against 15. My tidy little fail-closed fix wasn't filtering trains out at all, it was silently dropping trains those people could have caught. And because going quiet looks exactly the same as there being nothing to say, they'd never have a reason to suspect it.

\n

The other branch was just as broken in the opposite direction. Redfern and Wynyard never match a headsign from Town Hall, not one of the 80, so the filter never got to learn that the direction works there and sat in the noisy everything-goes branch forever, pinging those subscribers 79 times a window. I widened the measurement out to 56 plausible Sydney destinations and depending where you start from only 30 to 45% of them are ever a headsign. Blacktown, Sutherland, Lidcombe, Bankstown, Museum, Wynyard and Redfern are all completely ordinary places to be heading home to and not one of them is a headsign from Town Hall. Both halves of my filter were wrong and they were wrong for the same reason. The signal was wrong. I'd spent two whole rounds carefully tuning what to do when the match failed. The actual bug was the question I was asking.

\n

So the fix was to stop asking whether the destination is the last stop and start asking the only thing that actually matters. Does this train stop where I'm going? TfNSW's live departure board hands back a gtfsTripId on nearly every service and the static GTFS timetable carries the full calling pattern for every trip, so if those two join I get the real list of stations instead of a guess off the front of the train. They don't join directly, which took a whole investigation to establish. That id isn't a real trip id in any feed TfNSW publishes, not even the combined one. It isn't opaque either though. The route part of it reconstructs exactly to a route_id in the complete feed. Route plus origin station plus departure minute then pins the trip right down. 18 out of 18 live departures reconciled on the first proper run. That was a very good afternoon.

\n

None of that fits on a Worker. The complete feed is 293 MB zipped and 1.45 GB extracted against a 128 MB isolate, so the heavy lifting runs on my laptop instead. A script pulls both feeds down, works out the onward calling pattern for every trip and boils the whole network into one small pattern shard per origin station, then publishes them into Workers KV. The first build came out at 247 stations and 18,090 trips indexed for about 7 MB all up. At alert time the Durable Object reads only its own station's shard, looks each departure up by route and minute and keeps the train if the destination is somewhere in what's left of its stop list. Everything hard gets resolved once, on the laptop, in code I can write proper tests against. The Worker ends up holding exactly one idea. The stations this service calls at after this one.

\n

The genuinely nasty bit was through-running. Sydney Trains sends a lot of services straight through the City and the static timetable models the approach leg as a trip that terminates at Central, while the live board is already advertising the eventual destination. Read that literally and you get a false negative, which is worse than the thing I was replacing. GTFS has a block_id that links the trips worked by the same physical train. Sure enough the continuation leg leaves Central exactly one second after the approach leg arrives. Stitch the two and a trip that looked like it stopped at Central becomes the real 49 stop run. 1,671 of 1,671 Central-terminating trips on those lines had a block mate waiting for them. Without the stitching Cronulla would have gone from 5 alerts to 16, so it was required rather than a nice to have. I keyed the block on the service id as well as the block id too, because Sydney Trains reuses block ids across weekday and Saturday patterns whose join times happen to line up. That would have stamped a weekday timetable onto a Saturday train. A confident wrong yes, which is the worst kind, because nothing at runtime can catch it.

\n

The rule I care about most in this whole thing is that absence is never a no. Destination missing from the shard, a schema version I don't recognise, the published dates running out, a KV read timing out, every one of those returns don't know and falls back to the old headsign matcher rather than deciding no. A wrong no is silent permanent non-delivery and that's the only failure in here a user can't recover from on their own. Where it sits today is that the shards are built and live in KV, the matcher is merged and sitting inert and the last pull request is waiting on one acceptance run in a real weekday peak window before I flip it on. I've got a scheduled job on my laptop taking that measurement at 16:52 every weekday, because I'm hardly ever at a keyboard at exactly the right minute on exactly the right day. Ferries stay on the headsign path for now. Their wharf ids don't map into the ferry GTFS feed at all, which is a whole separate rabbit hole for another week.

\n\n

The result

\n

I couldn't be happier with how this one came out! The first build took a couple of days with Claude Code alongside me and it's grown up a fair bit since it went live. The caching that keeps it inside the free tier, the ferry site and the whole stopping-pattern rework all landed after real people started using it, which is by far the best way to find that sort of thing. My phone buzzes two minutes before the train actually leaves, the alert already knows it's running late and one tap on \"I'm on board\" keeps everything quiet until my next travel day. I don't open the app, I don't refresh anything and I've stopped standing on platforms waiting. It's all covered end to end, Vitest across the schedule and crypto logic and Playwright driving the real Worker against a mock TfNSW upstream, so I can keep tweaking it without holding my breath. It's live at sydneytrainalerts.com, so if you're a Sydney commuter you're welcome to set one up of your own. There's a blog on each site too, nine posts apiece, to help people find them.

\n

Two things I'd pass on from this one. If you've built yourself a heuristic and you're onto your second round of tuning what it should do when it misses, stop and go measure how often the thing actually hits. Mine was landing 30 to 45% of the time and I'd never checked it once. Then if you're thinking about doing web push yourself, here's the tip I wish I'd had on day one. Don't test your encryption by firing real notifications and hoping. Make the random inputs injectable and verify your output against the worked example in RFC 8291 first, because the push services silently drop anything malformed and you'll get no clue why. Once that test passes, everything downstream is just plumbing. Happy coding!

" }, { "slug": "album-tracker-cli", From c8264b67033a0e79aa78e481a6c4f2e909542e45 Mon Sep 17 00:00:00 2001 From: Daniel Workman Date: Wed, 12 Aug 2026 14:54:09 +1000 Subject: [PATCH 09/11] Add "Getting it off my laptop" to the Train Alerts writeup The plot twist now ends with the acceptance gate passing (Town Hall 80/80, Cronulla 5) and the filter going live. The new section covers the 8-day shard expiry incident, the Cloudflare Container daily rebuild and why it beat GitHub Actions, the two-feed desync the assertion gate caught on its first run, the ops emails and the admin rebuild route that proved the happy path in 67 seconds. Tech list gains Cloudflare Containers and Docker. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01KMJ1S4ND1NBWaEwjC3pgpm --- src/data/projects.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/projects.json b/src/data/projects.json index 22d7b10..4b03321 100644 --- a/src/data/projects.json +++ b/src/data/projects.json @@ -5,10 +5,10 @@ "title": "Train Alerts: a nudge before every Sydney train", "company": "", "description": "A departure alert service for Sydney trains built on Cloudflare Workers and Durable Objects. Pick a station, a lead time and your travel window and it pings you before each train actually leaves, following the live delays, with an \"I'm on board\" tap that keeps the rest of the window quiet.", - "tech": ["TypeScript", "Cloudflare Workers", "Durable Objects", "Cloudflare D1", "Workers KV", "Cache API", "Hono", "Web Push / VAPID", "WebCrypto", "Resend", "TfNSW Open Data", "GTFS", "Vitest", "Playwright"], + "tech": ["TypeScript", "Cloudflare Workers", "Durable Objects", "Cloudflare D1", "Workers KV", "Cloudflare Containers", "Docker", "Cache API", "Hono", "Web Push / VAPID", "WebCrypto", "Resend", "TfNSW Open Data", "GTFS", "Vitest", "Playwright"], "period": "July 2026 — August 2026", "order": 1, - "content": "

Overview

\n

It's been nothing short of an exceptional first two months at Gyoza Flights! Jumping into the world of aviation has been super interesting and it has shifted my thinking as both an engineer and person. A recent example of this was the other day I was standing at Wynyard Station waiting for a train to Town Hall. My train of thought immediately drifted to origins and destinations. How my destination of Cronulla was all I really cared about and knowing when the next couple of trains would be departing. Sure I could go onto one of the various apps available and check but with my engineering brain I questioned if that was actually necessary. And did it cover what I really wanted? Sometimes I work back in the office, sometimes I leave early. I'm only in the office a few days a week and some of those are fortnightly. So as I stood and waited for my train an idea started to percolate. What if I just had a website where people could just choose an origin and destination, set a lead time, a window to receive the alerts, a type of alert (email or push notification) and then set and forget. Then they'd automatically get these alerts to their device and would be able to proactively determine when they should leave their office to make a train that suits what time they were thinking of leaving on that day. And that is how sydneytrainalerts.com was born.

\n

I build on Cloudflare constantly and my tennis calendar is still quietly doing its thing from a Worker right now, so I had a fair idea of what was sitting there already. Then I remembered the alarm on Durable Objects, where a tiny stateful object can put itself to sleep and wake at an exact moment. That's precisely the shape of \"ping me two minutes before each train\", so that's what I built it on. You pick a station, how much notice you want, the days and window you travel in and whether you want the alert by push or email. From then on it watches the live departures and nudges you before each train you could catch. Once you're on, you tap \"I'm on board\" and it stays quiet until your next window.

\n\"The\n\n

How it works

\n

The data comes from Transport for NSW's Open Data platform. Their Trip Planner API has a Departure Monitor endpoint that hands back the upcoming departures from a stop as plain JSON with the realtime estimates already merged in, so there's no GTFS protobuf wrangling at all. A second endpoint, Stop Finder, powers the station autocomplete on the form. The free tier gives you 60,000 calls a day which is heaps for a personal tool like this.

\n\"The\n

The part I really wanted to get right is the timing. Every alert is its own little Cloudflare Durable Object that watches your station through your travel window, follows the live delays and nudges you before each train you could catch. Once it's pinged you about one it lists the next couple behind it too, so if you miss the 5:12 you already know the 5:22 is coming. Getting that to actually work is the most interesting thing in the whole build, so I've given it its own section below.

\n\"Choosing\n

Schedules are Sydney wall-clock time with travel days, a start and end for the window and even fortnightly days for rosters that alternate. The subscriptions themselves live in a D1 database, which stays the source of truth. A cron fires every 15 minutes purely as a safety net so nothing gets stranded after a deploy. Once you're on your train you tap \"I'm on board\" and it goes quiet for the rest of that window, then picks back up on your next travel day. That pause sits on the manage page for every alert as well as on the notification itself.

\n\"The\n

One thing I had to be careful about is staying inside that free tier once more than a handful of people are using it. TfNSW's free plan also caps you at about 5 calls a second. It starts knocking requests back the moment you push past that. So the departure lookups run through Cloudflare's cache, keyed on the station and the minute. If ten people are all watching Town Hall at 5:31 they mostly land on the same cached answer instead of making ten separate calls. If TfNSW ever does rate-limit me the alerts back off and give it room instead of hammering away. I also sprinkle a bit of random jitter on the wake-ups so a deploy doesn't fire every alert at the same second and trip the limit myself. Since then I've added a counter in D1 that tallies every call that genuinely leaves for TfNSW, keyed on the Sydney day the quota actually resets on rather than UTC. It emails me as I cross 50, 75, 90 and 100% of the daily allowance. I'd rather find out I'm running hot from my inbox in the morning than from a window of alerts that quietly didn't go out. None of it is glamorous but it's what lets one free key quietly handle a proper crowd of commuters.

\n

Delivery is push or email. Push is the default now and that swap was worth doing. An email subscriber costs a send for every matching train against a provider cap I share across both sites, where a push message costs nothing and skips the confirmation email entirely, because granting the browser permission is already proof you own the device. Push started out as the second radio button and unticked, so almost everyone was taking the expensive channel without ever thinking about it. The form still flips itself back to email on any browser where push isn't a single tap, uninstalled iPhone Safari especially, so nobody lands on an Add to Home Screen wall when they were just trying to fill in a form. Email goes through Resend with a manage link in every message so you can pause or cancel from the alert itself. If the push service ever reports a subscription as gone, the alert deactivates itself rather than pushing into the void forever. The frontend is one dependency-free page, no framework, with the autocomplete, a live preview of the alert you're building and a proper install walkthrough for iPhones, because iOS will only deliver web push to a site that's been added to the home screen. It started at about 2,000 lines and it's around 6,500 now, still with exactly one runtime dependency, Hono for the routing.

\n

The thing I never planned on building was a second site. Ferries turn out to be the exact same problem with different nouns, so instead of forking the lot I made the mode a property of the request. The Worker reads the Host header, picks up the config for that brand and serves sydneyferryalerts.com off the same code, the same database and the same deploy as the trains site. That mode then gets stamped onto each subscription, because a Durable Object waking up on an alarm hours later has no request sitting there to read a hostname off. Everything that differs between the two lives in one config object, the brand and the homepage copy and whether you're waiting on a platform or a wharf. A third mode is one more entry in it.

\n\n

The Cool Nerd Part

\n

Right, this is the bit I actually get excited about. Before I committed to building it this way I sat down and properly grilled the design, because \"give every single alert its own little server that never really sleeps\" sounds mad when you first say it out loud. The more I poked at it though the more Durable Objects turned out to be the perfect fit, so let me walk through why.

\n

The timing first. Your train is meant to leave at 5:12 so a two minute warning should fire at 5:10. Then it runs four minutes late and the warning needs to fire at 5:14 instead. The target keeps moving all afternoon. A Durable Object has this thing called an alarm, which is basically a single built-in timer the object can point at any exact moment in the future. Better still, it survives restarts and deploys. So every time the alert wakes up and checks the live departures it works out the new fire time from the latest estimate and re-points its own alarm at it. The 5:12 slips to 5:14 and the alarm slides right along with it. It's an alert that reschedules itself off the delay. A plain cron can't do that. The finest a cron will give you is once a minute, so you'd be waking every minute, pulling every subscription and still landing up to a minute wide of a target that won't sit still.

\n

Then there's the fact that each alert is its own object with its own name. The trick with Durable Objects is that you address one by a string. I just use the subscription's id as that string. So wherever I am in the code, whether it's the subscribe request, the cancel button, the \"I'm on board\" tap or the safety-net cron, I hand it the same id and I'm always talking to the exact same object holding that one alert's state. No lookup table, no routing, no working out which server a subscription lives on. The id is the address. And because each object only ever holds its own alert, one person's alert going sideways can't touch anyone else's.

\n

Here's the one that really sold me though. A Durable Object only ever does one thing at a time. It won't run two requests against the same object at once, it queues them up. That sounds like a small detail but it's the whole reason the tricky part of this app is even doable, which brings me to the messy bit.

\n

A single wake-up isn't instant. The object has to go out over the network to TfNSW for the live departures. Then it has to actually send your push or your email. Both of those take real time. Now picture the alert half way through that, sitting there waiting on the departures to come back. Right at that moment you tap \"cancel\" or \"I'm on board\". Because the object does one thing at a time your tap can't barge in mid-step, but it does get its turn the instant the alert pauses to wait on the network. So it flips the state out from under the rest of the run. The alert wakes back up thinking everything's fine and, if I'm not careful, cheerfully sends you a notification for an alert you just cancelled.

\n

The honest answer to that is a lot of little if-checks. The wake-up loop is peppered with early exits that all ask the same question in different spots: has this been cancelled or paused while I wasn't looking? Bail out if it has. There's one on the very first line before it does anything. There's one straight after the departures come back from TfNSW. There's one right before it sends each notification and another right after. There's even one tucked inside the final save, so if a cancel landed while the alert was mid-flight, the moment it goes to write \"all done, wake me in 45 seconds\" it checks one last time and just doesn't, because arming a fresh timer on an alert you've killed is exactly how you end up haunted by a cancelled alert that won't die. Cancelling also leaves a little durable marker behind, a tombstone, so the safety-net cron can't come past later, spot an alert with no timer set and helpfully resurrect the very thing you just deleted.

\n

It's not the most elegant code I've ever written and there are more of those guards than I'd like. But every single one of them is a real race I could sit and describe to you. And the reason they get to be simple little if-blocks instead of a knot of locks is that one-thing-at-a-time promise. Without Durable Objects I'd be running one big loop over everyone's alerts, all of them fighting over the same database rows with a proper lock bolted on so a cancel and a poll couldn't clobber each other. With a Durable Object the isolation is the lock. Each alert ends up as a little self-winding alarm clock, chasing the train's live delay, costing nothing while it's asleep between your commutes and never fighting anyone for its own state, because it's the only thing that ever touches it.

\n\n

Sticking points

\n

The API that swore every station was invalid

\n

Early on the station search flat out refused to work. Every query came back with zero locations and a \"stop invalid\" message, even for Central. The culprit was one innocent looking parameter. Stop Finder takes a type_sf field and I'd set it to stop, because I was searching for stops. It turns out type_sf=stop actually means \"the text I'm sending is a stop ID\", so the broker was taking the word Cronulla and trying to look it up as a numeric station identifier. The fix was type_sf=any and then filtering the mixed bag of streets and points of interest down to actual stops. I only caught it because I verified the client against the live API instead of trusting the docs. The mock TfNSW server my Playwright tests drive now mirrors that exact broker behaviour, so this one can never quietly come back.

\n

Daylight saving and the missing hour

\n

Active windows are Sydney local time and my first version of \"when does today's window start\" was simple minute arithmetic from the current moment. An adversarial review pass over the whole codebase caught that the simple version drifts by exactly an hour whenever the calculation straddles the 2am daylight saving changeover, which in the worst case means a whole morning of missed alerts. The fix was to stop doing arithmetic and start constructing the actual Sydney wall time, using Intl with the Australia/Sydney timezone to check the guess and correct it. I reckon DST bugs are the sneakiest kind there is, because the broken code is provably correct for 363 days of the year.

\n

The alert that sent people the wrong way

\n

The \"Towards\" box is meant to be a light direction filter. Say you're leaving Town Hall heading home to Cronulla, you pop Cronulla in and the alert only bothers you about trains actually going your way. The catch is that a train only really tells you its final stop, so a service that passes through your destination on the way to somewhere else doesn't always say your stop's name. The filter would match nothing. My first version handled that by failing open, so when nothing matched it just alerted you about everything, on the theory that too many alerts beats none. That turned out to be genuinely annoying. On a quiet poll with no Cronulla train in sight it would happily ping me about trains heading the complete opposite way, up to Berowra and out to Emu Plains. The fix was to give the filter a memory. The first time a real matching train shows up it quietly notes that this direction does work here. From then on a poll with no match means go quiet, not alert about everything. It only falls back to the noisy everything-goes behaviour for a destination that has never once matched, which is the genuine can't-tell case. I still reckon failing open was the right instinct. It just shouldn't have been what happens every time. That felt like the end of it. It really wasn't.

\n

Web push from scratch, byte for byte

\n

The web push libraries everyone reaches for are built for Node and lean on APIs a Worker doesn't have. Rather than fight that, I wrote the push layer from scratch on WebCrypto. That means two RFCs. VAPID (RFC 8292), an ES256 signed JWT that proves to Apple's or Google's push service the message really came from my server. Then the payload encryption (RFC 8291), a chain of ECDH key agreement, HKDF derivations and AES-128-GCM. Hand rolling crypto is nerve-racking because the failure mode is silence, the push service just drops anything malformed and tells you nothing. The saving grace is that the encryption RFC ships a complete worked example with every intermediate value spelled out. So I made the encrypt function accept an injectable random seed and wrote a test that reproduces that worked example byte for byte. When it went green I knew the scariest part of the codebase was actually correct rather than just plausible.

\n\n

The plot twist

\n

So I shipped that direction fix and moved on, pretty pleased with myself. A few weeks later I finally sat down to put a number on how often the filter actually matched, which I'd never once measured. I pulled the real departure board for Town Hall on a Monday afternoon, 80 trains across the shipped default window, then checked it against six destinations a normal person would genuinely type in. Here's the twist. A headsign only ever tells you where a train finishes up. It says nothing at all about the twenty odd stations it calls at on the way there. Only 4 of those 80 trains terminate at Parramatta. 20 of them actually stop at Parramatta. So a Parramatta subscriber was being told about 4 trains a window and never hearing a whisper about the other 16. Hurstville was 5 against 15. My tidy little fail-closed fix wasn't filtering trains out at all, it was silently dropping trains those people could have caught. And because going quiet looks exactly the same as there being nothing to say, they'd never have a reason to suspect it.

\n

The other branch was just as broken in the opposite direction. Redfern and Wynyard never match a headsign from Town Hall, not one of the 80, so the filter never got to learn that the direction works there and sat in the noisy everything-goes branch forever, pinging those subscribers 79 times a window. I widened the measurement out to 56 plausible Sydney destinations and depending where you start from only 30 to 45% of them are ever a headsign. Blacktown, Sutherland, Lidcombe, Bankstown, Museum, Wynyard and Redfern are all completely ordinary places to be heading home to and not one of them is a headsign from Town Hall. Both halves of my filter were wrong and they were wrong for the same reason. The signal was wrong. I'd spent two whole rounds carefully tuning what to do when the match failed. The actual bug was the question I was asking.

\n

So the fix was to stop asking whether the destination is the last stop and start asking the only thing that actually matters. Does this train stop where I'm going? TfNSW's live departure board hands back a gtfsTripId on nearly every service and the static GTFS timetable carries the full calling pattern for every trip, so if those two join I get the real list of stations instead of a guess off the front of the train. They don't join directly, which took a whole investigation to establish. That id isn't a real trip id in any feed TfNSW publishes, not even the combined one. It isn't opaque either though. The route part of it reconstructs exactly to a route_id in the complete feed. Route plus origin station plus departure minute then pins the trip right down. 18 out of 18 live departures reconciled on the first proper run. That was a very good afternoon.

\n

None of that fits on a Worker. The complete feed is 293 MB zipped and 1.45 GB extracted against a 128 MB isolate, so the heavy lifting runs on my laptop instead. A script pulls both feeds down, works out the onward calling pattern for every trip and boils the whole network into one small pattern shard per origin station, then publishes them into Workers KV. The first build came out at 247 stations and 18,090 trips indexed for about 7 MB all up. At alert time the Durable Object reads only its own station's shard, looks each departure up by route and minute and keeps the train if the destination is somewhere in what's left of its stop list. Everything hard gets resolved once, on the laptop, in code I can write proper tests against. The Worker ends up holding exactly one idea. The stations this service calls at after this one.

\n

The genuinely nasty bit was through-running. Sydney Trains sends a lot of services straight through the City and the static timetable models the approach leg as a trip that terminates at Central, while the live board is already advertising the eventual destination. Read that literally and you get a false negative, which is worse than the thing I was replacing. GTFS has a block_id that links the trips worked by the same physical train. Sure enough the continuation leg leaves Central exactly one second after the approach leg arrives. Stitch the two and a trip that looked like it stopped at Central becomes the real 49 stop run. 1,671 of 1,671 Central-terminating trips on those lines had a block mate waiting for them. Without the stitching Cronulla would have gone from 5 alerts to 16, so it was required rather than a nice to have. I keyed the block on the service id as well as the block id too, because Sydney Trains reuses block ids across weekday and Saturday patterns whose join times happen to line up. That would have stamped a weekday timetable onto a Saturday train. A confident wrong yes, which is the worst kind, because nothing at runtime can catch it.

\n

The rule I care about most in this whole thing is that absence is never a no. Destination missing from the shard, a schema version I don't recognise, the published dates running out, a KV read timing out, every one of those returns don't know and falls back to the old headsign matcher rather than deciding no. A wrong no is silent permanent non-delivery and that's the only failure in here a user can't recover from on their own. Where it sits today is that the shards are built and live in KV, the matcher is merged and sitting inert and the last pull request is waiting on one acceptance run in a real weekday peak window before I flip it on. I've got a scheduled job on my laptop taking that measurement at 16:52 every weekday, because I'm hardly ever at a keyboard at exactly the right minute on exactly the right day. Ferries stay on the headsign path for now. Their wharf ids don't map into the ferry GTFS feed at all, which is a whole separate rabbit hole for another week.

\n\n

The result

\n

I couldn't be happier with how this one came out! The first build took a couple of days with Claude Code alongside me and it's grown up a fair bit since it went live. The caching that keeps it inside the free tier, the ferry site and the whole stopping-pattern rework all landed after real people started using it, which is by far the best way to find that sort of thing. My phone buzzes two minutes before the train actually leaves, the alert already knows it's running late and one tap on \"I'm on board\" keeps everything quiet until my next travel day. I don't open the app, I don't refresh anything and I've stopped standing on platforms waiting. It's all covered end to end, Vitest across the schedule and crypto logic and Playwright driving the real Worker against a mock TfNSW upstream, so I can keep tweaking it without holding my breath. It's live at sydneytrainalerts.com, so if you're a Sydney commuter you're welcome to set one up of your own. There's a blog on each site too, nine posts apiece, to help people find them.

\n

Two things I'd pass on from this one. If you've built yourself a heuristic and you're onto your second round of tuning what it should do when it misses, stop and go measure how often the thing actually hits. Mine was landing 30 to 45% of the time and I'd never checked it once. Then if you're thinking about doing web push yourself, here's the tip I wish I'd had on day one. Don't test your encryption by firing real notifications and hoping. Make the random inputs injectable and verify your output against the worked example in RFC 8291 first, because the push services silently drop anything malformed and you'll get no clue why. Once that test passes, everything downstream is just plumbing. Happy coding!

" + "content": "

Overview

\n

It's been nothing short of an exceptional first two months at Gyoza Flights! Jumping into the world of aviation has been super interesting and it has shifted my thinking as both an engineer and person. A recent example of this was the other day I was standing at Wynyard Station waiting for a train to Town Hall. My train of thought immediately drifted to origins and destinations. How my destination of Cronulla was all I really cared about and knowing when the next couple of trains would be departing. Sure I could go onto one of the various apps available and check but with my engineering brain I questioned if that was actually necessary. And did it cover what I really wanted? Sometimes I work back in the office, sometimes I leave early. I'm only in the office a few days a week and some of those are fortnightly. So as I stood and waited for my train an idea started to percolate. What if I just had a website where people could just choose an origin and destination, set a lead time, a window to receive the alerts, a type of alert (email or push notification) and then set and forget. Then they'd automatically get these alerts to their device and would be able to proactively determine when they should leave their office to make a train that suits what time they were thinking of leaving on that day. And that is how sydneytrainalerts.com was born.

\n

I build on Cloudflare constantly and my tennis calendar is still quietly doing its thing from a Worker right now, so I had a fair idea of what was sitting there already. Then I remembered the alarm on Durable Objects, where a tiny stateful object can put itself to sleep and wake at an exact moment. That's precisely the shape of \"ping me two minutes before each train\", so that's what I built it on. You pick a station, how much notice you want, the days and window you travel in and whether you want the alert by push or email. From then on it watches the live departures and nudges you before each train you could catch. Once you're on, you tap \"I'm on board\" and it stays quiet until your next window.

\n\"The\n\n

How it works

\n

The data comes from Transport for NSW's Open Data platform. Their Trip Planner API has a Departure Monitor endpoint that hands back the upcoming departures from a stop as plain JSON with the realtime estimates already merged in, so there's no GTFS protobuf wrangling at all. A second endpoint, Stop Finder, powers the station autocomplete on the form. The free tier gives you 60,000 calls a day which is heaps for a personal tool like this.

\n\"The\n

The part I really wanted to get right is the timing. Every alert is its own little Cloudflare Durable Object that watches your station through your travel window, follows the live delays and nudges you before each train you could catch. Once it's pinged you about one it lists the next couple behind it too, so if you miss the 5:12 you already know the 5:22 is coming. Getting that to actually work is the most interesting thing in the whole build, so I've given it its own section below.

\n\"Choosing\n

Schedules are Sydney wall-clock time with travel days, a start and end for the window and even fortnightly days for rosters that alternate. The subscriptions themselves live in a D1 database, which stays the source of truth. A cron fires every 15 minutes purely as a safety net so nothing gets stranded after a deploy. Once you're on your train you tap \"I'm on board\" and it goes quiet for the rest of that window, then picks back up on your next travel day. That pause sits on the manage page for every alert as well as on the notification itself.

\n\"The\n

One thing I had to be careful about is staying inside that free tier once more than a handful of people are using it. TfNSW's free plan also caps you at about 5 calls a second. It starts knocking requests back the moment you push past that. So the departure lookups run through Cloudflare's cache, keyed on the station and the minute. If ten people are all watching Town Hall at 5:31 they mostly land on the same cached answer instead of making ten separate calls. If TfNSW ever does rate-limit me the alerts back off and give it room instead of hammering away. I also sprinkle a bit of random jitter on the wake-ups so a deploy doesn't fire every alert at the same second and trip the limit myself. Since then I've added a counter in D1 that tallies every call that genuinely leaves for TfNSW, keyed on the Sydney day the quota actually resets on rather than UTC. It emails me as I cross 50, 75, 90 and 100% of the daily allowance. I'd rather find out I'm running hot from my inbox in the morning than from a window of alerts that quietly didn't go out. None of it is glamorous but it's what lets one free key quietly handle a proper crowd of commuters.

\n

Delivery is push or email. Push is the default now and that swap was worth doing. An email subscriber costs a send for every matching train against a provider cap I share across both sites, where a push message costs nothing and skips the confirmation email entirely, because granting the browser permission is already proof you own the device. Push started out as the second radio button and unticked, so almost everyone was taking the expensive channel without ever thinking about it. The form still flips itself back to email on any browser where push isn't a single tap, uninstalled iPhone Safari especially, so nobody lands on an Add to Home Screen wall when they were just trying to fill in a form. Email goes through Resend with a manage link in every message so you can pause or cancel from the alert itself. If the push service ever reports a subscription as gone, the alert deactivates itself rather than pushing into the void forever. The frontend is one dependency-free page, no framework, with the autocomplete, a live preview of the alert you're building and a proper install walkthrough for iPhones, because iOS will only deliver web push to a site that's been added to the home screen. It started at about 2,000 lines and it's around 6,500 now, still with exactly one runtime dependency, Hono for the routing.

\n

The thing I never planned on building was a second site. Ferries turn out to be the exact same problem with different nouns, so instead of forking the lot I made the mode a property of the request. The Worker reads the Host header, picks up the config for that brand and serves sydneyferryalerts.com off the same code, the same database and the same deploy as the trains site. That mode then gets stamped onto each subscription, because a Durable Object waking up on an alarm hours later has no request sitting there to read a hostname off. Everything that differs between the two lives in one config object, the brand and the homepage copy and whether you're waiting on a platform or a wharf. A third mode is one more entry in it.

\n\n

The Cool Nerd Part

\n

Right, this is the bit I actually get excited about. Before I committed to building it this way I sat down and properly grilled the design, because \"give every single alert its own little server that never really sleeps\" sounds mad when you first say it out loud. The more I poked at it though the more Durable Objects turned out to be the perfect fit, so let me walk through why.

\n

The timing first. Your train is meant to leave at 5:12 so a two minute warning should fire at 5:10. Then it runs four minutes late and the warning needs to fire at 5:14 instead. The target keeps moving all afternoon. A Durable Object has this thing called an alarm, which is basically a single built-in timer the object can point at any exact moment in the future. Better still, it survives restarts and deploys. So every time the alert wakes up and checks the live departures it works out the new fire time from the latest estimate and re-points its own alarm at it. The 5:12 slips to 5:14 and the alarm slides right along with it. It's an alert that reschedules itself off the delay. A plain cron can't do that. The finest a cron will give you is once a minute, so you'd be waking every minute, pulling every subscription and still landing up to a minute wide of a target that won't sit still.

\n

Then there's the fact that each alert is its own object with its own name. The trick with Durable Objects is that you address one by a string. I just use the subscription's id as that string. So wherever I am in the code, whether it's the subscribe request, the cancel button, the \"I'm on board\" tap or the safety-net cron, I hand it the same id and I'm always talking to the exact same object holding that one alert's state. No lookup table, no routing, no working out which server a subscription lives on. The id is the address. And because each object only ever holds its own alert, one person's alert going sideways can't touch anyone else's.

\n

Here's the one that really sold me though. A Durable Object only ever does one thing at a time. It won't run two requests against the same object at once, it queues them up. That sounds like a small detail but it's the whole reason the tricky part of this app is even doable, which brings me to the messy bit.

\n

A single wake-up isn't instant. The object has to go out over the network to TfNSW for the live departures. Then it has to actually send your push or your email. Both of those take real time. Now picture the alert half way through that, sitting there waiting on the departures to come back. Right at that moment you tap \"cancel\" or \"I'm on board\". Because the object does one thing at a time your tap can't barge in mid-step, but it does get its turn the instant the alert pauses to wait on the network. So it flips the state out from under the rest of the run. The alert wakes back up thinking everything's fine and, if I'm not careful, cheerfully sends you a notification for an alert you just cancelled.

\n

The honest answer to that is a lot of little if-checks. The wake-up loop is peppered with early exits that all ask the same question in different spots: has this been cancelled or paused while I wasn't looking? Bail out if it has. There's one on the very first line before it does anything. There's one straight after the departures come back from TfNSW. There's one right before it sends each notification and another right after. There's even one tucked inside the final save, so if a cancel landed while the alert was mid-flight, the moment it goes to write \"all done, wake me in 45 seconds\" it checks one last time and just doesn't, because arming a fresh timer on an alert you've killed is exactly how you end up haunted by a cancelled alert that won't die. Cancelling also leaves a little durable marker behind, a tombstone, so the safety-net cron can't come past later, spot an alert with no timer set and helpfully resurrect the very thing you just deleted.

\n

It's not the most elegant code I've ever written and there are more of those guards than I'd like. But every single one of them is a real race I could sit and describe to you. And the reason they get to be simple little if-blocks instead of a knot of locks is that one-thing-at-a-time promise. Without Durable Objects I'd be running one big loop over everyone's alerts, all of them fighting over the same database rows with a proper lock bolted on so a cancel and a poll couldn't clobber each other. With a Durable Object the isolation is the lock. Each alert ends up as a little self-winding alarm clock, chasing the train's live delay, costing nothing while it's asleep between your commutes and never fighting anyone for its own state, because it's the only thing that ever touches it.

\n\n

Sticking points

\n

The API that swore every station was invalid

\n

Early on the station search flat out refused to work. Every query came back with zero locations and a \"stop invalid\" message, even for Central. The culprit was one innocent looking parameter. Stop Finder takes a type_sf field and I'd set it to stop, because I was searching for stops. It turns out type_sf=stop actually means \"the text I'm sending is a stop ID\", so the broker was taking the word Cronulla and trying to look it up as a numeric station identifier. The fix was type_sf=any and then filtering the mixed bag of streets and points of interest down to actual stops. I only caught it because I verified the client against the live API instead of trusting the docs. The mock TfNSW server my Playwright tests drive now mirrors that exact broker behaviour, so this one can never quietly come back.

\n

Daylight saving and the missing hour

\n

Active windows are Sydney local time and my first version of \"when does today's window start\" was simple minute arithmetic from the current moment. An adversarial review pass over the whole codebase caught that the simple version drifts by exactly an hour whenever the calculation straddles the 2am daylight saving changeover, which in the worst case means a whole morning of missed alerts. The fix was to stop doing arithmetic and start constructing the actual Sydney wall time, using Intl with the Australia/Sydney timezone to check the guess and correct it. I reckon DST bugs are the sneakiest kind there is, because the broken code is provably correct for 363 days of the year.

\n

The alert that sent people the wrong way

\n

The \"Towards\" box is meant to be a light direction filter. Say you're leaving Town Hall heading home to Cronulla, you pop Cronulla in and the alert only bothers you about trains actually going your way. The catch is that a train only really tells you its final stop, so a service that passes through your destination on the way to somewhere else doesn't always say your stop's name. The filter would match nothing. My first version handled that by failing open, so when nothing matched it just alerted you about everything, on the theory that too many alerts beats none. That turned out to be genuinely annoying. On a quiet poll with no Cronulla train in sight it would happily ping me about trains heading the complete opposite way, up to Berowra and out to Emu Plains. The fix was to give the filter a memory. The first time a real matching train shows up it quietly notes that this direction does work here. From then on a poll with no match means go quiet, not alert about everything. It only falls back to the noisy everything-goes behaviour for a destination that has never once matched, which is the genuine can't-tell case. I still reckon failing open was the right instinct. It just shouldn't have been what happens every time. That felt like the end of it. It really wasn't.

\n

Web push from scratch, byte for byte

\n

The web push libraries everyone reaches for are built for Node and lean on APIs a Worker doesn't have. Rather than fight that, I wrote the push layer from scratch on WebCrypto. That means two RFCs. VAPID (RFC 8292), an ES256 signed JWT that proves to Apple's or Google's push service the message really came from my server. Then the payload encryption (RFC 8291), a chain of ECDH key agreement, HKDF derivations and AES-128-GCM. Hand rolling crypto is nerve-racking because the failure mode is silence, the push service just drops anything malformed and tells you nothing. The saving grace is that the encryption RFC ships a complete worked example with every intermediate value spelled out. So I made the encrypt function accept an injectable random seed and wrote a test that reproduces that worked example byte for byte. When it went green I knew the scariest part of the codebase was actually correct rather than just plausible.

\n\n

The plot twist

\n

So I shipped that direction fix and moved on, pretty pleased with myself. A few weeks later I finally sat down to put a number on how often the filter actually matched, which I'd never once measured. I pulled the real departure board for Town Hall on a Monday afternoon, 80 trains across the shipped default window, then checked it against six destinations a normal person would genuinely type in. Here's the twist. A headsign only ever tells you where a train finishes up. It says nothing at all about the twenty odd stations it calls at on the way there. Only 4 of those 80 trains terminate at Parramatta. 20 of them actually stop at Parramatta. So a Parramatta subscriber was being told about 4 trains a window and never hearing a whisper about the other 16. Hurstville was 5 against 15. My tidy little fail-closed fix wasn't filtering trains out at all, it was silently dropping trains those people could have caught. And because going quiet looks exactly the same as there being nothing to say, they'd never have a reason to suspect it.

\n

The other branch was just as broken in the opposite direction. Redfern and Wynyard never match a headsign from Town Hall, not one of the 80, so the filter never got to learn that the direction works there and sat in the noisy everything-goes branch forever, pinging those subscribers 79 times a window. I widened the measurement out to 56 plausible Sydney destinations and depending where you start from only 30 to 45% of them are ever a headsign. Blacktown, Sutherland, Lidcombe, Bankstown, Museum, Wynyard and Redfern are all completely ordinary places to be heading home to and not one of them is a headsign from Town Hall. Both halves of my filter were wrong and they were wrong for the same reason. The signal was wrong. I'd spent two whole rounds carefully tuning what to do when the match failed. The actual bug was the question I was asking.

\n

So the fix was to stop asking whether the destination is the last stop and start asking the only thing that actually matters. Does this train stop where I'm going? TfNSW's live departure board hands back a gtfsTripId on nearly every service and the static GTFS timetable carries the full calling pattern for every trip, so if those two join I get the real list of stations instead of a guess off the front of the train. They don't join directly, which took a whole investigation to establish. That id isn't a real trip id in any feed TfNSW publishes, not even the combined one. It isn't opaque either though. The route part of it reconstructs exactly to a route_id in the complete feed. Route plus origin station plus departure minute then pins the trip right down. 18 out of 18 live departures reconciled on the first proper run. That was a very good afternoon.

\n

None of that fits on a Worker. The complete feed is 293 MB zipped and 1.45 GB extracted against a 128 MB isolate, so the heavy lifting runs on my laptop instead. A script pulls both feeds down, works out the onward calling pattern for every trip and boils the whole network into one small pattern shard per origin station, then publishes them into Workers KV. The first build came out at 247 stations and 18,090 trips indexed for about 7 MB all up. At alert time the Durable Object reads only its own station's shard, looks each departure up by route and minute and keeps the train if the destination is somewhere in what's left of its stop list. Everything hard gets resolved once, on the laptop, in code I can write proper tests against. The Worker ends up holding exactly one idea. The stations this service calls at after this one.

\n

The genuinely nasty bit was through-running. Sydney Trains sends a lot of services straight through the City and the static timetable models the approach leg as a trip that terminates at Central, while the live board is already advertising the eventual destination. Read that literally and you get a false negative, which is worse than the thing I was replacing. GTFS has a block_id that links the trips worked by the same physical train. Sure enough the continuation leg leaves Central exactly one second after the approach leg arrives. Stitch the two and a trip that looked like it stopped at Central becomes the real 49 stop run. 1,671 of 1,671 Central-terminating trips on those lines had a block mate waiting for them. Without the stitching Cronulla would have gone from 5 alerts to 16, so it was required rather than a nice to have. I keyed the block on the service id as well as the block id too, because Sydney Trains reuses block ids across weekday and Saturday patterns whose join times happen to line up. That would have stamped a weekday timetable onto a Saturday train. A confident wrong yes, which is the worst kind, because nothing at runtime can catch it.

\n

The rule I care about most in this whole thing is that absence is never a no. Destination missing from the shard, a schema version I don't recognise, the published dates running out, a KV read timing out, every one of those returns don't know and falls back to the old headsign matcher rather than deciding no. A wrong no is silent permanent non-delivery and that's the only failure in here a user can't recover from on their own. The acceptance run I was holding out for finally landed in a real weekday peak window, courtesy of a little scheduled job on my laptop taking the measurement at 4:52pm every weekday because I'm hardly ever at a keyboard at exactly the right minute. Town Hall came back with 80 out of 80 departures covered by the shards. My own Cronulla alert landed on 5 trains for the window, right where the earlier measurements said it should be. So I flipped it on. The stopping-pattern filter has been live for every train alert since early August and the rollback story is as boring as I could make it. Deleting the shard keys out of KV turns the whole thing off in seconds, no deploy needed. Ferries stay on the headsign path for now. Their wharf ids don't map into the ferry GTFS feed at all, which is a whole separate rabbit hole for another week.

\n\n

Getting it off my laptop

\n

There was one weak link left in all of this and it was me. Each shard deliberately carries just eight days of timetable, today plus a week, so that a stale build degrades gracefully instead of serving wrong answers forever. The thing that refreshed them though was a script I ran by hand on my laptop. You can probably see where this is going. The build from the 3rd of August covered up to the 10th, so on the 11th every train alert quietly aged out of its shard and spent the whole day back on the old headsign matcher. Nothing broke, which is exactly how I designed it. Honestly that almost made it worse. Alerts kept arriving, just fewer of them. The canary I'd built to spot stale shards was only writing a line into a log I wasn't reading, so production ran thin all day before I rebuilt the shards that evening. I'd shipped a feature with a hand-wound eight day timer on it and the timer had run out exactly on schedule.

\n

So the rebuild had to go somewhere that isn't my laptop. My first thought was GitHub Actions and I went off it for two reasons. It would need a Cloudflare API token with write access to KV sitting in GitHub's secrets, a brand new credential to mint and then worry about forever. On top of that GitHub quietly switches off scheduled workflows when a repo goes inactive, which for a set-and-forget project is exactly the kind of silent stop I was trying to get rid of. Then I remembered Cloudflare had shipped Containers, where you attach a real Docker container to a Worker for the jobs an isolate can't handle. That was the lightbulb moment. The 1.45 GB extract that pushed this build onto my laptop in the first place stops mattering the moment the build gets a proper machine of its own.

\n

Here's the bit I love about how Containers work. You address the container through a Durable Object. The same one-object-one-address idea that runs every alert in this app also runs the build machine. It only ever exists while it's working. A second cron in the same Worker fires once a day at midday and wakes the container. The container pulls both GTFS feeds down, chews through them with the exact same build code my laptop was running and hands the finished shards back over HTTP. Then comes my favourite part. The Worker writes the shards into KV itself through the binding it already owns, so the container never touches storage and holds no credentials at all. Automating the whole pipeline added zero new secrets anywhere. The first deploy did bounce because Containers need the paid Workers plan, so this project now costs me five dollars a month plus about five cents of actual container time. For a build that runs itself every day, I reckon that's a bargain.

\n

Then the very first end-to-end run failed and honestly I couldn't have asked for a better failure. The build indexed 9,133 trips where the previous day's run had found over 20,000, so one of the assertion gates I'd wired through the pipeline looked at that number and refused to publish. The investigation turned up something I'd never have guessed. The two TfNSW feeds run on completely different clocks. The Sydney Trains operator feed regenerates at about 1am every morning like clockwork. The complete feed only republishes when something changes and I caught it nearly seven hours stale that morning, with one past incident lagging almost 44 hours. The trip ids carry a day counter that ticks up with each generation, so whenever the two feeds sit a generation apart the join between them collapses and half the trips silently fall out of the build.

\n

Two things came out of that. The daily rebuild moved from the small hours to midday, well clear of that 1am regeneration. And the gate itself went from box-ticking to being the hero of the story, because publishing that half-empty bundle would have been far worse than publishing nothing. A trip that's missing from the shard doesn't look missing at match time. It looks like a train that doesn't stop where you're going, which is the silent non-delivery I'd already ranked as the one unforgivable failure in this system. So here's a tip if you're building a pipeline whose output other code will trust. Give the build a gate on its own numbers and let it refuse loudly. Mine caught a genuine upstream problem on its very first run and the old shards simply kept serving until the feeds caught back up to each other.

\n

The last piece was making sure every failure lands in my inbox rather than a log, since a log-only canary had already burned me once. The staleness canary emails me now. A failed rebuild emails me too, naming the gate that tripped and the number it measured. Ops emails are capped at one a day so a bad week can't bury me. Best of all, every one of those emails carries the actual fix inside it, a curl command against a new authenticated admin route that kicks off a rebuild on demand. There's a fun Workers quirk in that route too. A Worker only gets about 30 seconds of background time after it responds, nowhere near enough for this build, but there's no wall-clock limit while the client stays connected. So the route just holds the connection open until the build finishes and then answers with the result.

\n

That admin route is exactly how the first real production rebuild ran. It published 239 station shards, swept up 8 stale ones an older build had left behind and came back in 67 seconds. That number was its own little lesson too, because every estimate along the way had confidently said the build takes about ten minutes. The very last pull request in this whole saga was me going back through the emails and docs to correct that claim to what production actually measured. So the timer on this feature is no longer wound by hand. I reckon that's the real finish line for a side project like this, the moment it keeps working when you're not looking at it.

\n\n

The result

\n

I couldn't be happier with how this one came out! The first build took a couple of days with Claude Code alongside me and it's grown up a fair bit since it went live. The caching that keeps it inside the free tier, the ferry site, the whole stopping-pattern rework and the daily container rebuild that keeps its timetable data fresh all landed after real people started using it, which is by far the best way to find that sort of thing. My phone buzzes two minutes before the train actually leaves, the alert already knows it's running late and one tap on \"I'm on board\" keeps everything quiet until my next travel day. I don't open the app, I don't refresh anything and I've stopped standing on platforms waiting. It's all covered end to end, Vitest across the schedule and crypto logic and Playwright driving the real Worker against a mock TfNSW upstream, so I can keep tweaking it without holding my breath. It's live at sydneytrainalerts.com, so if you're a Sydney commuter you're welcome to set one up of your own. There's a blog on each site too, nine posts apiece, to help people find them.

\n

Two things I'd pass on from this one. If you've built yourself a heuristic and you're onto your second round of tuning what it should do when it misses, stop and go measure how often the thing actually hits. Mine was landing 30 to 45% of the time and I'd never checked it once. Then if you're thinking about doing web push yourself, here's the tip I wish I'd had on day one. Don't test your encryption by firing real notifications and hoping. Make the random inputs injectable and verify your output against the worked example in RFC 8291 first, because the push services silently drop anything malformed and you'll get no clue why. Once that test passes, everything downstream is just plumbing. Happy coding!

" }, { "slug": "album-tracker-cli", From 479352a8626ac19c3dd19087cb6d351569d377da Mon Sep 17 00:00:00 2001 From: Daniel Workman Date: Wed, 12 Aug 2026 15:22:54 +1000 Subject: [PATCH 10/11] Fold the rebuild story into The plot twist section The standalone "Getting it off my laptop" heading is gone. The bridge is reframed: the laptop was always a test-run arrangement and the plan was to move the build into the cloud once proven, with the 11 August shard expiry as the nudge. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01KMJ1S4ND1NBWaEwjC3pgpm --- src/data/projects.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/projects.json b/src/data/projects.json index 4b03321..3148e47 100644 --- a/src/data/projects.json +++ b/src/data/projects.json @@ -8,7 +8,7 @@ "tech": ["TypeScript", "Cloudflare Workers", "Durable Objects", "Cloudflare D1", "Workers KV", "Cloudflare Containers", "Docker", "Cache API", "Hono", "Web Push / VAPID", "WebCrypto", "Resend", "TfNSW Open Data", "GTFS", "Vitest", "Playwright"], "period": "July 2026 — August 2026", "order": 1, - "content": "

Overview

\n

It's been nothing short of an exceptional first two months at Gyoza Flights! Jumping into the world of aviation has been super interesting and it has shifted my thinking as both an engineer and person. A recent example of this was the other day I was standing at Wynyard Station waiting for a train to Town Hall. My train of thought immediately drifted to origins and destinations. How my destination of Cronulla was all I really cared about and knowing when the next couple of trains would be departing. Sure I could go onto one of the various apps available and check but with my engineering brain I questioned if that was actually necessary. And did it cover what I really wanted? Sometimes I work back in the office, sometimes I leave early. I'm only in the office a few days a week and some of those are fortnightly. So as I stood and waited for my train an idea started to percolate. What if I just had a website where people could just choose an origin and destination, set a lead time, a window to receive the alerts, a type of alert (email or push notification) and then set and forget. Then they'd automatically get these alerts to their device and would be able to proactively determine when they should leave their office to make a train that suits what time they were thinking of leaving on that day. And that is how sydneytrainalerts.com was born.

\n

I build on Cloudflare constantly and my tennis calendar is still quietly doing its thing from a Worker right now, so I had a fair idea of what was sitting there already. Then I remembered the alarm on Durable Objects, where a tiny stateful object can put itself to sleep and wake at an exact moment. That's precisely the shape of \"ping me two minutes before each train\", so that's what I built it on. You pick a station, how much notice you want, the days and window you travel in and whether you want the alert by push or email. From then on it watches the live departures and nudges you before each train you could catch. Once you're on, you tap \"I'm on board\" and it stays quiet until your next window.

\n\"The\n\n

How it works

\n

The data comes from Transport for NSW's Open Data platform. Their Trip Planner API has a Departure Monitor endpoint that hands back the upcoming departures from a stop as plain JSON with the realtime estimates already merged in, so there's no GTFS protobuf wrangling at all. A second endpoint, Stop Finder, powers the station autocomplete on the form. The free tier gives you 60,000 calls a day which is heaps for a personal tool like this.

\n\"The\n

The part I really wanted to get right is the timing. Every alert is its own little Cloudflare Durable Object that watches your station through your travel window, follows the live delays and nudges you before each train you could catch. Once it's pinged you about one it lists the next couple behind it too, so if you miss the 5:12 you already know the 5:22 is coming. Getting that to actually work is the most interesting thing in the whole build, so I've given it its own section below.

\n\"Choosing\n

Schedules are Sydney wall-clock time with travel days, a start and end for the window and even fortnightly days for rosters that alternate. The subscriptions themselves live in a D1 database, which stays the source of truth. A cron fires every 15 minutes purely as a safety net so nothing gets stranded after a deploy. Once you're on your train you tap \"I'm on board\" and it goes quiet for the rest of that window, then picks back up on your next travel day. That pause sits on the manage page for every alert as well as on the notification itself.

\n\"The\n

One thing I had to be careful about is staying inside that free tier once more than a handful of people are using it. TfNSW's free plan also caps you at about 5 calls a second. It starts knocking requests back the moment you push past that. So the departure lookups run through Cloudflare's cache, keyed on the station and the minute. If ten people are all watching Town Hall at 5:31 they mostly land on the same cached answer instead of making ten separate calls. If TfNSW ever does rate-limit me the alerts back off and give it room instead of hammering away. I also sprinkle a bit of random jitter on the wake-ups so a deploy doesn't fire every alert at the same second and trip the limit myself. Since then I've added a counter in D1 that tallies every call that genuinely leaves for TfNSW, keyed on the Sydney day the quota actually resets on rather than UTC. It emails me as I cross 50, 75, 90 and 100% of the daily allowance. I'd rather find out I'm running hot from my inbox in the morning than from a window of alerts that quietly didn't go out. None of it is glamorous but it's what lets one free key quietly handle a proper crowd of commuters.

\n

Delivery is push or email. Push is the default now and that swap was worth doing. An email subscriber costs a send for every matching train against a provider cap I share across both sites, where a push message costs nothing and skips the confirmation email entirely, because granting the browser permission is already proof you own the device. Push started out as the second radio button and unticked, so almost everyone was taking the expensive channel without ever thinking about it. The form still flips itself back to email on any browser where push isn't a single tap, uninstalled iPhone Safari especially, so nobody lands on an Add to Home Screen wall when they were just trying to fill in a form. Email goes through Resend with a manage link in every message so you can pause or cancel from the alert itself. If the push service ever reports a subscription as gone, the alert deactivates itself rather than pushing into the void forever. The frontend is one dependency-free page, no framework, with the autocomplete, a live preview of the alert you're building and a proper install walkthrough for iPhones, because iOS will only deliver web push to a site that's been added to the home screen. It started at about 2,000 lines and it's around 6,500 now, still with exactly one runtime dependency, Hono for the routing.

\n

The thing I never planned on building was a second site. Ferries turn out to be the exact same problem with different nouns, so instead of forking the lot I made the mode a property of the request. The Worker reads the Host header, picks up the config for that brand and serves sydneyferryalerts.com off the same code, the same database and the same deploy as the trains site. That mode then gets stamped onto each subscription, because a Durable Object waking up on an alarm hours later has no request sitting there to read a hostname off. Everything that differs between the two lives in one config object, the brand and the homepage copy and whether you're waiting on a platform or a wharf. A third mode is one more entry in it.

\n\n

The Cool Nerd Part

\n

Right, this is the bit I actually get excited about. Before I committed to building it this way I sat down and properly grilled the design, because \"give every single alert its own little server that never really sleeps\" sounds mad when you first say it out loud. The more I poked at it though the more Durable Objects turned out to be the perfect fit, so let me walk through why.

\n

The timing first. Your train is meant to leave at 5:12 so a two minute warning should fire at 5:10. Then it runs four minutes late and the warning needs to fire at 5:14 instead. The target keeps moving all afternoon. A Durable Object has this thing called an alarm, which is basically a single built-in timer the object can point at any exact moment in the future. Better still, it survives restarts and deploys. So every time the alert wakes up and checks the live departures it works out the new fire time from the latest estimate and re-points its own alarm at it. The 5:12 slips to 5:14 and the alarm slides right along with it. It's an alert that reschedules itself off the delay. A plain cron can't do that. The finest a cron will give you is once a minute, so you'd be waking every minute, pulling every subscription and still landing up to a minute wide of a target that won't sit still.

\n

Then there's the fact that each alert is its own object with its own name. The trick with Durable Objects is that you address one by a string. I just use the subscription's id as that string. So wherever I am in the code, whether it's the subscribe request, the cancel button, the \"I'm on board\" tap or the safety-net cron, I hand it the same id and I'm always talking to the exact same object holding that one alert's state. No lookup table, no routing, no working out which server a subscription lives on. The id is the address. And because each object only ever holds its own alert, one person's alert going sideways can't touch anyone else's.

\n

Here's the one that really sold me though. A Durable Object only ever does one thing at a time. It won't run two requests against the same object at once, it queues them up. That sounds like a small detail but it's the whole reason the tricky part of this app is even doable, which brings me to the messy bit.

\n

A single wake-up isn't instant. The object has to go out over the network to TfNSW for the live departures. Then it has to actually send your push or your email. Both of those take real time. Now picture the alert half way through that, sitting there waiting on the departures to come back. Right at that moment you tap \"cancel\" or \"I'm on board\". Because the object does one thing at a time your tap can't barge in mid-step, but it does get its turn the instant the alert pauses to wait on the network. So it flips the state out from under the rest of the run. The alert wakes back up thinking everything's fine and, if I'm not careful, cheerfully sends you a notification for an alert you just cancelled.

\n

The honest answer to that is a lot of little if-checks. The wake-up loop is peppered with early exits that all ask the same question in different spots: has this been cancelled or paused while I wasn't looking? Bail out if it has. There's one on the very first line before it does anything. There's one straight after the departures come back from TfNSW. There's one right before it sends each notification and another right after. There's even one tucked inside the final save, so if a cancel landed while the alert was mid-flight, the moment it goes to write \"all done, wake me in 45 seconds\" it checks one last time and just doesn't, because arming a fresh timer on an alert you've killed is exactly how you end up haunted by a cancelled alert that won't die. Cancelling also leaves a little durable marker behind, a tombstone, so the safety-net cron can't come past later, spot an alert with no timer set and helpfully resurrect the very thing you just deleted.

\n

It's not the most elegant code I've ever written and there are more of those guards than I'd like. But every single one of them is a real race I could sit and describe to you. And the reason they get to be simple little if-blocks instead of a knot of locks is that one-thing-at-a-time promise. Without Durable Objects I'd be running one big loop over everyone's alerts, all of them fighting over the same database rows with a proper lock bolted on so a cancel and a poll couldn't clobber each other. With a Durable Object the isolation is the lock. Each alert ends up as a little self-winding alarm clock, chasing the train's live delay, costing nothing while it's asleep between your commutes and never fighting anyone for its own state, because it's the only thing that ever touches it.

\n\n

Sticking points

\n

The API that swore every station was invalid

\n

Early on the station search flat out refused to work. Every query came back with zero locations and a \"stop invalid\" message, even for Central. The culprit was one innocent looking parameter. Stop Finder takes a type_sf field and I'd set it to stop, because I was searching for stops. It turns out type_sf=stop actually means \"the text I'm sending is a stop ID\", so the broker was taking the word Cronulla and trying to look it up as a numeric station identifier. The fix was type_sf=any and then filtering the mixed bag of streets and points of interest down to actual stops. I only caught it because I verified the client against the live API instead of trusting the docs. The mock TfNSW server my Playwright tests drive now mirrors that exact broker behaviour, so this one can never quietly come back.

\n

Daylight saving and the missing hour

\n

Active windows are Sydney local time and my first version of \"when does today's window start\" was simple minute arithmetic from the current moment. An adversarial review pass over the whole codebase caught that the simple version drifts by exactly an hour whenever the calculation straddles the 2am daylight saving changeover, which in the worst case means a whole morning of missed alerts. The fix was to stop doing arithmetic and start constructing the actual Sydney wall time, using Intl with the Australia/Sydney timezone to check the guess and correct it. I reckon DST bugs are the sneakiest kind there is, because the broken code is provably correct for 363 days of the year.

\n

The alert that sent people the wrong way

\n

The \"Towards\" box is meant to be a light direction filter. Say you're leaving Town Hall heading home to Cronulla, you pop Cronulla in and the alert only bothers you about trains actually going your way. The catch is that a train only really tells you its final stop, so a service that passes through your destination on the way to somewhere else doesn't always say your stop's name. The filter would match nothing. My first version handled that by failing open, so when nothing matched it just alerted you about everything, on the theory that too many alerts beats none. That turned out to be genuinely annoying. On a quiet poll with no Cronulla train in sight it would happily ping me about trains heading the complete opposite way, up to Berowra and out to Emu Plains. The fix was to give the filter a memory. The first time a real matching train shows up it quietly notes that this direction does work here. From then on a poll with no match means go quiet, not alert about everything. It only falls back to the noisy everything-goes behaviour for a destination that has never once matched, which is the genuine can't-tell case. I still reckon failing open was the right instinct. It just shouldn't have been what happens every time. That felt like the end of it. It really wasn't.

\n

Web push from scratch, byte for byte

\n

The web push libraries everyone reaches for are built for Node and lean on APIs a Worker doesn't have. Rather than fight that, I wrote the push layer from scratch on WebCrypto. That means two RFCs. VAPID (RFC 8292), an ES256 signed JWT that proves to Apple's or Google's push service the message really came from my server. Then the payload encryption (RFC 8291), a chain of ECDH key agreement, HKDF derivations and AES-128-GCM. Hand rolling crypto is nerve-racking because the failure mode is silence, the push service just drops anything malformed and tells you nothing. The saving grace is that the encryption RFC ships a complete worked example with every intermediate value spelled out. So I made the encrypt function accept an injectable random seed and wrote a test that reproduces that worked example byte for byte. When it went green I knew the scariest part of the codebase was actually correct rather than just plausible.

\n\n

The plot twist

\n

So I shipped that direction fix and moved on, pretty pleased with myself. A few weeks later I finally sat down to put a number on how often the filter actually matched, which I'd never once measured. I pulled the real departure board for Town Hall on a Monday afternoon, 80 trains across the shipped default window, then checked it against six destinations a normal person would genuinely type in. Here's the twist. A headsign only ever tells you where a train finishes up. It says nothing at all about the twenty odd stations it calls at on the way there. Only 4 of those 80 trains terminate at Parramatta. 20 of them actually stop at Parramatta. So a Parramatta subscriber was being told about 4 trains a window and never hearing a whisper about the other 16. Hurstville was 5 against 15. My tidy little fail-closed fix wasn't filtering trains out at all, it was silently dropping trains those people could have caught. And because going quiet looks exactly the same as there being nothing to say, they'd never have a reason to suspect it.

\n

The other branch was just as broken in the opposite direction. Redfern and Wynyard never match a headsign from Town Hall, not one of the 80, so the filter never got to learn that the direction works there and sat in the noisy everything-goes branch forever, pinging those subscribers 79 times a window. I widened the measurement out to 56 plausible Sydney destinations and depending where you start from only 30 to 45% of them are ever a headsign. Blacktown, Sutherland, Lidcombe, Bankstown, Museum, Wynyard and Redfern are all completely ordinary places to be heading home to and not one of them is a headsign from Town Hall. Both halves of my filter were wrong and they were wrong for the same reason. The signal was wrong. I'd spent two whole rounds carefully tuning what to do when the match failed. The actual bug was the question I was asking.

\n

So the fix was to stop asking whether the destination is the last stop and start asking the only thing that actually matters. Does this train stop where I'm going? TfNSW's live departure board hands back a gtfsTripId on nearly every service and the static GTFS timetable carries the full calling pattern for every trip, so if those two join I get the real list of stations instead of a guess off the front of the train. They don't join directly, which took a whole investigation to establish. That id isn't a real trip id in any feed TfNSW publishes, not even the combined one. It isn't opaque either though. The route part of it reconstructs exactly to a route_id in the complete feed. Route plus origin station plus departure minute then pins the trip right down. 18 out of 18 live departures reconciled on the first proper run. That was a very good afternoon.

\n

None of that fits on a Worker. The complete feed is 293 MB zipped and 1.45 GB extracted against a 128 MB isolate, so the heavy lifting runs on my laptop instead. A script pulls both feeds down, works out the onward calling pattern for every trip and boils the whole network into one small pattern shard per origin station, then publishes them into Workers KV. The first build came out at 247 stations and 18,090 trips indexed for about 7 MB all up. At alert time the Durable Object reads only its own station's shard, looks each departure up by route and minute and keeps the train if the destination is somewhere in what's left of its stop list. Everything hard gets resolved once, on the laptop, in code I can write proper tests against. The Worker ends up holding exactly one idea. The stations this service calls at after this one.

\n

The genuinely nasty bit was through-running. Sydney Trains sends a lot of services straight through the City and the static timetable models the approach leg as a trip that terminates at Central, while the live board is already advertising the eventual destination. Read that literally and you get a false negative, which is worse than the thing I was replacing. GTFS has a block_id that links the trips worked by the same physical train. Sure enough the continuation leg leaves Central exactly one second after the approach leg arrives. Stitch the two and a trip that looked like it stopped at Central becomes the real 49 stop run. 1,671 of 1,671 Central-terminating trips on those lines had a block mate waiting for them. Without the stitching Cronulla would have gone from 5 alerts to 16, so it was required rather than a nice to have. I keyed the block on the service id as well as the block id too, because Sydney Trains reuses block ids across weekday and Saturday patterns whose join times happen to line up. That would have stamped a weekday timetable onto a Saturday train. A confident wrong yes, which is the worst kind, because nothing at runtime can catch it.

\n

The rule I care about most in this whole thing is that absence is never a no. Destination missing from the shard, a schema version I don't recognise, the published dates running out, a KV read timing out, every one of those returns don't know and falls back to the old headsign matcher rather than deciding no. A wrong no is silent permanent non-delivery and that's the only failure in here a user can't recover from on their own. The acceptance run I was holding out for finally landed in a real weekday peak window, courtesy of a little scheduled job on my laptop taking the measurement at 4:52pm every weekday because I'm hardly ever at a keyboard at exactly the right minute. Town Hall came back with 80 out of 80 departures covered by the shards. My own Cronulla alert landed on 5 trains for the window, right where the earlier measurements said it should be. So I flipped it on. The stopping-pattern filter has been live for every train alert since early August and the rollback story is as boring as I could make it. Deleting the shard keys out of KV turns the whole thing off in seconds, no deploy needed. Ferries stay on the headsign path for now. Their wharf ids don't map into the ferry GTFS feed at all, which is a whole separate rabbit hole for another week.

\n\n

Getting it off my laptop

\n

There was one weak link left in all of this and it was me. Each shard deliberately carries just eight days of timetable, today plus a week, so that a stale build degrades gracefully instead of serving wrong answers forever. The thing that refreshed them though was a script I ran by hand on my laptop. You can probably see where this is going. The build from the 3rd of August covered up to the 10th, so on the 11th every train alert quietly aged out of its shard and spent the whole day back on the old headsign matcher. Nothing broke, which is exactly how I designed it. Honestly that almost made it worse. Alerts kept arriving, just fewer of them. The canary I'd built to spot stale shards was only writing a line into a log I wasn't reading, so production ran thin all day before I rebuilt the shards that evening. I'd shipped a feature with a hand-wound eight day timer on it and the timer had run out exactly on schedule.

\n

So the rebuild had to go somewhere that isn't my laptop. My first thought was GitHub Actions and I went off it for two reasons. It would need a Cloudflare API token with write access to KV sitting in GitHub's secrets, a brand new credential to mint and then worry about forever. On top of that GitHub quietly switches off scheduled workflows when a repo goes inactive, which for a set-and-forget project is exactly the kind of silent stop I was trying to get rid of. Then I remembered Cloudflare had shipped Containers, where you attach a real Docker container to a Worker for the jobs an isolate can't handle. That was the lightbulb moment. The 1.45 GB extract that pushed this build onto my laptop in the first place stops mattering the moment the build gets a proper machine of its own.

\n

Here's the bit I love about how Containers work. You address the container through a Durable Object. The same one-object-one-address idea that runs every alert in this app also runs the build machine. It only ever exists while it's working. A second cron in the same Worker fires once a day at midday and wakes the container. The container pulls both GTFS feeds down, chews through them with the exact same build code my laptop was running and hands the finished shards back over HTTP. Then comes my favourite part. The Worker writes the shards into KV itself through the binding it already owns, so the container never touches storage and holds no credentials at all. Automating the whole pipeline added zero new secrets anywhere. The first deploy did bounce because Containers need the paid Workers plan, so this project now costs me five dollars a month plus about five cents of actual container time. For a build that runs itself every day, I reckon that's a bargain.

\n

Then the very first end-to-end run failed and honestly I couldn't have asked for a better failure. The build indexed 9,133 trips where the previous day's run had found over 20,000, so one of the assertion gates I'd wired through the pipeline looked at that number and refused to publish. The investigation turned up something I'd never have guessed. The two TfNSW feeds run on completely different clocks. The Sydney Trains operator feed regenerates at about 1am every morning like clockwork. The complete feed only republishes when something changes and I caught it nearly seven hours stale that morning, with one past incident lagging almost 44 hours. The trip ids carry a day counter that ticks up with each generation, so whenever the two feeds sit a generation apart the join between them collapses and half the trips silently fall out of the build.

\n

Two things came out of that. The daily rebuild moved from the small hours to midday, well clear of that 1am regeneration. And the gate itself went from box-ticking to being the hero of the story, because publishing that half-empty bundle would have been far worse than publishing nothing. A trip that's missing from the shard doesn't look missing at match time. It looks like a train that doesn't stop where you're going, which is the silent non-delivery I'd already ranked as the one unforgivable failure in this system. So here's a tip if you're building a pipeline whose output other code will trust. Give the build a gate on its own numbers and let it refuse loudly. Mine caught a genuine upstream problem on its very first run and the old shards simply kept serving until the feeds caught back up to each other.

\n

The last piece was making sure every failure lands in my inbox rather than a log, since a log-only canary had already burned me once. The staleness canary emails me now. A failed rebuild emails me too, naming the gate that tripped and the number it measured. Ops emails are capped at one a day so a bad week can't bury me. Best of all, every one of those emails carries the actual fix inside it, a curl command against a new authenticated admin route that kicks off a rebuild on demand. There's a fun Workers quirk in that route too. A Worker only gets about 30 seconds of background time after it responds, nowhere near enough for this build, but there's no wall-clock limit while the client stays connected. So the route just holds the connection open until the build finishes and then answers with the result.

\n

That admin route is exactly how the first real production rebuild ran. It published 239 station shards, swept up 8 stale ones an older build had left behind and came back in 67 seconds. That number was its own little lesson too, because every estimate along the way had confidently said the build takes about ten minutes. The very last pull request in this whole saga was me going back through the emails and docs to correct that claim to what production actually measured. So the timer on this feature is no longer wound by hand. I reckon that's the real finish line for a side project like this, the moment it keeps working when you're not looking at it.

\n\n

The result

\n

I couldn't be happier with how this one came out! The first build took a couple of days with Claude Code alongside me and it's grown up a fair bit since it went live. The caching that keeps it inside the free tier, the ferry site, the whole stopping-pattern rework and the daily container rebuild that keeps its timetable data fresh all landed after real people started using it, which is by far the best way to find that sort of thing. My phone buzzes two minutes before the train actually leaves, the alert already knows it's running late and one tap on \"I'm on board\" keeps everything quiet until my next travel day. I don't open the app, I don't refresh anything and I've stopped standing on platforms waiting. It's all covered end to end, Vitest across the schedule and crypto logic and Playwright driving the real Worker against a mock TfNSW upstream, so I can keep tweaking it without holding my breath. It's live at sydneytrainalerts.com, so if you're a Sydney commuter you're welcome to set one up of your own. There's a blog on each site too, nine posts apiece, to help people find them.

\n

Two things I'd pass on from this one. If you've built yourself a heuristic and you're onto your second round of tuning what it should do when it misses, stop and go measure how often the thing actually hits. Mine was landing 30 to 45% of the time and I'd never checked it once. Then if you're thinking about doing web push yourself, here's the tip I wish I'd had on day one. Don't test your encryption by firing real notifications and hoping. Make the random inputs injectable and verify your output against the worked example in RFC 8291 first, because the push services silently drop anything malformed and you'll get no clue why. Once that test passes, everything downstream is just plumbing. Happy coding!

" + "content": "

Overview

\n

It's been nothing short of an exceptional first two months at Gyoza Flights! Jumping into the world of aviation has been super interesting and it has shifted my thinking as both an engineer and person. A recent example of this was the other day I was standing at Wynyard Station waiting for a train to Town Hall. My train of thought immediately drifted to origins and destinations. How my destination of Cronulla was all I really cared about and knowing when the next couple of trains would be departing. Sure I could go onto one of the various apps available and check but with my engineering brain I questioned if that was actually necessary. And did it cover what I really wanted? Sometimes I work back in the office, sometimes I leave early. I'm only in the office a few days a week and some of those are fortnightly. So as I stood and waited for my train an idea started to percolate. What if I just had a website where people could just choose an origin and destination, set a lead time, a window to receive the alerts, a type of alert (email or push notification) and then set and forget. Then they'd automatically get these alerts to their device and would be able to proactively determine when they should leave their office to make a train that suits what time they were thinking of leaving on that day. And that is how sydneytrainalerts.com was born.

\n

I build on Cloudflare constantly and my tennis calendar is still quietly doing its thing from a Worker right now, so I had a fair idea of what was sitting there already. Then I remembered the alarm on Durable Objects, where a tiny stateful object can put itself to sleep and wake at an exact moment. That's precisely the shape of \"ping me two minutes before each train\", so that's what I built it on. You pick a station, how much notice you want, the days and window you travel in and whether you want the alert by push or email. From then on it watches the live departures and nudges you before each train you could catch. Once you're on, you tap \"I'm on board\" and it stays quiet until your next window.

\n\"The\n\n

How it works

\n

The data comes from Transport for NSW's Open Data platform. Their Trip Planner API has a Departure Monitor endpoint that hands back the upcoming departures from a stop as plain JSON with the realtime estimates already merged in, so there's no GTFS protobuf wrangling at all. A second endpoint, Stop Finder, powers the station autocomplete on the form. The free tier gives you 60,000 calls a day which is heaps for a personal tool like this.

\n\"The\n

The part I really wanted to get right is the timing. Every alert is its own little Cloudflare Durable Object that watches your station through your travel window, follows the live delays and nudges you before each train you could catch. Once it's pinged you about one it lists the next couple behind it too, so if you miss the 5:12 you already know the 5:22 is coming. Getting that to actually work is the most interesting thing in the whole build, so I've given it its own section below.

\n\"Choosing\n

Schedules are Sydney wall-clock time with travel days, a start and end for the window and even fortnightly days for rosters that alternate. The subscriptions themselves live in a D1 database, which stays the source of truth. A cron fires every 15 minutes purely as a safety net so nothing gets stranded after a deploy. Once you're on your train you tap \"I'm on board\" and it goes quiet for the rest of that window, then picks back up on your next travel day. That pause sits on the manage page for every alert as well as on the notification itself.

\n\"The\n

One thing I had to be careful about is staying inside that free tier once more than a handful of people are using it. TfNSW's free plan also caps you at about 5 calls a second. It starts knocking requests back the moment you push past that. So the departure lookups run through Cloudflare's cache, keyed on the station and the minute. If ten people are all watching Town Hall at 5:31 they mostly land on the same cached answer instead of making ten separate calls. If TfNSW ever does rate-limit me the alerts back off and give it room instead of hammering away. I also sprinkle a bit of random jitter on the wake-ups so a deploy doesn't fire every alert at the same second and trip the limit myself. Since then I've added a counter in D1 that tallies every call that genuinely leaves for TfNSW, keyed on the Sydney day the quota actually resets on rather than UTC. It emails me as I cross 50, 75, 90 and 100% of the daily allowance. I'd rather find out I'm running hot from my inbox in the morning than from a window of alerts that quietly didn't go out. None of it is glamorous but it's what lets one free key quietly handle a proper crowd of commuters.

\n

Delivery is push or email. Push is the default now and that swap was worth doing. An email subscriber costs a send for every matching train against a provider cap I share across both sites, where a push message costs nothing and skips the confirmation email entirely, because granting the browser permission is already proof you own the device. Push started out as the second radio button and unticked, so almost everyone was taking the expensive channel without ever thinking about it. The form still flips itself back to email on any browser where push isn't a single tap, uninstalled iPhone Safari especially, so nobody lands on an Add to Home Screen wall when they were just trying to fill in a form. Email goes through Resend with a manage link in every message so you can pause or cancel from the alert itself. If the push service ever reports a subscription as gone, the alert deactivates itself rather than pushing into the void forever. The frontend is one dependency-free page, no framework, with the autocomplete, a live preview of the alert you're building and a proper install walkthrough for iPhones, because iOS will only deliver web push to a site that's been added to the home screen. It started at about 2,000 lines and it's around 6,500 now, still with exactly one runtime dependency, Hono for the routing.

\n

The thing I never planned on building was a second site. Ferries turn out to be the exact same problem with different nouns, so instead of forking the lot I made the mode a property of the request. The Worker reads the Host header, picks up the config for that brand and serves sydneyferryalerts.com off the same code, the same database and the same deploy as the trains site. That mode then gets stamped onto each subscription, because a Durable Object waking up on an alarm hours later has no request sitting there to read a hostname off. Everything that differs between the two lives in one config object, the brand and the homepage copy and whether you're waiting on a platform or a wharf. A third mode is one more entry in it.

\n\n

The Cool Nerd Part

\n

Right, this is the bit I actually get excited about. Before I committed to building it this way I sat down and properly grilled the design, because \"give every single alert its own little server that never really sleeps\" sounds mad when you first say it out loud. The more I poked at it though the more Durable Objects turned out to be the perfect fit, so let me walk through why.

\n

The timing first. Your train is meant to leave at 5:12 so a two minute warning should fire at 5:10. Then it runs four minutes late and the warning needs to fire at 5:14 instead. The target keeps moving all afternoon. A Durable Object has this thing called an alarm, which is basically a single built-in timer the object can point at any exact moment in the future. Better still, it survives restarts and deploys. So every time the alert wakes up and checks the live departures it works out the new fire time from the latest estimate and re-points its own alarm at it. The 5:12 slips to 5:14 and the alarm slides right along with it. It's an alert that reschedules itself off the delay. A plain cron can't do that. The finest a cron will give you is once a minute, so you'd be waking every minute, pulling every subscription and still landing up to a minute wide of a target that won't sit still.

\n

Then there's the fact that each alert is its own object with its own name. The trick with Durable Objects is that you address one by a string. I just use the subscription's id as that string. So wherever I am in the code, whether it's the subscribe request, the cancel button, the \"I'm on board\" tap or the safety-net cron, I hand it the same id and I'm always talking to the exact same object holding that one alert's state. No lookup table, no routing, no working out which server a subscription lives on. The id is the address. And because each object only ever holds its own alert, one person's alert going sideways can't touch anyone else's.

\n

Here's the one that really sold me though. A Durable Object only ever does one thing at a time. It won't run two requests against the same object at once, it queues them up. That sounds like a small detail but it's the whole reason the tricky part of this app is even doable, which brings me to the messy bit.

\n

A single wake-up isn't instant. The object has to go out over the network to TfNSW for the live departures. Then it has to actually send your push or your email. Both of those take real time. Now picture the alert half way through that, sitting there waiting on the departures to come back. Right at that moment you tap \"cancel\" or \"I'm on board\". Because the object does one thing at a time your tap can't barge in mid-step, but it does get its turn the instant the alert pauses to wait on the network. So it flips the state out from under the rest of the run. The alert wakes back up thinking everything's fine and, if I'm not careful, cheerfully sends you a notification for an alert you just cancelled.

\n

The honest answer to that is a lot of little if-checks. The wake-up loop is peppered with early exits that all ask the same question in different spots: has this been cancelled or paused while I wasn't looking? Bail out if it has. There's one on the very first line before it does anything. There's one straight after the departures come back from TfNSW. There's one right before it sends each notification and another right after. There's even one tucked inside the final save, so if a cancel landed while the alert was mid-flight, the moment it goes to write \"all done, wake me in 45 seconds\" it checks one last time and just doesn't, because arming a fresh timer on an alert you've killed is exactly how you end up haunted by a cancelled alert that won't die. Cancelling also leaves a little durable marker behind, a tombstone, so the safety-net cron can't come past later, spot an alert with no timer set and helpfully resurrect the very thing you just deleted.

\n

It's not the most elegant code I've ever written and there are more of those guards than I'd like. But every single one of them is a real race I could sit and describe to you. And the reason they get to be simple little if-blocks instead of a knot of locks is that one-thing-at-a-time promise. Without Durable Objects I'd be running one big loop over everyone's alerts, all of them fighting over the same database rows with a proper lock bolted on so a cancel and a poll couldn't clobber each other. With a Durable Object the isolation is the lock. Each alert ends up as a little self-winding alarm clock, chasing the train's live delay, costing nothing while it's asleep between your commutes and never fighting anyone for its own state, because it's the only thing that ever touches it.

\n\n

Sticking points

\n

The API that swore every station was invalid

\n

Early on the station search flat out refused to work. Every query came back with zero locations and a \"stop invalid\" message, even for Central. The culprit was one innocent looking parameter. Stop Finder takes a type_sf field and I'd set it to stop, because I was searching for stops. It turns out type_sf=stop actually means \"the text I'm sending is a stop ID\", so the broker was taking the word Cronulla and trying to look it up as a numeric station identifier. The fix was type_sf=any and then filtering the mixed bag of streets and points of interest down to actual stops. I only caught it because I verified the client against the live API instead of trusting the docs. The mock TfNSW server my Playwright tests drive now mirrors that exact broker behaviour, so this one can never quietly come back.

\n

Daylight saving and the missing hour

\n

Active windows are Sydney local time and my first version of \"when does today's window start\" was simple minute arithmetic from the current moment. An adversarial review pass over the whole codebase caught that the simple version drifts by exactly an hour whenever the calculation straddles the 2am daylight saving changeover, which in the worst case means a whole morning of missed alerts. The fix was to stop doing arithmetic and start constructing the actual Sydney wall time, using Intl with the Australia/Sydney timezone to check the guess and correct it. I reckon DST bugs are the sneakiest kind there is, because the broken code is provably correct for 363 days of the year.

\n

The alert that sent people the wrong way

\n

The \"Towards\" box is meant to be a light direction filter. Say you're leaving Town Hall heading home to Cronulla, you pop Cronulla in and the alert only bothers you about trains actually going your way. The catch is that a train only really tells you its final stop, so a service that passes through your destination on the way to somewhere else doesn't always say your stop's name. The filter would match nothing. My first version handled that by failing open, so when nothing matched it just alerted you about everything, on the theory that too many alerts beats none. That turned out to be genuinely annoying. On a quiet poll with no Cronulla train in sight it would happily ping me about trains heading the complete opposite way, up to Berowra and out to Emu Plains. The fix was to give the filter a memory. The first time a real matching train shows up it quietly notes that this direction does work here. From then on a poll with no match means go quiet, not alert about everything. It only falls back to the noisy everything-goes behaviour for a destination that has never once matched, which is the genuine can't-tell case. I still reckon failing open was the right instinct. It just shouldn't have been what happens every time. That felt like the end of it. It really wasn't.

\n

Web push from scratch, byte for byte

\n

The web push libraries everyone reaches for are built for Node and lean on APIs a Worker doesn't have. Rather than fight that, I wrote the push layer from scratch on WebCrypto. That means two RFCs. VAPID (RFC 8292), an ES256 signed JWT that proves to Apple's or Google's push service the message really came from my server. Then the payload encryption (RFC 8291), a chain of ECDH key agreement, HKDF derivations and AES-128-GCM. Hand rolling crypto is nerve-racking because the failure mode is silence, the push service just drops anything malformed and tells you nothing. The saving grace is that the encryption RFC ships a complete worked example with every intermediate value spelled out. So I made the encrypt function accept an injectable random seed and wrote a test that reproduces that worked example byte for byte. When it went green I knew the scariest part of the codebase was actually correct rather than just plausible.

\n\n

The plot twist

\n

So I shipped that direction fix and moved on, pretty pleased with myself. A few weeks later I finally sat down to put a number on how often the filter actually matched, which I'd never once measured. I pulled the real departure board for Town Hall on a Monday afternoon, 80 trains across the shipped default window, then checked it against six destinations a normal person would genuinely type in. Here's the twist. A headsign only ever tells you where a train finishes up. It says nothing at all about the twenty odd stations it calls at on the way there. Only 4 of those 80 trains terminate at Parramatta. 20 of them actually stop at Parramatta. So a Parramatta subscriber was being told about 4 trains a window and never hearing a whisper about the other 16. Hurstville was 5 against 15. My tidy little fail-closed fix wasn't filtering trains out at all, it was silently dropping trains those people could have caught. And because going quiet looks exactly the same as there being nothing to say, they'd never have a reason to suspect it.

\n

The other branch was just as broken in the opposite direction. Redfern and Wynyard never match a headsign from Town Hall, not one of the 80, so the filter never got to learn that the direction works there and sat in the noisy everything-goes branch forever, pinging those subscribers 79 times a window. I widened the measurement out to 56 plausible Sydney destinations and depending where you start from only 30 to 45% of them are ever a headsign. Blacktown, Sutherland, Lidcombe, Bankstown, Museum, Wynyard and Redfern are all completely ordinary places to be heading home to and not one of them is a headsign from Town Hall. Both halves of my filter were wrong and they were wrong for the same reason. The signal was wrong. I'd spent two whole rounds carefully tuning what to do when the match failed. The actual bug was the question I was asking.

\n

So the fix was to stop asking whether the destination is the last stop and start asking the only thing that actually matters. Does this train stop where I'm going? TfNSW's live departure board hands back a gtfsTripId on nearly every service and the static GTFS timetable carries the full calling pattern for every trip, so if those two join I get the real list of stations instead of a guess off the front of the train. They don't join directly, which took a whole investigation to establish. That id isn't a real trip id in any feed TfNSW publishes, not even the combined one. It isn't opaque either though. The route part of it reconstructs exactly to a route_id in the complete feed. Route plus origin station plus departure minute then pins the trip right down. 18 out of 18 live departures reconciled on the first proper run. That was a very good afternoon.

\n

None of that fits on a Worker. The complete feed is 293 MB zipped and 1.45 GB extracted against a 128 MB isolate, so the heavy lifting runs on my laptop instead. A script pulls both feeds down, works out the onward calling pattern for every trip and boils the whole network into one small pattern shard per origin station, then publishes them into Workers KV. The first build came out at 247 stations and 18,090 trips indexed for about 7 MB all up. At alert time the Durable Object reads only its own station's shard, looks each departure up by route and minute and keeps the train if the destination is somewhere in what's left of its stop list. Everything hard gets resolved once, on the laptop, in code I can write proper tests against. The Worker ends up holding exactly one idea. The stations this service calls at after this one.

\n

The genuinely nasty bit was through-running. Sydney Trains sends a lot of services straight through the City and the static timetable models the approach leg as a trip that terminates at Central, while the live board is already advertising the eventual destination. Read that literally and you get a false negative, which is worse than the thing I was replacing. GTFS has a block_id that links the trips worked by the same physical train. Sure enough the continuation leg leaves Central exactly one second after the approach leg arrives. Stitch the two and a trip that looked like it stopped at Central becomes the real 49 stop run. 1,671 of 1,671 Central-terminating trips on those lines had a block mate waiting for them. Without the stitching Cronulla would have gone from 5 alerts to 16, so it was required rather than a nice to have. I keyed the block on the service id as well as the block id too, because Sydney Trains reuses block ids across weekday and Saturday patterns whose join times happen to line up. That would have stamped a weekday timetable onto a Saturday train. A confident wrong yes, which is the worst kind, because nothing at runtime can catch it.

\n

The rule I care about most in this whole thing is that absence is never a no. Destination missing from the shard, a schema version I don't recognise, the published dates running out, a KV read timing out, every one of those returns don't know and falls back to the old headsign matcher rather than deciding no. A wrong no is silent permanent non-delivery and that's the only failure in here a user can't recover from on their own. The acceptance run I was holding out for finally landed in a real weekday peak window, courtesy of a little scheduled job on my laptop taking the measurement at 4:52pm every weekday because I'm hardly ever at a keyboard at exactly the right minute. Town Hall came back with 80 out of 80 departures covered by the shards. My own Cronulla alert landed on 5 trains for the window, right where the earlier measurements said it should be. So I flipped it on. The stopping-pattern filter has been live for every train alert since early August and the rollback story is as boring as I could make it. Deleting the shard keys out of KV turns the whole thing off in seconds, no deploy needed. Ferries stay on the headsign path for now. Their wharf ids don't map into the ferry GTFS feed at all, which is a whole separate rabbit hole for another week.

\n

The laptop was never the long-term plan though. It was fine while I was proving the idea, running the build by hand in test runs and eyeballing the numbers before each publish, but I knew the whole time that if I wanted this to be properly automatic the build had to come off my machine and live in the cloud somewhere. A script that only runs when I remember to run it isn't automation. The shards even gave me a deadline, because each one deliberately carries just eight days of timetable, today plus a week, so that a stale build degrades gracefully instead of serving wrong answers forever. Sure enough the build from the 3rd of August covered up to the 10th, so on the 11th every train alert quietly aged out of its shard and spent the whole day back on the old headsign matcher. Nothing broke, which is exactly how I designed it. Honestly that almost made it worse. Alerts kept arriving, just fewer of them. The canary I'd built to spot stale shards was only writing a line into a log I wasn't reading, so production ran thin all day before I rebuilt the shards by hand that evening. That was the nudge I needed to stop putting the move off.

\n

My first thought for a cloud home was GitHub Actions and I went off it for two reasons. It would need a Cloudflare API token with write access to KV sitting in GitHub's secrets, a brand new credential to mint and then worry about forever. On top of that GitHub quietly switches off scheduled workflows when a repo goes inactive, which for a set-and-forget project is exactly the kind of silent stop I was trying to get rid of. Then I remembered Cloudflare had shipped Containers, where you attach a real Docker container to a Worker for the jobs an isolate can't handle. That was the lightbulb moment. The 1.45 GB extract that pushed this build onto my laptop in the first place stops mattering the moment the build gets a proper machine of its own.

\n

Here's the bit I love about how Containers work. You address the container through a Durable Object. The same one-object-one-address idea that runs every alert in this app also runs the build machine. It only ever exists while it's working. A second cron in the same Worker fires once a day at midday and wakes the container. The container pulls both GTFS feeds down, chews through them with the exact same build code my laptop was running and hands the finished shards back over HTTP. Then comes my favourite part. The Worker writes the shards into KV itself through the binding it already owns, so the container never touches storage and holds no credentials at all. Automating the whole pipeline added zero new secrets anywhere. The first deploy did bounce because Containers need the paid Workers plan, so this project now costs me five dollars a month plus about five cents of actual container time. For a build that runs itself every day, I reckon that's a bargain.

\n

Then the very first end-to-end run failed and honestly I couldn't have asked for a better failure. The build indexed 9,133 trips where the previous day's run had found over 20,000, so one of the assertion gates I'd wired through the pipeline looked at that number and refused to publish. The investigation turned up something I'd never have guessed. The two TfNSW feeds run on completely different clocks. The Sydney Trains operator feed regenerates at about 1am every morning like clockwork. The complete feed only republishes when something changes and I caught it nearly seven hours stale that morning, with one past incident lagging almost 44 hours. The trip ids carry a day counter that ticks up with each generation, so whenever the two feeds sit a generation apart the join between them collapses and half the trips silently fall out of the build.

\n

Two things came out of that. The daily rebuild moved from the small hours to midday, well clear of that 1am regeneration. And the gate itself went from box-ticking to being the hero of the story, because publishing that half-empty bundle would have been far worse than publishing nothing. A trip that's missing from the shard doesn't look missing at match time. It looks like a train that doesn't stop where you're going, which is the silent non-delivery I'd already ranked as the one unforgivable failure in this system. So here's a tip if you're building a pipeline whose output other code will trust. Give the build a gate on its own numbers and let it refuse loudly. Mine caught a genuine upstream problem on its very first run and the old shards simply kept serving until the feeds caught back up to each other.

\n

The last piece was making sure every failure lands in my inbox rather than a log, since a log-only canary had already burned me once. The staleness canary emails me now. A failed rebuild emails me too, naming the gate that tripped and the number it measured. Ops emails are capped at one a day so a bad week can't bury me. Best of all, every one of those emails carries the actual fix inside it, a curl command against a new authenticated admin route that kicks off a rebuild on demand. There's a fun Workers quirk in that route too. A Worker only gets about 30 seconds of background time after it responds, nowhere near enough for this build, but there's no wall-clock limit while the client stays connected. So the route just holds the connection open until the build finishes and then answers with the result.

\n

That admin route is exactly how the first real production rebuild ran. It published 239 station shards, swept up 8 stale ones an older build had left behind and came back in 67 seconds. That number was its own little lesson too, because every estimate along the way had confidently said the build takes about ten minutes. The very last pull request in this whole saga was me going back through the emails and docs to correct that claim to what production actually measured. So the timer on this feature is no longer wound by hand. I reckon that's the real finish line for a side project like this, the moment it keeps working when you're not looking at it.

\n\n

The result

\n

I couldn't be happier with how this one came out! The first build took a couple of days with Claude Code alongside me and it's grown up a fair bit since it went live. The caching that keeps it inside the free tier, the ferry site, the whole stopping-pattern rework and the daily container rebuild that keeps its timetable data fresh all landed after real people started using it, which is by far the best way to find that sort of thing. My phone buzzes two minutes before the train actually leaves, the alert already knows it's running late and one tap on \"I'm on board\" keeps everything quiet until my next travel day. I don't open the app, I don't refresh anything and I've stopped standing on platforms waiting. It's all covered end to end, Vitest across the schedule and crypto logic and Playwright driving the real Worker against a mock TfNSW upstream, so I can keep tweaking it without holding my breath. It's live at sydneytrainalerts.com, so if you're a Sydney commuter you're welcome to set one up of your own. There's a blog on each site too, nine posts apiece, to help people find them.

\n

Two things I'd pass on from this one. If you've built yourself a heuristic and you're onto your second round of tuning what it should do when it misses, stop and go measure how often the thing actually hits. Mine was landing 30 to 45% of the time and I'd never checked it once. Then if you're thinking about doing web push yourself, here's the tip I wish I'd had on day one. Don't test your encryption by firing real notifications and hoping. Make the random inputs injectable and verify your output against the worked example in RFC 8291 first, because the push services silently drop anything malformed and you'll get no clue why. Once that test passes, everything downstream is just plumbing. Happy coding!

" }, { "slug": "album-tracker-cli", From e6d016c61bc77078245976c7af3373ded2008247 Mon Sep 17 00:00:00 2001 From: Daniel Workman Date: Wed, 12 Aug 2026 15:58:14 +1000 Subject: [PATCH 11/11] Split the cloud move into "The plot twist, solved for good" The laptop build paragraph moves to past tense (started out, pulled, worked out, boiled, published) and "resolved once, on the laptop" becomes "at build time". The bridge paragraph breaks in two. Also corrects the stale line count (6,500 -> past 8,000) and the one-runtime- dependency claim, which stopped being true when @cloudflare/containers arrived. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01KMJ1S4ND1NBWaEwjC3pgpm --- src/data/projects.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/projects.json b/src/data/projects.json index 3148e47..2a171e8 100644 --- a/src/data/projects.json +++ b/src/data/projects.json @@ -8,7 +8,7 @@ "tech": ["TypeScript", "Cloudflare Workers", "Durable Objects", "Cloudflare D1", "Workers KV", "Cloudflare Containers", "Docker", "Cache API", "Hono", "Web Push / VAPID", "WebCrypto", "Resend", "TfNSW Open Data", "GTFS", "Vitest", "Playwright"], "period": "July 2026 — August 2026", "order": 1, - "content": "

Overview

\n

It's been nothing short of an exceptional first two months at Gyoza Flights! Jumping into the world of aviation has been super interesting and it has shifted my thinking as both an engineer and person. A recent example of this was the other day I was standing at Wynyard Station waiting for a train to Town Hall. My train of thought immediately drifted to origins and destinations. How my destination of Cronulla was all I really cared about and knowing when the next couple of trains would be departing. Sure I could go onto one of the various apps available and check but with my engineering brain I questioned if that was actually necessary. And did it cover what I really wanted? Sometimes I work back in the office, sometimes I leave early. I'm only in the office a few days a week and some of those are fortnightly. So as I stood and waited for my train an idea started to percolate. What if I just had a website where people could just choose an origin and destination, set a lead time, a window to receive the alerts, a type of alert (email or push notification) and then set and forget. Then they'd automatically get these alerts to their device and would be able to proactively determine when they should leave their office to make a train that suits what time they were thinking of leaving on that day. And that is how sydneytrainalerts.com was born.

\n

I build on Cloudflare constantly and my tennis calendar is still quietly doing its thing from a Worker right now, so I had a fair idea of what was sitting there already. Then I remembered the alarm on Durable Objects, where a tiny stateful object can put itself to sleep and wake at an exact moment. That's precisely the shape of \"ping me two minutes before each train\", so that's what I built it on. You pick a station, how much notice you want, the days and window you travel in and whether you want the alert by push or email. From then on it watches the live departures and nudges you before each train you could catch. Once you're on, you tap \"I'm on board\" and it stays quiet until your next window.

\n\"The\n\n

How it works

\n

The data comes from Transport for NSW's Open Data platform. Their Trip Planner API has a Departure Monitor endpoint that hands back the upcoming departures from a stop as plain JSON with the realtime estimates already merged in, so there's no GTFS protobuf wrangling at all. A second endpoint, Stop Finder, powers the station autocomplete on the form. The free tier gives you 60,000 calls a day which is heaps for a personal tool like this.

\n\"The\n

The part I really wanted to get right is the timing. Every alert is its own little Cloudflare Durable Object that watches your station through your travel window, follows the live delays and nudges you before each train you could catch. Once it's pinged you about one it lists the next couple behind it too, so if you miss the 5:12 you already know the 5:22 is coming. Getting that to actually work is the most interesting thing in the whole build, so I've given it its own section below.

\n\"Choosing\n

Schedules are Sydney wall-clock time with travel days, a start and end for the window and even fortnightly days for rosters that alternate. The subscriptions themselves live in a D1 database, which stays the source of truth. A cron fires every 15 minutes purely as a safety net so nothing gets stranded after a deploy. Once you're on your train you tap \"I'm on board\" and it goes quiet for the rest of that window, then picks back up on your next travel day. That pause sits on the manage page for every alert as well as on the notification itself.

\n\"The\n

One thing I had to be careful about is staying inside that free tier once more than a handful of people are using it. TfNSW's free plan also caps you at about 5 calls a second. It starts knocking requests back the moment you push past that. So the departure lookups run through Cloudflare's cache, keyed on the station and the minute. If ten people are all watching Town Hall at 5:31 they mostly land on the same cached answer instead of making ten separate calls. If TfNSW ever does rate-limit me the alerts back off and give it room instead of hammering away. I also sprinkle a bit of random jitter on the wake-ups so a deploy doesn't fire every alert at the same second and trip the limit myself. Since then I've added a counter in D1 that tallies every call that genuinely leaves for TfNSW, keyed on the Sydney day the quota actually resets on rather than UTC. It emails me as I cross 50, 75, 90 and 100% of the daily allowance. I'd rather find out I'm running hot from my inbox in the morning than from a window of alerts that quietly didn't go out. None of it is glamorous but it's what lets one free key quietly handle a proper crowd of commuters.

\n

Delivery is push or email. Push is the default now and that swap was worth doing. An email subscriber costs a send for every matching train against a provider cap I share across both sites, where a push message costs nothing and skips the confirmation email entirely, because granting the browser permission is already proof you own the device. Push started out as the second radio button and unticked, so almost everyone was taking the expensive channel without ever thinking about it. The form still flips itself back to email on any browser where push isn't a single tap, uninstalled iPhone Safari especially, so nobody lands on an Add to Home Screen wall when they were just trying to fill in a form. Email goes through Resend with a manage link in every message so you can pause or cancel from the alert itself. If the push service ever reports a subscription as gone, the alert deactivates itself rather than pushing into the void forever. The frontend is one dependency-free page, no framework, with the autocomplete, a live preview of the alert you're building and a proper install walkthrough for iPhones, because iOS will only deliver web push to a site that's been added to the home screen. It started at about 2,000 lines and it's around 6,500 now, still with exactly one runtime dependency, Hono for the routing.

\n

The thing I never planned on building was a second site. Ferries turn out to be the exact same problem with different nouns, so instead of forking the lot I made the mode a property of the request. The Worker reads the Host header, picks up the config for that brand and serves sydneyferryalerts.com off the same code, the same database and the same deploy as the trains site. That mode then gets stamped onto each subscription, because a Durable Object waking up on an alarm hours later has no request sitting there to read a hostname off. Everything that differs between the two lives in one config object, the brand and the homepage copy and whether you're waiting on a platform or a wharf. A third mode is one more entry in it.

\n\n

The Cool Nerd Part

\n

Right, this is the bit I actually get excited about. Before I committed to building it this way I sat down and properly grilled the design, because \"give every single alert its own little server that never really sleeps\" sounds mad when you first say it out loud. The more I poked at it though the more Durable Objects turned out to be the perfect fit, so let me walk through why.

\n

The timing first. Your train is meant to leave at 5:12 so a two minute warning should fire at 5:10. Then it runs four minutes late and the warning needs to fire at 5:14 instead. The target keeps moving all afternoon. A Durable Object has this thing called an alarm, which is basically a single built-in timer the object can point at any exact moment in the future. Better still, it survives restarts and deploys. So every time the alert wakes up and checks the live departures it works out the new fire time from the latest estimate and re-points its own alarm at it. The 5:12 slips to 5:14 and the alarm slides right along with it. It's an alert that reschedules itself off the delay. A plain cron can't do that. The finest a cron will give you is once a minute, so you'd be waking every minute, pulling every subscription and still landing up to a minute wide of a target that won't sit still.

\n

Then there's the fact that each alert is its own object with its own name. The trick with Durable Objects is that you address one by a string. I just use the subscription's id as that string. So wherever I am in the code, whether it's the subscribe request, the cancel button, the \"I'm on board\" tap or the safety-net cron, I hand it the same id and I'm always talking to the exact same object holding that one alert's state. No lookup table, no routing, no working out which server a subscription lives on. The id is the address. And because each object only ever holds its own alert, one person's alert going sideways can't touch anyone else's.

\n

Here's the one that really sold me though. A Durable Object only ever does one thing at a time. It won't run two requests against the same object at once, it queues them up. That sounds like a small detail but it's the whole reason the tricky part of this app is even doable, which brings me to the messy bit.

\n

A single wake-up isn't instant. The object has to go out over the network to TfNSW for the live departures. Then it has to actually send your push or your email. Both of those take real time. Now picture the alert half way through that, sitting there waiting on the departures to come back. Right at that moment you tap \"cancel\" or \"I'm on board\". Because the object does one thing at a time your tap can't barge in mid-step, but it does get its turn the instant the alert pauses to wait on the network. So it flips the state out from under the rest of the run. The alert wakes back up thinking everything's fine and, if I'm not careful, cheerfully sends you a notification for an alert you just cancelled.

\n

The honest answer to that is a lot of little if-checks. The wake-up loop is peppered with early exits that all ask the same question in different spots: has this been cancelled or paused while I wasn't looking? Bail out if it has. There's one on the very first line before it does anything. There's one straight after the departures come back from TfNSW. There's one right before it sends each notification and another right after. There's even one tucked inside the final save, so if a cancel landed while the alert was mid-flight, the moment it goes to write \"all done, wake me in 45 seconds\" it checks one last time and just doesn't, because arming a fresh timer on an alert you've killed is exactly how you end up haunted by a cancelled alert that won't die. Cancelling also leaves a little durable marker behind, a tombstone, so the safety-net cron can't come past later, spot an alert with no timer set and helpfully resurrect the very thing you just deleted.

\n

It's not the most elegant code I've ever written and there are more of those guards than I'd like. But every single one of them is a real race I could sit and describe to you. And the reason they get to be simple little if-blocks instead of a knot of locks is that one-thing-at-a-time promise. Without Durable Objects I'd be running one big loop over everyone's alerts, all of them fighting over the same database rows with a proper lock bolted on so a cancel and a poll couldn't clobber each other. With a Durable Object the isolation is the lock. Each alert ends up as a little self-winding alarm clock, chasing the train's live delay, costing nothing while it's asleep between your commutes and never fighting anyone for its own state, because it's the only thing that ever touches it.

\n\n

Sticking points

\n

The API that swore every station was invalid

\n

Early on the station search flat out refused to work. Every query came back with zero locations and a \"stop invalid\" message, even for Central. The culprit was one innocent looking parameter. Stop Finder takes a type_sf field and I'd set it to stop, because I was searching for stops. It turns out type_sf=stop actually means \"the text I'm sending is a stop ID\", so the broker was taking the word Cronulla and trying to look it up as a numeric station identifier. The fix was type_sf=any and then filtering the mixed bag of streets and points of interest down to actual stops. I only caught it because I verified the client against the live API instead of trusting the docs. The mock TfNSW server my Playwright tests drive now mirrors that exact broker behaviour, so this one can never quietly come back.

\n

Daylight saving and the missing hour

\n

Active windows are Sydney local time and my first version of \"when does today's window start\" was simple minute arithmetic from the current moment. An adversarial review pass over the whole codebase caught that the simple version drifts by exactly an hour whenever the calculation straddles the 2am daylight saving changeover, which in the worst case means a whole morning of missed alerts. The fix was to stop doing arithmetic and start constructing the actual Sydney wall time, using Intl with the Australia/Sydney timezone to check the guess and correct it. I reckon DST bugs are the sneakiest kind there is, because the broken code is provably correct for 363 days of the year.

\n

The alert that sent people the wrong way

\n

The \"Towards\" box is meant to be a light direction filter. Say you're leaving Town Hall heading home to Cronulla, you pop Cronulla in and the alert only bothers you about trains actually going your way. The catch is that a train only really tells you its final stop, so a service that passes through your destination on the way to somewhere else doesn't always say your stop's name. The filter would match nothing. My first version handled that by failing open, so when nothing matched it just alerted you about everything, on the theory that too many alerts beats none. That turned out to be genuinely annoying. On a quiet poll with no Cronulla train in sight it would happily ping me about trains heading the complete opposite way, up to Berowra and out to Emu Plains. The fix was to give the filter a memory. The first time a real matching train shows up it quietly notes that this direction does work here. From then on a poll with no match means go quiet, not alert about everything. It only falls back to the noisy everything-goes behaviour for a destination that has never once matched, which is the genuine can't-tell case. I still reckon failing open was the right instinct. It just shouldn't have been what happens every time. That felt like the end of it. It really wasn't.

\n

Web push from scratch, byte for byte

\n

The web push libraries everyone reaches for are built for Node and lean on APIs a Worker doesn't have. Rather than fight that, I wrote the push layer from scratch on WebCrypto. That means two RFCs. VAPID (RFC 8292), an ES256 signed JWT that proves to Apple's or Google's push service the message really came from my server. Then the payload encryption (RFC 8291), a chain of ECDH key agreement, HKDF derivations and AES-128-GCM. Hand rolling crypto is nerve-racking because the failure mode is silence, the push service just drops anything malformed and tells you nothing. The saving grace is that the encryption RFC ships a complete worked example with every intermediate value spelled out. So I made the encrypt function accept an injectable random seed and wrote a test that reproduces that worked example byte for byte. When it went green I knew the scariest part of the codebase was actually correct rather than just plausible.

\n\n

The plot twist

\n

So I shipped that direction fix and moved on, pretty pleased with myself. A few weeks later I finally sat down to put a number on how often the filter actually matched, which I'd never once measured. I pulled the real departure board for Town Hall on a Monday afternoon, 80 trains across the shipped default window, then checked it against six destinations a normal person would genuinely type in. Here's the twist. A headsign only ever tells you where a train finishes up. It says nothing at all about the twenty odd stations it calls at on the way there. Only 4 of those 80 trains terminate at Parramatta. 20 of them actually stop at Parramatta. So a Parramatta subscriber was being told about 4 trains a window and never hearing a whisper about the other 16. Hurstville was 5 against 15. My tidy little fail-closed fix wasn't filtering trains out at all, it was silently dropping trains those people could have caught. And because going quiet looks exactly the same as there being nothing to say, they'd never have a reason to suspect it.

\n

The other branch was just as broken in the opposite direction. Redfern and Wynyard never match a headsign from Town Hall, not one of the 80, so the filter never got to learn that the direction works there and sat in the noisy everything-goes branch forever, pinging those subscribers 79 times a window. I widened the measurement out to 56 plausible Sydney destinations and depending where you start from only 30 to 45% of them are ever a headsign. Blacktown, Sutherland, Lidcombe, Bankstown, Museum, Wynyard and Redfern are all completely ordinary places to be heading home to and not one of them is a headsign from Town Hall. Both halves of my filter were wrong and they were wrong for the same reason. The signal was wrong. I'd spent two whole rounds carefully tuning what to do when the match failed. The actual bug was the question I was asking.

\n

So the fix was to stop asking whether the destination is the last stop and start asking the only thing that actually matters. Does this train stop where I'm going? TfNSW's live departure board hands back a gtfsTripId on nearly every service and the static GTFS timetable carries the full calling pattern for every trip, so if those two join I get the real list of stations instead of a guess off the front of the train. They don't join directly, which took a whole investigation to establish. That id isn't a real trip id in any feed TfNSW publishes, not even the combined one. It isn't opaque either though. The route part of it reconstructs exactly to a route_id in the complete feed. Route plus origin station plus departure minute then pins the trip right down. 18 out of 18 live departures reconciled on the first proper run. That was a very good afternoon.

\n

None of that fits on a Worker. The complete feed is 293 MB zipped and 1.45 GB extracted against a 128 MB isolate, so the heavy lifting runs on my laptop instead. A script pulls both feeds down, works out the onward calling pattern for every trip and boils the whole network into one small pattern shard per origin station, then publishes them into Workers KV. The first build came out at 247 stations and 18,090 trips indexed for about 7 MB all up. At alert time the Durable Object reads only its own station's shard, looks each departure up by route and minute and keeps the train if the destination is somewhere in what's left of its stop list. Everything hard gets resolved once, on the laptop, in code I can write proper tests against. The Worker ends up holding exactly one idea. The stations this service calls at after this one.

\n

The genuinely nasty bit was through-running. Sydney Trains sends a lot of services straight through the City and the static timetable models the approach leg as a trip that terminates at Central, while the live board is already advertising the eventual destination. Read that literally and you get a false negative, which is worse than the thing I was replacing. GTFS has a block_id that links the trips worked by the same physical train. Sure enough the continuation leg leaves Central exactly one second after the approach leg arrives. Stitch the two and a trip that looked like it stopped at Central becomes the real 49 stop run. 1,671 of 1,671 Central-terminating trips on those lines had a block mate waiting for them. Without the stitching Cronulla would have gone from 5 alerts to 16, so it was required rather than a nice to have. I keyed the block on the service id as well as the block id too, because Sydney Trains reuses block ids across weekday and Saturday patterns whose join times happen to line up. That would have stamped a weekday timetable onto a Saturday train. A confident wrong yes, which is the worst kind, because nothing at runtime can catch it.

\n

The rule I care about most in this whole thing is that absence is never a no. Destination missing from the shard, a schema version I don't recognise, the published dates running out, a KV read timing out, every one of those returns don't know and falls back to the old headsign matcher rather than deciding no. A wrong no is silent permanent non-delivery and that's the only failure in here a user can't recover from on their own. The acceptance run I was holding out for finally landed in a real weekday peak window, courtesy of a little scheduled job on my laptop taking the measurement at 4:52pm every weekday because I'm hardly ever at a keyboard at exactly the right minute. Town Hall came back with 80 out of 80 departures covered by the shards. My own Cronulla alert landed on 5 trains for the window, right where the earlier measurements said it should be. So I flipped it on. The stopping-pattern filter has been live for every train alert since early August and the rollback story is as boring as I could make it. Deleting the shard keys out of KV turns the whole thing off in seconds, no deploy needed. Ferries stay on the headsign path for now. Their wharf ids don't map into the ferry GTFS feed at all, which is a whole separate rabbit hole for another week.

\n

The laptop was never the long-term plan though. It was fine while I was proving the idea, running the build by hand in test runs and eyeballing the numbers before each publish, but I knew the whole time that if I wanted this to be properly automatic the build had to come off my machine and live in the cloud somewhere. A script that only runs when I remember to run it isn't automation. The shards even gave me a deadline, because each one deliberately carries just eight days of timetable, today plus a week, so that a stale build degrades gracefully instead of serving wrong answers forever. Sure enough the build from the 3rd of August covered up to the 10th, so on the 11th every train alert quietly aged out of its shard and spent the whole day back on the old headsign matcher. Nothing broke, which is exactly how I designed it. Honestly that almost made it worse. Alerts kept arriving, just fewer of them. The canary I'd built to spot stale shards was only writing a line into a log I wasn't reading, so production ran thin all day before I rebuilt the shards by hand that evening. That was the nudge I needed to stop putting the move off.

\n

My first thought for a cloud home was GitHub Actions and I went off it for two reasons. It would need a Cloudflare API token with write access to KV sitting in GitHub's secrets, a brand new credential to mint and then worry about forever. On top of that GitHub quietly switches off scheduled workflows when a repo goes inactive, which for a set-and-forget project is exactly the kind of silent stop I was trying to get rid of. Then I remembered Cloudflare had shipped Containers, where you attach a real Docker container to a Worker for the jobs an isolate can't handle. That was the lightbulb moment. The 1.45 GB extract that pushed this build onto my laptop in the first place stops mattering the moment the build gets a proper machine of its own.

\n

Here's the bit I love about how Containers work. You address the container through a Durable Object. The same one-object-one-address idea that runs every alert in this app also runs the build machine. It only ever exists while it's working. A second cron in the same Worker fires once a day at midday and wakes the container. The container pulls both GTFS feeds down, chews through them with the exact same build code my laptop was running and hands the finished shards back over HTTP. Then comes my favourite part. The Worker writes the shards into KV itself through the binding it already owns, so the container never touches storage and holds no credentials at all. Automating the whole pipeline added zero new secrets anywhere. The first deploy did bounce because Containers need the paid Workers plan, so this project now costs me five dollars a month plus about five cents of actual container time. For a build that runs itself every day, I reckon that's a bargain.

\n

Then the very first end-to-end run failed and honestly I couldn't have asked for a better failure. The build indexed 9,133 trips where the previous day's run had found over 20,000, so one of the assertion gates I'd wired through the pipeline looked at that number and refused to publish. The investigation turned up something I'd never have guessed. The two TfNSW feeds run on completely different clocks. The Sydney Trains operator feed regenerates at about 1am every morning like clockwork. The complete feed only republishes when something changes and I caught it nearly seven hours stale that morning, with one past incident lagging almost 44 hours. The trip ids carry a day counter that ticks up with each generation, so whenever the two feeds sit a generation apart the join between them collapses and half the trips silently fall out of the build.

\n

Two things came out of that. The daily rebuild moved from the small hours to midday, well clear of that 1am regeneration. And the gate itself went from box-ticking to being the hero of the story, because publishing that half-empty bundle would have been far worse than publishing nothing. A trip that's missing from the shard doesn't look missing at match time. It looks like a train that doesn't stop where you're going, which is the silent non-delivery I'd already ranked as the one unforgivable failure in this system. So here's a tip if you're building a pipeline whose output other code will trust. Give the build a gate on its own numbers and let it refuse loudly. Mine caught a genuine upstream problem on its very first run and the old shards simply kept serving until the feeds caught back up to each other.

\n

The last piece was making sure every failure lands in my inbox rather than a log, since a log-only canary had already burned me once. The staleness canary emails me now. A failed rebuild emails me too, naming the gate that tripped and the number it measured. Ops emails are capped at one a day so a bad week can't bury me. Best of all, every one of those emails carries the actual fix inside it, a curl command against a new authenticated admin route that kicks off a rebuild on demand. There's a fun Workers quirk in that route too. A Worker only gets about 30 seconds of background time after it responds, nowhere near enough for this build, but there's no wall-clock limit while the client stays connected. So the route just holds the connection open until the build finishes and then answers with the result.

\n

That admin route is exactly how the first real production rebuild ran. It published 239 station shards, swept up 8 stale ones an older build had left behind and came back in 67 seconds. That number was its own little lesson too, because every estimate along the way had confidently said the build takes about ten minutes. The very last pull request in this whole saga was me going back through the emails and docs to correct that claim to what production actually measured. So the timer on this feature is no longer wound by hand. I reckon that's the real finish line for a side project like this, the moment it keeps working when you're not looking at it.

\n\n

The result

\n

I couldn't be happier with how this one came out! The first build took a couple of days with Claude Code alongside me and it's grown up a fair bit since it went live. The caching that keeps it inside the free tier, the ferry site, the whole stopping-pattern rework and the daily container rebuild that keeps its timetable data fresh all landed after real people started using it, which is by far the best way to find that sort of thing. My phone buzzes two minutes before the train actually leaves, the alert already knows it's running late and one tap on \"I'm on board\" keeps everything quiet until my next travel day. I don't open the app, I don't refresh anything and I've stopped standing on platforms waiting. It's all covered end to end, Vitest across the schedule and crypto logic and Playwright driving the real Worker against a mock TfNSW upstream, so I can keep tweaking it without holding my breath. It's live at sydneytrainalerts.com, so if you're a Sydney commuter you're welcome to set one up of your own. There's a blog on each site too, nine posts apiece, to help people find them.

\n

Two things I'd pass on from this one. If you've built yourself a heuristic and you're onto your second round of tuning what it should do when it misses, stop and go measure how often the thing actually hits. Mine was landing 30 to 45% of the time and I'd never checked it once. Then if you're thinking about doing web push yourself, here's the tip I wish I'd had on day one. Don't test your encryption by firing real notifications and hoping. Make the random inputs injectable and verify your output against the worked example in RFC 8291 first, because the push services silently drop anything malformed and you'll get no clue why. Once that test passes, everything downstream is just plumbing. Happy coding!

" + "content": "

Overview

\n

It's been nothing short of an exceptional first two months at Gyoza Flights! Jumping into the world of aviation has been super interesting and it has shifted my thinking as both an engineer and person. A recent example of this was the other day I was standing at Wynyard Station waiting for a train to Town Hall. My train of thought immediately drifted to origins and destinations. How my destination of Cronulla was all I really cared about and knowing when the next couple of trains would be departing. Sure I could go onto one of the various apps available and check but with my engineering brain I questioned if that was actually necessary. And did it cover what I really wanted? Sometimes I work back in the office, sometimes I leave early. I'm only in the office a few days a week and some of those are fortnightly. So as I stood and waited for my train an idea started to percolate. What if I just had a website where people could just choose an origin and destination, set a lead time, a window to receive the alerts, a type of alert (email or push notification) and then set and forget. Then they'd automatically get these alerts to their device and would be able to proactively determine when they should leave their office to make a train that suits what time they were thinking of leaving on that day. And that is how sydneytrainalerts.com was born.

\n

I build on Cloudflare constantly and my tennis calendar is still quietly doing its thing from a Worker right now, so I had a fair idea of what was sitting there already. Then I remembered the alarm on Durable Objects, where a tiny stateful object can put itself to sleep and wake at an exact moment. That's precisely the shape of \"ping me two minutes before each train\", so that's what I built it on. You pick a station, how much notice you want, the days and window you travel in and whether you want the alert by push or email. From then on it watches the live departures and nudges you before each train you could catch. Once you're on, you tap \"I'm on board\" and it stays quiet until your next window.

\n\"The\n\n

How it works

\n

The data comes from Transport for NSW's Open Data platform. Their Trip Planner API has a Departure Monitor endpoint that hands back the upcoming departures from a stop as plain JSON with the realtime estimates already merged in, so there's no GTFS protobuf wrangling at all. A second endpoint, Stop Finder, powers the station autocomplete on the form. The free tier gives you 60,000 calls a day which is heaps for a personal tool like this.

\n\"The\n

The part I really wanted to get right is the timing. Every alert is its own little Cloudflare Durable Object that watches your station through your travel window, follows the live delays and nudges you before each train you could catch. Once it's pinged you about one it lists the next couple behind it too, so if you miss the 5:12 you already know the 5:22 is coming. Getting that to actually work is the most interesting thing in the whole build, so I've given it its own section below.

\n\"Choosing\n

Schedules are Sydney wall-clock time with travel days, a start and end for the window and even fortnightly days for rosters that alternate. The subscriptions themselves live in a D1 database, which stays the source of truth. A cron fires every 15 minutes purely as a safety net so nothing gets stranded after a deploy. Once you're on your train you tap \"I'm on board\" and it goes quiet for the rest of that window, then picks back up on your next travel day. That pause sits on the manage page for every alert as well as on the notification itself.

\n\"The\n

One thing I had to be careful about is staying inside that free tier once more than a handful of people are using it. TfNSW's free plan also caps you at about 5 calls a second. It starts knocking requests back the moment you push past that. So the departure lookups run through Cloudflare's cache, keyed on the station and the minute. If ten people are all watching Town Hall at 5:31 they mostly land on the same cached answer instead of making ten separate calls. If TfNSW ever does rate-limit me the alerts back off and give it room instead of hammering away. I also sprinkle a bit of random jitter on the wake-ups so a deploy doesn't fire every alert at the same second and trip the limit myself. Since then I've added a counter in D1 that tallies every call that genuinely leaves for TfNSW, keyed on the Sydney day the quota actually resets on rather than UTC. It emails me as I cross 50, 75, 90 and 100% of the daily allowance. I'd rather find out I'm running hot from my inbox in the morning than from a window of alerts that quietly didn't go out. None of it is glamorous but it's what lets one free key quietly handle a proper crowd of commuters.

\n

Delivery is push or email. Push is the default now and that swap was worth doing. An email subscriber costs a send for every matching train against a provider cap I share across both sites, where a push message costs nothing and skips the confirmation email entirely, because granting the browser permission is already proof you own the device. Push started out as the second radio button and unticked, so almost everyone was taking the expensive channel without ever thinking about it. The form still flips itself back to email on any browser where push isn't a single tap, uninstalled iPhone Safari especially, so nobody lands on an Add to Home Screen wall when they were just trying to fill in a form. Email goes through Resend with a manage link in every message so you can pause or cancel from the alert itself. If the push service ever reports a subscription as gone, the alert deactivates itself rather than pushing into the void forever. The frontend is one dependency-free page, no framework, with the autocomplete, a live preview of the alert you're building and a proper install walkthrough for iPhones, because iOS will only deliver web push to a site that's been added to the home screen. It started at about 2,000 lines and it's past 8,000 now. The Worker itself still only carries two runtime dependencies, Hono for the routing and the small Cloudflare helper that drives the rebuild container.

\n

The thing I never planned on building was a second site. Ferries turn out to be the exact same problem with different nouns, so instead of forking the lot I made the mode a property of the request. The Worker reads the Host header, picks up the config for that brand and serves sydneyferryalerts.com off the same code, the same database and the same deploy as the trains site. That mode then gets stamped onto each subscription, because a Durable Object waking up on an alarm hours later has no request sitting there to read a hostname off. Everything that differs between the two lives in one config object, the brand and the homepage copy and whether you're waiting on a platform or a wharf. A third mode is one more entry in it.

\n\n

The Cool Nerd Part

\n

Right, this is the bit I actually get excited about. Before I committed to building it this way I sat down and properly grilled the design, because \"give every single alert its own little server that never really sleeps\" sounds mad when you first say it out loud. The more I poked at it though the more Durable Objects turned out to be the perfect fit, so let me walk through why.

\n

The timing first. Your train is meant to leave at 5:12 so a two minute warning should fire at 5:10. Then it runs four minutes late and the warning needs to fire at 5:14 instead. The target keeps moving all afternoon. A Durable Object has this thing called an alarm, which is basically a single built-in timer the object can point at any exact moment in the future. Better still, it survives restarts and deploys. So every time the alert wakes up and checks the live departures it works out the new fire time from the latest estimate and re-points its own alarm at it. The 5:12 slips to 5:14 and the alarm slides right along with it. It's an alert that reschedules itself off the delay. A plain cron can't do that. The finest a cron will give you is once a minute, so you'd be waking every minute, pulling every subscription and still landing up to a minute wide of a target that won't sit still.

\n

Then there's the fact that each alert is its own object with its own name. The trick with Durable Objects is that you address one by a string. I just use the subscription's id as that string. So wherever I am in the code, whether it's the subscribe request, the cancel button, the \"I'm on board\" tap or the safety-net cron, I hand it the same id and I'm always talking to the exact same object holding that one alert's state. No lookup table, no routing, no working out which server a subscription lives on. The id is the address. And because each object only ever holds its own alert, one person's alert going sideways can't touch anyone else's.

\n

Here's the one that really sold me though. A Durable Object only ever does one thing at a time. It won't run two requests against the same object at once, it queues them up. That sounds like a small detail but it's the whole reason the tricky part of this app is even doable, which brings me to the messy bit.

\n

A single wake-up isn't instant. The object has to go out over the network to TfNSW for the live departures. Then it has to actually send your push or your email. Both of those take real time. Now picture the alert half way through that, sitting there waiting on the departures to come back. Right at that moment you tap \"cancel\" or \"I'm on board\". Because the object does one thing at a time your tap can't barge in mid-step, but it does get its turn the instant the alert pauses to wait on the network. So it flips the state out from under the rest of the run. The alert wakes back up thinking everything's fine and, if I'm not careful, cheerfully sends you a notification for an alert you just cancelled.

\n

The honest answer to that is a lot of little if-checks. The wake-up loop is peppered with early exits that all ask the same question in different spots: has this been cancelled or paused while I wasn't looking? Bail out if it has. There's one on the very first line before it does anything. There's one straight after the departures come back from TfNSW. There's one right before it sends each notification and another right after. There's even one tucked inside the final save, so if a cancel landed while the alert was mid-flight, the moment it goes to write \"all done, wake me in 45 seconds\" it checks one last time and just doesn't, because arming a fresh timer on an alert you've killed is exactly how you end up haunted by a cancelled alert that won't die. Cancelling also leaves a little durable marker behind, a tombstone, so the safety-net cron can't come past later, spot an alert with no timer set and helpfully resurrect the very thing you just deleted.

\n

It's not the most elegant code I've ever written and there are more of those guards than I'd like. But every single one of them is a real race I could sit and describe to you. And the reason they get to be simple little if-blocks instead of a knot of locks is that one-thing-at-a-time promise. Without Durable Objects I'd be running one big loop over everyone's alerts, all of them fighting over the same database rows with a proper lock bolted on so a cancel and a poll couldn't clobber each other. With a Durable Object the isolation is the lock. Each alert ends up as a little self-winding alarm clock, chasing the train's live delay, costing nothing while it's asleep between your commutes and never fighting anyone for its own state, because it's the only thing that ever touches it.

\n\n

Sticking points

\n

The API that swore every station was invalid

\n

Early on the station search flat out refused to work. Every query came back with zero locations and a \"stop invalid\" message, even for Central. The culprit was one innocent looking parameter. Stop Finder takes a type_sf field and I'd set it to stop, because I was searching for stops. It turns out type_sf=stop actually means \"the text I'm sending is a stop ID\", so the broker was taking the word Cronulla and trying to look it up as a numeric station identifier. The fix was type_sf=any and then filtering the mixed bag of streets and points of interest down to actual stops. I only caught it because I verified the client against the live API instead of trusting the docs. The mock TfNSW server my Playwright tests drive now mirrors that exact broker behaviour, so this one can never quietly come back.

\n

Daylight saving and the missing hour

\n

Active windows are Sydney local time and my first version of \"when does today's window start\" was simple minute arithmetic from the current moment. An adversarial review pass over the whole codebase caught that the simple version drifts by exactly an hour whenever the calculation straddles the 2am daylight saving changeover, which in the worst case means a whole morning of missed alerts. The fix was to stop doing arithmetic and start constructing the actual Sydney wall time, using Intl with the Australia/Sydney timezone to check the guess and correct it. I reckon DST bugs are the sneakiest kind there is, because the broken code is provably correct for 363 days of the year.

\n

The alert that sent people the wrong way

\n

The \"Towards\" box is meant to be a light direction filter. Say you're leaving Town Hall heading home to Cronulla, you pop Cronulla in and the alert only bothers you about trains actually going your way. The catch is that a train only really tells you its final stop, so a service that passes through your destination on the way to somewhere else doesn't always say your stop's name. The filter would match nothing. My first version handled that by failing open, so when nothing matched it just alerted you about everything, on the theory that too many alerts beats none. That turned out to be genuinely annoying. On a quiet poll with no Cronulla train in sight it would happily ping me about trains heading the complete opposite way, up to Berowra and out to Emu Plains. The fix was to give the filter a memory. The first time a real matching train shows up it quietly notes that this direction does work here. From then on a poll with no match means go quiet, not alert about everything. It only falls back to the noisy everything-goes behaviour for a destination that has never once matched, which is the genuine can't-tell case. I still reckon failing open was the right instinct. It just shouldn't have been what happens every time. That felt like the end of it. It really wasn't.

\n

Web push from scratch, byte for byte

\n

The web push libraries everyone reaches for are built for Node and lean on APIs a Worker doesn't have. Rather than fight that, I wrote the push layer from scratch on WebCrypto. That means two RFCs. VAPID (RFC 8292), an ES256 signed JWT that proves to Apple's or Google's push service the message really came from my server. Then the payload encryption (RFC 8291), a chain of ECDH key agreement, HKDF derivations and AES-128-GCM. Hand rolling crypto is nerve-racking because the failure mode is silence, the push service just drops anything malformed and tells you nothing. The saving grace is that the encryption RFC ships a complete worked example with every intermediate value spelled out. So I made the encrypt function accept an injectable random seed and wrote a test that reproduces that worked example byte for byte. When it went green I knew the scariest part of the codebase was actually correct rather than just plausible.

\n\n

The plot twist

\n

So I shipped that direction fix and moved on, pretty pleased with myself. A few weeks later I finally sat down to put a number on how often the filter actually matched, which I'd never once measured. I pulled the real departure board for Town Hall on a Monday afternoon, 80 trains across the shipped default window, then checked it against six destinations a normal person would genuinely type in. Here's the twist. A headsign only ever tells you where a train finishes up. It says nothing at all about the twenty odd stations it calls at on the way there. Only 4 of those 80 trains terminate at Parramatta. 20 of them actually stop at Parramatta. So a Parramatta subscriber was being told about 4 trains a window and never hearing a whisper about the other 16. Hurstville was 5 against 15. My tidy little fail-closed fix wasn't filtering trains out at all, it was silently dropping trains those people could have caught. And because going quiet looks exactly the same as there being nothing to say, they'd never have a reason to suspect it.

\n

The other branch was just as broken in the opposite direction. Redfern and Wynyard never match a headsign from Town Hall, not one of the 80, so the filter never got to learn that the direction works there and sat in the noisy everything-goes branch forever, pinging those subscribers 79 times a window. I widened the measurement out to 56 plausible Sydney destinations and depending where you start from only 30 to 45% of them are ever a headsign. Blacktown, Sutherland, Lidcombe, Bankstown, Museum, Wynyard and Redfern are all completely ordinary places to be heading home to and not one of them is a headsign from Town Hall. Both halves of my filter were wrong and they were wrong for the same reason. The signal was wrong. I'd spent two whole rounds carefully tuning what to do when the match failed. The actual bug was the question I was asking.

\n

So the fix was to stop asking whether the destination is the last stop and start asking the only thing that actually matters. Does this train stop where I'm going? TfNSW's live departure board hands back a gtfsTripId on nearly every service and the static GTFS timetable carries the full calling pattern for every trip, so if those two join I get the real list of stations instead of a guess off the front of the train. They don't join directly, which took a whole investigation to establish. That id isn't a real trip id in any feed TfNSW publishes, not even the combined one. It isn't opaque either though. The route part of it reconstructs exactly to a route_id in the complete feed. Route plus origin station plus departure minute then pins the trip right down. 18 out of 18 live departures reconciled on the first proper run. That was a very good afternoon.

\n

None of that fits on a Worker. The complete feed is 293 MB zipped and 1.45 GB extracted against a 128 MB isolate, so the heavy lifting started out on my laptop. A script pulled both feeds down, worked out the onward calling pattern for every trip and boiled the whole network into one small pattern shard per origin station, then published them into Workers KV. The first build came out at 247 stations and 18,090 trips indexed for about 7 MB all up. At alert time the Durable Object reads only its own station's shard, looks each departure up by route and minute and keeps the train if the destination is somewhere in what's left of its stop list. Everything hard gets resolved once, at build time, in code I can write proper tests against. The Worker ends up holding exactly one idea. The stations this service calls at after this one.

\n

The genuinely nasty bit was through-running. Sydney Trains sends a lot of services straight through the City and the static timetable models the approach leg as a trip that terminates at Central, while the live board is already advertising the eventual destination. Read that literally and you get a false negative, which is worse than the thing I was replacing. GTFS has a block_id that links the trips worked by the same physical train. Sure enough the continuation leg leaves Central exactly one second after the approach leg arrives. Stitch the two and a trip that looked like it stopped at Central becomes the real 49 stop run. 1,671 of 1,671 Central-terminating trips on those lines had a block mate waiting for them. Without the stitching Cronulla would have gone from 5 alerts to 16, so it was required rather than a nice to have. I keyed the block on the service id as well as the block id too, because Sydney Trains reuses block ids across weekday and Saturday patterns whose join times happen to line up. That would have stamped a weekday timetable onto a Saturday train. A confident wrong yes, which is the worst kind, because nothing at runtime can catch it.

\n

The rule I care about most in this whole thing is that absence is never a no. Destination missing from the shard, a schema version I don't recognise, the published dates running out, a KV read timing out, every one of those returns don't know and falls back to the old headsign matcher rather than deciding no. A wrong no is silent permanent non-delivery and that's the only failure in here a user can't recover from on their own. The acceptance run I was holding out for finally landed in a real weekday peak window, courtesy of a little scheduled job on my laptop taking the measurement at 4:52pm every weekday because I'm hardly ever at a keyboard at exactly the right minute. Town Hall came back with 80 out of 80 departures covered by the shards. My own Cronulla alert landed on 5 trains for the window, right where the earlier measurements said it should be. So I flipped it on. The stopping-pattern filter has been live for every train alert since early August and the rollback story is as boring as I could make it. Deleting the shard keys out of KV turns the whole thing off in seconds, no deploy needed. Ferries stay on the headsign path for now. Their wharf ids don't map into the ferry GTFS feed at all, which is a whole separate rabbit hole for another week.

\n\n

The plot twist, solved for good

\n

The laptop was never the long-term plan though. It was fine while I was proving the idea, running the build by hand in test runs and eyeballing the numbers before each publish, but I knew the whole time that if I wanted this to be properly automatic the build had to come off my machine and live in the cloud somewhere. A script that only runs when I remember to run it isn't automation.

\n

The shards even gave me a deadline, because each one deliberately carries just eight days of timetable, today plus a week, so that a stale build degrades gracefully instead of serving wrong answers forever. Sure enough the build from the 3rd of August covered up to the 10th, so on the 11th every train alert quietly aged out of its shard and spent the whole day back on the old headsign matcher. Nothing broke, which is exactly how I designed it. Honestly that almost made it worse. Alerts kept arriving, just fewer of them. The canary I'd built to spot stale shards was only writing a line into a log I wasn't reading, so production ran thin all day before I rebuilt the shards by hand that evening. That was the nudge I needed to stop putting the move off.

\n

My first thought for a cloud home was GitHub Actions and I went off it for two reasons. It would need a Cloudflare API token with write access to KV sitting in GitHub's secrets, a brand new credential to mint and then worry about forever. On top of that GitHub quietly switches off scheduled workflows when a repo goes inactive, which for a set-and-forget project is exactly the kind of silent stop I was trying to get rid of. Then I remembered Cloudflare had shipped Containers, where you attach a real Docker container to a Worker for the jobs an isolate can't handle. That was the lightbulb moment. The 1.45 GB extract that pushed this build onto my laptop in the first place stops mattering the moment the build gets a proper machine of its own.

\n

Here's the bit I love about how Containers work. You address the container through a Durable Object. The same one-object-one-address idea that runs every alert in this app also runs the build machine. It only ever exists while it's working. A second cron in the same Worker fires once a day at midday and wakes the container. The container pulls both GTFS feeds down, chews through them with the exact same build code my laptop was running and hands the finished shards back over HTTP. Then comes my favourite part. The Worker writes the shards into KV itself through the binding it already owns, so the container never touches storage and holds no credentials at all. Automating the whole pipeline added zero new secrets anywhere. The first deploy did bounce because Containers need the paid Workers plan, so this project now costs me five dollars a month plus about five cents of actual container time. For a build that runs itself every day, I reckon that's a bargain.

\n

Then the very first end-to-end run failed and honestly I couldn't have asked for a better failure. The build indexed 9,133 trips where the previous day's run had found over 20,000, so one of the assertion gates I'd wired through the pipeline looked at that number and refused to publish. The investigation turned up something I'd never have guessed. The two TfNSW feeds run on completely different clocks. The Sydney Trains operator feed regenerates at about 1am every morning like clockwork. The complete feed only republishes when something changes and I caught it nearly seven hours stale that morning, with one past incident lagging almost 44 hours. The trip ids carry a day counter that ticks up with each generation, so whenever the two feeds sit a generation apart the join between them collapses and half the trips silently fall out of the build.

\n

Two things came out of that. The daily rebuild moved from the small hours to midday, well clear of that 1am regeneration. And the gate itself went from box-ticking to being the hero of the story, because publishing that half-empty bundle would have been far worse than publishing nothing. A trip that's missing from the shard doesn't look missing at match time. It looks like a train that doesn't stop where you're going, which is the silent non-delivery I'd already ranked as the one unforgivable failure in this system. So here's a tip if you're building a pipeline whose output other code will trust. Give the build a gate on its own numbers and let it refuse loudly. Mine caught a genuine upstream problem on its very first run and the old shards simply kept serving until the feeds caught back up to each other.

\n

The last piece was making sure every failure lands in my inbox rather than a log, since a log-only canary had already burned me once. The staleness canary emails me now. A failed rebuild emails me too, naming the gate that tripped and the number it measured. Ops emails are capped at one a day so a bad week can't bury me. Best of all, every one of those emails carries the actual fix inside it, a curl command against a new authenticated admin route that kicks off a rebuild on demand. There's a fun Workers quirk in that route too. A Worker only gets about 30 seconds of background time after it responds, nowhere near enough for this build, but there's no wall-clock limit while the client stays connected. So the route just holds the connection open until the build finishes and then answers with the result.

\n

That admin route is exactly how the first real production rebuild ran. It published 239 station shards, swept up 8 stale ones an older build had left behind and came back in 67 seconds. That number was its own little lesson too, because every estimate along the way had confidently said the build takes about ten minutes. The very last pull request in this whole saga was me going back through the emails and docs to correct that claim to what production actually measured. So the timer on this feature is no longer wound by hand. I reckon that's the real finish line for a side project like this, the moment it keeps working when you're not looking at it.

\n\n

The result

\n

I couldn't be happier with how this one came out! The first build took a couple of days with Claude Code alongside me and it's grown up a fair bit since it went live. The caching that keeps it inside the free tier, the ferry site, the whole stopping-pattern rework and the daily container rebuild that keeps its timetable data fresh all landed after real people started using it, which is by far the best way to find that sort of thing. My phone buzzes two minutes before the train actually leaves, the alert already knows it's running late and one tap on \"I'm on board\" keeps everything quiet until my next travel day. I don't open the app, I don't refresh anything and I've stopped standing on platforms waiting. It's all covered end to end, Vitest across the schedule and crypto logic and Playwright driving the real Worker against a mock TfNSW upstream, so I can keep tweaking it without holding my breath. It's live at sydneytrainalerts.com, so if you're a Sydney commuter you're welcome to set one up of your own. There's a blog on each site too, nine posts apiece, to help people find them.

\n

Two things I'd pass on from this one. If you've built yourself a heuristic and you're onto your second round of tuning what it should do when it misses, stop and go measure how often the thing actually hits. Mine was landing 30 to 45% of the time and I'd never checked it once. Then if you're thinking about doing web push yourself, here's the tip I wish I'd had on day one. Don't test your encryption by firing real notifications and hoping. Make the random inputs injectable and verify your output against the worked example in RFC 8291 first, because the push services silently drop anything malformed and you'll get no clue why. Once that test passes, everything downstream is just plumbing. Happy coding!

" }, { "slug": "album-tracker-cli",