From 412ed0605e4516430e78e5fdbb53fff3ca52f4f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 5 Oct 2026 14:41:44 -0400 Subject: [PATCH 01/14] wip-devtools --- key_value_input.go | 88 ++++++++++++++++++ key_value_input_test.go | 65 +++++++++++++ messages_devtools.go | 11 +++ .../snapshots/darwin/key_value_input-dark.png | Bin 0 -> 9219 bytes .../darwin/key_value_input-light.png | Bin 0 -> 8880 bytes 5 files changed, 164 insertions(+) create mode 100644 key_value_input.go create mode 100644 key_value_input_test.go create mode 100644 messages_devtools.go create mode 100644 testdata/snapshots/darwin/key_value_input-dark.png create mode 100644 testdata/snapshots/darwin/key_value_input-light.png diff --git a/key_value_input.go b/key_value_input.go new file mode 100644 index 0000000..7a0beca --- /dev/null +++ b/key_value_input.go @@ -0,0 +1,88 @@ +package mujica + +import ( + "fmt" + "log/slog" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" +) + +// KeyValue is one editable pair; disabled pairs are kept but not used. +type KeyValue struct { + Key, Value string + Enabled bool +} + +// KeyValueInputOptions configure a KeyValueInput. +type KeyValueInputOptions struct { + // KeyPlaceholder and ValuePlaceholder default to "Key" and "Value". + KeyPlaceholder, ValuePlaceholder string + // Label names the editor for assistive technology. + Label string + Disabled bool +} + +// KeyValueInput edits rows of pairs with an enable switch, add and remove. +func KeyValueInput(c *ui.Context, rows *[]KeyValue, opts KeyValueInputOptions) ChoiceResult { + if rows == nil { + panic("mujica: KeyValueInput needs rows") + } + kp, vp := opts.KeyPlaceholder, opts.ValuePlaceholder + if kp == "" { + kp = msg(c, "key_value_input.key") + } + if vp == "" { + vp = msg(c, "key_value_input.value") + } + u := Density(c).Unit() + var r ChoiceResult + r.Element = ui.Column(c).Shrink(0).Gap(u * 2).Disabled(opts.Disabled).Label(opts.Label) + locked := r.Element.IsDisabled() + remove := -1 + r.Element.Children(func() { + for i := range *rows { + kv := &(*rows)[i] + ui.Row(c).Key(i).Gap(u * 2).AlignItems(ui.Center).Children(func() { + name := fmt.Sprintf(msg(c, "key_value_input.row"), i+1) + on := kv.Enabled + box := Checkbox(c, &on, "", CheckboxOptions{Disabled: locked}).Label(name) + if box.Changed() && !locked { + kv.Enabled, r.changed = on, true + } + k := TextInput(c, &kv.Key, TextInputOptions{Placeholder: kp, Label: name + " " + kp, Disabled: locked}) + k.Element.Grow(1).Basis(0) + v := TextInput(c, &kv.Value, TextInputOptions{Placeholder: vp, Label: name + " " + vp, Disabled: locked}) + v.Element.Grow(2).Basis(0) + r.changed = r.changed || k.Changed() || v.Changed() + if iconAction(c, icons.Must("x"), fmt.Sprintf(msg(c, "key_value_input.remove"), i+1)).Disabled(locked).Clicked() && !locked { + remove = i + } + }) + } + if Button(c, msg(c, "key_value_input.add"), ButtonOptions{Variant: Ghost, Icon: icons.Must("plus"), Disabled: locked}).AlignSelf(ui.Start).Clicked() && !locked { + *rows = append(*rows, KeyValue{Enabled: true}) + r.changed = true + } + }) + if remove >= 0 { + *rows = append((*rows)[:remove:remove], (*rows)[remove+1:]...) + r.changed = true + } + if r.changed { + slog.Debug("mujica: key_value_input change", "rows", len(*rows)) + } + return r +} + +// keyValueEnabled returns the enabled pairs with a key. +func keyValueEnabled(rows []KeyValue) []KeyValue { + var out []KeyValue + for _, kv := range rows { + if kv.Enabled && kv.Key != "" { + out = append(out, kv) + } + } + return out +} diff --git a/key_value_input_test.go b/key_value_input_test.go new file mode 100644 index 0000000..c100bb8 --- /dev/null +++ b/key_value_input_test.go @@ -0,0 +1,65 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +func kvTester(rows *[]KeyValue, opts KeyValueInputOptions, changes *int) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if KeyValueInput(c, rows, opts).Changed() { + *changes++ + } + }, 600, 300) +} + +func TestKeyValueInputEditToggleAddRemove(t *testing.T) { + rows := []KeyValue{{Key: "page", Value: "1", Enabled: true}, {Key: "debug", Value: "true"}} + n := 0 + tt := kvTester(&rows, KeyValueInputOptions{}, &n) + _ = tt.Click("Row 2") // its enable box + if !rows[1].Enabled || n != 1 { + t.Fatalf("toggle: %+v", rows) + } + _ = tt.Click("Add row") + if len(rows) != 3 || !rows[2].Enabled { + t.Fatalf("add: %+v", rows) + } + _ = tt.Click("Row 3 Key") + tt.Type("sort") + tt.Key(0, ui.KeyTab) + tt.Type("name") + if rows[2].Key != "sort" || rows[2].Value != "name" { + t.Fatalf("typing: %+v", rows[2]) + } + _ = tt.Click("Remove row 1") + if len(rows) != 2 || rows[0].Key != "debug" { + t.Fatalf("remove: %+v", rows) + } + if got := keyValueEnabled(rows); len(got) != 2 { + t.Fatalf("enabled pairs: %+v", got) + } +} + +func TestKeyValueInputDisabled(t *testing.T) { + rows := []KeyValue{{Key: "page", Value: "1"}} + n := 0 + tt := kvTester(&rows, KeyValueInputOptions{Disabled: true}, &n) + _ = tt.Click("Row 1") + _ = tt.Click("Add row") + _ = tt.Click("Remove row 1") + _ = tt.Click("Row 1 Key") + tt.Type("x") + if n != 0 || len(rows) != 1 || rows[0].Enabled || rows[0].Key != "page" { + t.Fatalf("disabled editor changed: %+v", rows) + } +} + +func TestKeyValueInputSnapshot(t *testing.T) { + snapshot(t, "key_value_input", 560, 200, func(c *ui.Context) { + rows := []KeyValue{{Key: "Accept", Value: "application/json", Enabled: true}, {Key: "X-Trace", Value: "on"}} + ui.Column(c).Padding(16).Children(func() { KeyValueInput(c, &rows, KeyValueInputOptions{}) }) + }) +} diff --git a/messages_devtools.go b/messages_devtools.go new file mode 100644 index 0000000..9276a76 --- /dev/null +++ b/messages_devtools.go @@ -0,0 +1,11 @@ +package mujica + +func init() { + addMessages(map[string][2]string{ + "key_value_input.key": {"Key", "键"}, + "key_value_input.value": {"Value", "值"}, + "key_value_input.row": {"Row %d", "第 %d 行"}, + "key_value_input.remove": {"Remove row %d", "删除第 %d 行"}, + "key_value_input.add": {"Add row", "添加一行"}, + }) +} diff --git a/testdata/snapshots/darwin/key_value_input-dark.png b/testdata/snapshots/darwin/key_value_input-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..9a3c8a34e519aa7728fdb048d6f09c2385815b0f GIT binary patch literal 9219 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/key_value_input-light.png b/testdata/snapshots/darwin/key_value_input-light.png new file mode 100644 index 0000000000000000000000000000000000000000..e89ea5cb0a26ffbfe093e2cc7860c0ba49ea2aa3 GIT binary patch literal 8880 zcmeHtRa9KvmTnP4f(Q2s7A$yhNnwQtcL?qf+$FdKf;$BS3+^FQ1qu?}3JYEk+}*wF zKmX|-{dl_jKHMJn?y<(&Ywd@*=A7R*=dvCBPDKs}ivkM(0N}`jrPTp|M_j0OFUBKO z?K7lg2>`tGl9zs~>7BEi>!bN%>Z08lTd~57EkXY*XEr7#-$R~GD+=pt{0p&}B1f2bLt^#)!IkmK6n-toK(XWRlS?F2HEb zo08!1V)CK)VxnEho{ClRvE`=22%!V}jfZ=G1c87B3GcwK7>Ee5FvG+CcXYR-Kx07T z+qu;ZE9;}`v{)I}mQPVYYD^xz4GpPZrkc>J+9hdhjrlV5PgT54B+kqNWp$v{=3y{qkjJ$fHh__5=bN=KVCKn^Sj`bj_- zpYS0pjGkdHE@5F*7C={#xS#-&b@)%X7HI)n3*+=%xc(Ey2f!i{%4q3rr-|h0F(!U~ ze%F?qX;8woXuvi5%a`T#_38!hc9Ad%PR_jPaRqht*PWd?Oy3qLGnHt_2|O{v@l4FBvP@ z$t-U(1}wHA)yq<*uNEvUFkTU1r3#1maYuu=nSNr7b>xrQCMjStLmmULDI;cObG&iK zGvy1%qYA}q36kQ{cY4Wjof)*ybrl&oO$bam=+XxeIZL)q~8PBaY&(2!J2dSFPNEpq7#nsnw zby@e~IjQgHSM3IC7&8;oV?erJ-?$qlfc?Ud?Q)zk9*&4(#wp6n2BBUy@fSz9wG@s! ztDRWSZ?t&*L{WqGCayR+K}h)Q<$5xwsx9$L0py}>#oL}|r-n&I!sOA?^};rHWZh5f z*+eAZl8N9-Vf^J41Qa^h+(@>v-^O+2U(O?a`(!w0Nsz}{SD!1;aJNv1hVolb3%3KA zk~I@EBzahQY8uQ@iU$IFitkUJSQ8jR`9^*2yO)M)Lq1bxs=6Vcx zKd`>X{KZ_ip#+9-Q&v(kZaQWj5=ETvnTla!PgWYuRPFSj4QQ{euV!Z9y5?(Q1vfOTL~kZ1CPt#=*Q>ie+=u>tyOWbs;_b&d+}CHY z-BbyMLd#MJ@^l75Z}8YyS+k%t6nl-PV|fxmN4uG>m7IjpVx;UWEpAf?=dDgm&qM+> zAP~kkg@_-?lZ&U?Ux5`Vs5iOwAhIMRer;HfZjtoAXuJ6Hn{BOccm7IIpTcXcJec_7 zJ-KM$VH!T*`-i3F{`RK4pauK0ZDJ(M>*D13>IpBu#78lB1Z+5$H<|wqAyI>h0N{PV zCWE^=OsFi*Vd9zv_Sx6HS#SBv?VCh}2Vp{cGZ~MCW!0C7f(GX#ADZA3BCpdNZRKxv zd1L6^lA66F^=916%?npo8<|xh5LK&-y*YG%@5Po8oo3A8;nE7t zp9K#r&syS-UL8_g?l1KoF82f=aKllppb>ci+n=|x3`hZzU5{$wHUZ}eG; z%8z3z`c`pCu?jB-?d44SLoCrdM$Xe zbN?)H3p{iyzev8fqeKh7wto(An(w_h{6L=wE{D%5E_@4k0XV$M#0r1OzXp4Z)jKwr=k3?$5Bfgz{2e5|Koiw6U{7kp}~uQMp`B zhI`JYn%hxMNGN9bQ#)X*Uq3}F4In>Lh4S02e?hc-P*WVBJ&W7-VqZ)qysIP>9j$#z zD)Q@3E{Dr^;h>|-BMq$Zq(tE6%*i;~;{|=>${#Kl`ww5~-6E>?zRM@?MFv*+n+M)2 z<9%*v8VKn%Y9p6{CwL}$qhnW{ECuDn(Er3Zb}q~;dmIc!B+r_F0SWlGK?tk^CFH7H z?iK*hLgnPD%TTA0&A`N@h)Sn;G{WA8_+O*M0=j>h?`I3}`}#sV)q*rM=pD&wDmBHq zd3fINaG};!Qc}O3LQXzmE}xWj+@v{Jvs*i?&kb73GWDY988JFhQ@^x=Th_Ig4$z`K*SE%TJ!T|=|a z^+t$(h>ZjdU>^xXr6%-DTnBE;GyTFTDLq>~-rP4z(9F!O7Pk$QP?+aldDij(eKIT~ z3%A@$*NBJbNi2yUp+QrWv>B^M=T|lNOeGSc#(mT@g@z^`I`r(f*vFh*QMZ$hPLhyD ziARupNZbUc;8D(JSYTyWxaWg=D)Nf@f!6G9&({szcFx~L!v8X=XGKKWl7f=5>>*ol zbaR%Nt~0Ye2WQ?pZx4yZJ?r71n&di)qr}8(O3e zDj*x*BB7i_H-Wmh)Yb~x{8oL@&D*t{-c%^2GFH(03uM~y%~9m!_?VhW`AM*Nz|H3N z_WhBZVJuwS{Dyf*w~6N35HyyX1Vah-78S3PoB8H|+2YPErjgJjN*4tx7_p(xlGiJ& z8r>-Ff;QH- zdEm9Rmq#nipJ~Oejw=EIe>MdfKPG#>I<7sAx#Pb*UV~>0_MI(6(d}VmDp?Jg{-(r6 zO_P;EmC%Mq2g483B9s|#hI!-R=hX7KozadwEO`!@gg?QGHlmxIN{{R*_&tPEYQn>- zN8V<0Nb1#L(%Q{ZoJ9CV4;0t{7rGu~RW& zNwNqb@5A784wY#j7s|s^$KFY=bzI|(8Z&x0RNT(L_Rc9b%AyQi-j!y|V zF3MS%2s1p`)rV^5=jF&&vnc`Wy8=n3?KMqO~z;u7@JT@+H!vqG*%;~ZmIc5VBfZk?nS#*+Z8&L zUxt6JYdhq*`%ZPy6y<)4_1bFJGZ_5ROp-NK30841-@+>_%$f*h+TS)RIR5$NmR@f8 z`uf_Er|g{|qkW?;J5^8Ju4qDbdbMDw!jQ0H^E%CPT^)DfsG!j6p`ohMpsXV&FKbDF zJr}lOF(hIzHKzuBBYBv@J=E0uH|uES1Dj@r>zVH_=fImUdpKE3QO<1TwlaWciZV*@=n8vijV&yQq|YY8;1eRC$MSE=Q|GYyZgGfh!9Y(;N6EE7<)R6;8jI0nIsmi|)2O!GN9+PKx1ABp#NWYBEmQiujKHKI+= zmW-&8pOG=z=}MzlH!b%v+j;)G=@$@ggy{u|ua_Y9FD`7(PiPDPOvy(HfPd-LXC2(h zwfpF-EtGVKBsSbsH!PzE7;$BBA+THjEJ>Par}c~*8FH&i5?kE7K_AWjq5l2C8u|v4f!|bdc6vA3M#Jb78VgY34&j>gnI9%AsE8J_$M5l_<4LgI6KeJq3P6< z3AGVn{0x;qQyfft@DF{de^tkS(Hc>-u_WT|zz-|PW|76QLc1~N3MNMn&-z-i!~#>; z78z);;%~iCmW|EjI#XZXALTpFZ2?xT>pfq#oVhe;uWVn-M%{`1tu?H&boVs$vA5>c zS)l?QzW-geNW?SrO#o=H8DGkOtnx1p5tg`Ia>uPO#baZQx&1sjWZ7Gg7KDmA0;JwFnV098ieTRf^q%% z!Od-BjlbTLMVQvys!WMc_4LuNxwjWo55JBu{kgW}{(h9VNaj3}Hn34-_hZOL>=uBW zifW!RfNsT>pZde*DqgZ@cz@{~*5VyCls1G8BP&)getg1KxY}a`twN;Q!)BY0%Bxwg z$H)>KyPJ@Zi?Aq3PuJRujgF5)%3<^k_(Aa1{o8_Rmfv+C^7zRjFoAH`se&p39utGm zTdcyr7Ipz*g!mgCgIioCzS>U^fQY!UMB?1zge`N%gdCT76EJ{%ebIYCH-1hkfyw)b zDh|gWN2j~H0eG4MqdQy*2o!M)BHY{25x?0DA|QwlDly67q@@^p0n*o>9TxXvdcFTn zD)hWK-S{2>cXmcX*ZN8!g6^C7E1|P&={x)a&iCeX-<)%<<@@jN>ca4o`!v;#K=z7r zmzUo`1gY7jB+=5RIU~2I#cyi5R#P*)Y-)%8BESK(2th;^fSzRvv|xFiE-yO(jf~SKjurZZWcB zz=j<8jyYZPC`sHZt8MI33}FT8DN;oM$_?L=O;?# zi_eH&snVt<4+LcS^mJ8+l#>Xg1fk;d(_W2!Au*go{yOaN^t2bT_-A>U$6os~>}Xs; zF<$pIeVCvL)nkTnj)4^k3}ChKU2EM@cQD#rIKE3o5-V$YWyPSP0vzVYxPT-l{^M+9 zKLv+Zx3!t>q^a85RW~*^VgMR!2N)x9(^FHk zAKbIrzU`b@!}z2N-3?E#-Rjv@aUf>p7~KAswH=}8S~@!%bXPYwde_4!dwZ;P9rv`> z770KQo5~Z}Nk1=7S_YwK;7B}LB>X|r)%EB4(A@YqPqV|&3(#N&!5h{;c|P>1Lj7ys zwu+%{*2g0qW7E@@1oQ!qd&D+&b~N;TCtR)TTklkxW<5W88_aP4!zLAX_V-_hjo9#U z52V}iQ>^)zMUr7RhvT>URir*E(5~Q!dsA`F*k{@%Q9wW;R3WP!MiVKIdx#15Y2X** z;I;7ma9YnuQN_d!v|Ma+-WZCF$`CPVDSFQGdf{M0;CLqh7LJdD6ZiD#+~Cklx_0=j z`&xa*lrk|eCg$AN4-bbcl$3^sgo48CzHHpY!^6YaSbwc6irVZ-wYprG*Jea=p#DB9 zYoJi>vpR%1%-yX&h_!jN7uwx%My@0By3s9I24`kw$H3sBcTkBGBrQ9PgP93^%?m3i zQ1fZCAKhw9=QI)WsaL9z$UapmXCL^P)7#sE4(FUPti79`F*!NLQrF;dYC?`}{Rj!^ zwyx|G_T25fIw4KcDGf(N+1S}>8-JPx8XM2bCx_HeTGEQS{wUubEKilEp$RBe%KR#- zim|m0P9kc0)o;Z`L`6Z>^hzO#p}0Uq7Mye{p_eA|UBZf^o?YTvEUmu&_zTjHcd|sk zO*}iuIfaDV`t2liH)q~qt=!Md*V(I8EV~(c8@JitpJ!q~MK9lKW|o$gu2UYi6_Re^ zQ&!y;I92^KCr5Q2iCkV;%%Txi#;9YF5D*u?8X{)HiI|g$z#c{{wqx|Qp@L0%p*yED zEMyu03dAGEO*m|GsVKvYQ>a%Do0ZH>*APoS;N|l?Hv@zoN1>Ks^V^YiOUd zE*d*L;C8I`J@ViBxE!vS&gOVwF)pWvX+Oo*#GJdnPST{mw|yN{yre=w>3q7gVS5sZ zu;KgG)a2t^|9#lg1=(Z2tiw>k+9bEVe17g(I67UbN?ZI9-)zJE@Z%n4@c?}W_euzE zz=@VyX?)&DJh{MI+fBWr!_5lO#yHt=RUdC=JGT@HA9fdRxiS>_q3+~Gqvnt`X-t)r zKmSo}4n`9WfBTA-0Koijl`Y%Hef2A$>h!po@d7LPvAOxNEW?_+^?vbgA**Gh6NcBy zWduF3mb|#CacQqTCtsHcYWVy?|Hho+G+v)9BL2?T!RL<^m3v*G7q?5v=^@gM#+6fK zJt0HN3^!Vc;je~kxe$ZoqyL@7l_nv}u7>}&v4&k3K8Q*%sKL30TiMu@s$JDUzeTr* zjzlK^Rjg!rIn)Wv0P#e=V!eOEMU=x2esHfstXsaJ@kK?W=j=1QQT^h1i9O& z+0w5_4NJ(Owmpxjopz8{_!7yPM-$|bwT30-SNh?1J^D6`|4N6Nj$9b3>O6PQzoH;e zv-88R<}fyI%f;2j84BHmdau}13F!D(RTs3z2J#R}$$6Q-o%s~?9wkln(!oP*tpyqR z%Z%~FIJMkj%GHZTN%)7otaNQ%Md_NX7y^Mh=d37f(PF9i5L^WH4Y<$d9Jc~s{_WCk z0WgBK>8d_c%`~~yr=U*J^;;=Q6{KkCE6xGs>xDD93+Rm!V;HtRdQEOGeOSh6E6&s6 zfo8`~Dlzn7N_mtiP28!Tz6{lnefdbQx@jp<(tU|-QfkQI)=gMK3tPIC)3ndYsxxmP z=#C`JzJemWB8`85_;#dSJA7>3di#KXbI$#d+LJvLy8obst$*n1(tzr8L$vm{fT-%NeUraurq>4?S$8F)$6OwgB>_6M_miZ!*O> zduNbwZ~Pb*I?!-YTSB<8j} z8#*?ZJJupGY?%Kd+Fz=yP!?Gyb^mmYErZ`q&m5auN1~UA>M6N~G%fD(MJuZkl*&`x zc=5b{gO{4Kl!X7&ck^ng>;2dO^_w|Kcvm#M^tQw|wM`1y*Q%5K)^~}_T z^Sh6Vi~mzaQt0u#J)+M_{)*-5i z0Pzb#CI%&NMgkX5xe-%7DG5y5lLd`sRBzod6e>n(r8ENocjBF`0$(yhLjBju{>z&+|ej8q-MyY0Rb~6iOt9PxT8eBbY zEu#HFOabe>$3(#~vD)`!xquCOc;88UsUgk?bsMp8s)b4?b5%)_&1CH8D0M` zcUG|ek+T00)c@Zd;Q!0`fe-@Y{xjqL7r_~mqPyb;MBNm&4ZOLnME!{Zke5-Bt_Hmi F`8URs!+ih% literal 0 HcmV?d00001 From 92c42620f4df3009287b966604efff30617e7ef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 5 Oct 2026 14:45:49 -0400 Subject: [PATCH 02/14] wip-devtools --- alert_list.go | 128 ++++++++++++++++++ alert_list_test.go | 83 ++++++++++++ devtools_parts.go | 30 ++++ messages_devtools.go | 12 ++ stat_card.go | 106 +++++++++++++++ stat_card_test.go | 73 ++++++++++ testdata/snapshots/darwin/alert_list-dark.png | Bin 0 -> 25065 bytes .../snapshots/darwin/alert_list-light.png | Bin 0 -> 24423 bytes testdata/snapshots/darwin/stat_card-dark.png | Bin 0 -> 17500 bytes testdata/snapshots/darwin/stat_card-light.png | Bin 0 -> 17069 bytes 10 files changed, 432 insertions(+) create mode 100644 alert_list.go create mode 100644 alert_list_test.go create mode 100644 devtools_parts.go create mode 100644 stat_card.go create mode 100644 stat_card_test.go create mode 100644 testdata/snapshots/darwin/alert_list-dark.png create mode 100644 testdata/snapshots/darwin/alert_list-light.png create mode 100644 testdata/snapshots/darwin/stat_card-dark.png create mode 100644 testdata/snapshots/darwin/stat_card-light.png diff --git a/alert_list.go b/alert_list.go new file mode 100644 index 0000000..97c92d6 --- /dev/null +++ b/alert_list.go @@ -0,0 +1,128 @@ +package mujica + +import ( + "fmt" + "log/slog" + "time" + + "github.com/egoist/mygo/ui" + + "mujicaui/theme" +) + +// AlertLevel is an alert's level. The zero value is AlertInfo. +type AlertLevel int + +const ( + AlertInfo AlertLevel = iota + AlertWarning + AlertCritical +) + +// AlertItem is one firing alert; the caller owns it. +type AlertItem struct { + ID string + Title string + Level AlertLevel + Source string + At time.Time + Acked bool + Silence time.Time // silenced until; zero is not silenced +} + +// AlertListOptions configure an AlertList. +type AlertListOptions struct { + // MaxHeight caps the scrolling list; zero means 360. + MaxHeight float32 + Disabled bool +} + +// AlertListResult reports this frame's request. +type AlertListResult struct { + Element *ui.Element + // Ack and Silence are the IDs to acknowledge or silence for an hour, else "". + Ack, Silence string +} + +// AlertList lists alerts by level, source and time with acknowledge and silence. +func AlertList(c *ui.Context, alerts []AlertItem, opts AlertListOptions) AlertListResult { + if opts.MaxHeight < 0 { + panic("mujica: AlertList MaxHeight is negative") + } + seen := map[string]bool{} + for i, a := range alerts { + if a.ID == "" || a.Title == "" || seen[a.ID] || a.Level < AlertInfo || a.Level > AlertCritical { + panic(fmt.Sprintf("mujica: AlertList alert %d needs a unique ID, a Title and a known Level", i)) + } + seen[a.ID] = true + } + maxH := opts.MaxHeight + if maxH == 0 { + maxH = 360 + } + k := Tokens(c) + var r AlertListResult + r.Element = ui.Column(c).Shrink(0).Disabled(opts.Disabled) + locked := r.Element.IsDisabled() + r.Element.Children(func() { + if len(alerts) == 0 { + agentCaption(c, msg(c, "alert_list.empty")) + return + } + agentRows(c, len(alerts), func(i int) { + ack, silence := alertRow(c, alerts[i], locked) + if ack && !locked { + r.Ack = alerts[i].ID + } + if silence && !locked { + r.Silence = alerts[i].ID + } + }).Shrink(1).MaxHeight(maxH).Background(k.Surface).Border(theme.BorderWidth, k.Border). + Radius(theme.CardRadius).Dividers(theme.BorderWidth, k.Border) + }) + if r.Ack != "" || r.Silence != "" { + slog.Debug("mujica: alert_list action", "ack", r.Ack, "silence", r.Silence) + } + return r +} + +// alertRow shows one alert and reports its acknowledge and silence presses. +func alertRow(c *ui.Context, a AlertItem, locked bool) (ack, silence bool) { + k := Tokens(c) + d := Density(c) + u := d.Unit() + sev := [...]Severity{SeverityInfo, SeverityWarning, SeverityError}[a.Level] + word := msg(c, [...]string{"alert_list.info", "alert_list.warning", "alert_list.critical"}[a.Level]) + silenced := a.Silence.After(c.Now()) + row := ui.Row(c).Key(a.ID).Gap(u * 3).AlignItems(ui.Center).MinHeight(d.ControlHeight() + u*2).Padding(u*2, u*3). + Label(word + ", " + a.Title) + if tone, _ := severityLook(k, sev); a.Level == AlertCritical && !a.Acked { + row.DrawOver(func(p *ui.Painter, r ui.Rect) { p.Fill(ui.Rect{X: r.X, Y: r.Y, W: 3, H: r.H}, tone, 0) }) + } + row.Children(func() { + ui.Box(c).Width(96).Shrink(0).Children(func() { devtoolsSeverity(c, sev, word) }) + ui.Column(c).Grow(1).Shrink(1).Gap(u / 2).Children(func() { + t := ui.Text(c, a.Title).FontWeight(600) + if a.Acked || silenced { + t.TextColor(k.TextMuted) + } + line := a.Source + if !a.At.IsZero() { + line += " · " + devtoolsAgo(c, a.At) + } + agentCaption(c, line) + }) + switch { + case silenced: + agentCaption(c, fmt.Sprintf(msg(c, "alert_list.silenced"), a.Silence.Format("15:04"))) + case a.Acked: + agentCaption(c, msg(c, "alert_list.acked")) + default: + ack = Button(c, msg(c, "alert_list.ack"), ButtonOptions{Variant: Secondary, Disabled: locked}).Clicked() + } + if !silenced { + silence = agentToggle(c, msg(c, "alert_list.silence"), "bell", locked) + } + }) + return ack, silence +} diff --git a/alert_list_test.go b/alert_list_test.go new file mode 100644 index 0000000..156f7e0 --- /dev/null +++ b/alert_list_test.go @@ -0,0 +1,83 @@ +package mujica + +import ( + "fmt" + "testing" + "time" + + "github.com/egoist/mygo/ui" +) + +func alertSample() []AlertItem { + now := time.Now() + return []AlertItem{ + {ID: "a", Title: "Disk 92% full", Level: AlertCritical, Source: "db-1", At: now.Add(-5 * time.Minute)}, + {ID: "b", Title: "Latency above 500 ms", Level: AlertWarning, Source: "api", At: now.Add(-time.Hour), Acked: true}, + {ID: "c", Title: "Deploy finished", Level: AlertInfo, Source: "ci", Silence: now.Add(time.Hour)}, + } +} + +func alertTester(alerts []AlertItem, opts AlertListOptions, got *[]AlertListResult) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + r := AlertList(c, alerts, opts) + if r.Ack != "" || r.Silence != "" { + *got = append(*got, r) + } + }, 640, 400) +} + +func TestAlertListLevelsAndActions(t *testing.T) { + var got []AlertListResult + tt := alertTester(alertSample(), AlertListOptions{}, &got) + for _, s := range []string{"Critical", "Warning", "Info", "db-1 · 5 m 00 s ago", "Acknowledged"} { + if !tt.HasText(s) { + t.Fatalf("missing %q in %q", s, tt.Texts()) + } + } + _ = tt.Click("Acknowledge") + silences, _ := tt.Find("Silence 1 h") + tt.ClickAt(silences.X+4, silences.Y+4) + if len(got) != 2 || got[0].Ack != "a" || got[1].Silence != "a" { + t.Fatalf("actions %+v", got) + } + tt = alertTester(alertSample(), AlertListOptions{}, &got) + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyEnter) + if len(got) != 3 || got[2].Ack != "a" { + t.Fatalf("keyboard ack: %+v", got) + } +} + +func TestAlertListDisabledEmptyMany(t *testing.T) { + var got []AlertListResult + tt := alertTester(alertSample(), AlertListOptions{Disabled: true}, &got) + _ = tt.Click("Acknowledge") + _ = tt.Click("Silence 1 h") + if len(got) != 0 { + t.Fatalf("disabled list acted: %+v", got) + } + tt = alertTester(nil, AlertListOptions{}, &got) + if !tt.HasText("No alerts") { + t.Fatal("empty text missing") + } + many := make([]AlertItem, 100000) + for i := range many { + many[i] = AlertItem{ID: fmt.Sprint(i), Title: fmt.Sprintf("alert %d", i)} + } + tt = alertTester(many, AlertListOptions{}, &got) + if n := len(tt.Texts()); n > 300 { + t.Fatalf("%d texts for 100000 alerts", n) + } +} + +func TestAlertListSnapshot(t *testing.T) { + snapshot(t, "alert_list", 640, 260, func(c *ui.Context) { + alerts := alertSample() + for i := range alerts { + alerts[i].At = time.Time{} + } + alerts[2].Silence = time.Time{} + ui.Column(c).Padding(16).Children(func() { AlertList(c, alerts, AlertListOptions{}) }) + }) +} diff --git a/devtools_parts.go b/devtools_parts.go new file mode 100644 index 0000000..845590d --- /dev/null +++ b/devtools_parts.go @@ -0,0 +1,30 @@ +package mujica + +import ( + "fmt" + "time" + + "github.com/egoist/mygo/ui" + + "mujicaui/theme" +) + +// devtoolsAgo formats how long before now t was, as "5 m ago". +func devtoolsAgo(c *ui.Context, t time.Time) string { + d := max(c.Now().Sub(t), 0).Truncate(time.Second) + return fmt.Sprintf(msg(c, "devtools.ago"), agentDuration(d)) +} + +// devtoolsSeverity shows a severity by icon and word, never color alone. +func devtoolsSeverity(c *ui.Context, s Severity, word string) { + tone, icon := severityLook(Tokens(c), s) + ui.Row(c).Gap(Density(c).Unit()).AlignItems(ui.Center).Shrink(0).Children(func() { + ui.Icon(c, icon).Size(14, 14).TextColor(tone) + ui.Text(c, word).TextColor(tone).FontSize(FontSize(c, theme.CaptionSize+1)).FontWeight(600).SingleLine() + }) +} + +// devtoolsTitle is a serif panel title. +func devtoolsTitle(c *ui.Context, s string) *ui.Element { + return ui.Text(c, s).Font(theme.SerifFont).FontSize(FontSize(c, theme.H3Size)).FontWeight(600) +} diff --git a/messages_devtools.go b/messages_devtools.go index 9276a76..5d9d5a6 100644 --- a/messages_devtools.go +++ b/messages_devtools.go @@ -7,5 +7,17 @@ func init() { "key_value_input.row": {"Row %d", "第 %d 行"}, "key_value_input.remove": {"Remove row %d", "删除第 %d 行"}, "key_value_input.add": {"Add row", "添加一行"}, + + "stat_card.change": {"Change %s", "变化 %s"}, + "devtools.ago": {"%s ago", "%s 前"}, + + "alert_list.info": {"Info", "提示"}, + "alert_list.warning": {"Warning", "警告"}, + "alert_list.critical": {"Critical", "严重"}, + "alert_list.ack": {"Acknowledge", "确认"}, + "alert_list.acked": {"Acknowledged", "已确认"}, + "alert_list.silence": {"Silence 1 h", "静默 1 小时"}, + "alert_list.silenced": {"Silenced until %s", "静默至 %s"}, + "alert_list.empty": {"No alerts", "没有告警"}, }) } diff --git a/stat_card.go b/stat_card.go new file mode 100644 index 0000000..1a5bc7c --- /dev/null +++ b/stat_card.go @@ -0,0 +1,106 @@ +package mujica + +import ( + "fmt" + "log/slog" + "math" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/theme" +) + +// StatCardOptions configure a StatCard. Label is required. +type StatCardOptions struct { + Label string + // Change is the relative change, 0.12 for +12%; HasChange shows it. + Change float64 + HasChange bool + // LowerIsBetter colors a fall as good, as for latency. + LowerIsBetter bool + // Trend draws a sparkline; nil hides it. + Trend []float64 + Status DataStatus + Error string +} + +// StatCardResult reports this frame's events. +type StatCardResult struct { + Element *ui.Element + Retry bool +} + +// StatCard shows a statistic, its change and trend, or loading and error states at the same size. +func StatCard(c *ui.Context, value string, opts StatCardOptions) StatCardResult { + if opts.Label == "" { + panic("mujica: StatCard needs a Label") + } + if math.IsNaN(opts.Change) || math.IsInf(opts.Change, 0) { + panic("mujica: StatCard Change must be finite") + } + if opts.Status != DataReady && opts.Status != DataLoading && opts.Status != DataFailed { + panic(fmt.Sprintf("mujica: StatCard Status %d is not ready, loading or failed", int(opts.Status))) + } + k := Tokens(c) + u := Density(c).Unit() + var r StatCardResult + r.Element = agentCard(c).Height(116).MinWidth(180).Label(opts.Label) + r.Element.Children(func() { + agentCaption(c, opts.Label) + switch opts.Status { + case DataLoading: + ui.Column(c).Grow(1).Justify(ui.Center).Children(func() { + Spinner(c, SpinnerOptions{Label: msg(c, "list.loading"), ShowLabel: true}) + }) + return + case DataFailed: + ui.Column(c).Grow(1).Justify(ui.Center).Gap(u).Children(func() { + ui.Row(c).Gap(u * 1.5).AlignItems(ui.Center).TextColor(k.Danger).Children(func() { + ui.Icon(c, icons.Must("circle-alert")).Size(16, 16) + ui.Text(c, dataError(c, opts.Error)).Role(ui.RoleStatus).FontSize(FontSize(c, theme.CaptionSize+1)) + }) + ui.Row(c).Children(func() { r.Retry = agentToggle(c, msg(c, "list.retry"), "refresh-cw", false) }) + }) + return + } + ui.Text(c, value).Font(theme.SerifFont).FontSize(FontSize(c, theme.H2Size)).FontWeight(600).FontFeatures("tnum").SingleLine() + ui.Row(c).Gap(u * 2).AlignItems(ui.Center).Children(func() { + if opts.HasChange { + statChange(c, opts.Change, opts.LowerIsBetter) + } + ui.Box(c).Grow(1) + if len(opts.Trend) > 0 { + Sparkline(c, opts.Trend, SparklineOptions{Frame: ChartFrame{Label: opts.Label}, Width: 88}) + } + }) + }) + if r.Retry { + slog.Debug("mujica: stat_card retry", "label", opts.Label) + } + return r +} + +// statChange shows a signed percentage with an arrow, toned good or bad. +func statChange(c *ui.Context, change float64, lowerIsBetter bool) { + k := Tokens(c) + icon, tone, sign := "minus", k.TextMuted, "" + switch { + case change > 0: + icon, tone, sign = "arrow-up", k.Success, "+" + case change < 0: + icon, tone = "arrow-down", k.Danger + } + if lowerIsBetter && change != 0 { + if tone == k.Success { + tone = k.Danger + } else { + tone = k.Success + } + } + text := fmt.Sprintf("%s%.1f%%", sign, change*100) + ui.Row(c).Gap(Density(c).Unit()).AlignItems(ui.Center).Label(fmt.Sprintf(msg(c, "stat_card.change"), text)).Children(func() { + ui.Icon(c, icons.Must(icon)).Size(14, 14).TextColor(tone) + ui.Text(c, text).TextColor(tone).FontSize(FontSize(c, theme.CaptionSize+1)).FontFeatures("tnum") + }) +} diff --git a/stat_card_test.go b/stat_card_test.go new file mode 100644 index 0000000..d843db7 --- /dev/null +++ b/stat_card_test.go @@ -0,0 +1,73 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +func TestStatCardValueChangeTrend(t *testing.T) { + opts := StatCardOptions{Label: "Requests", Change: 0.124, HasChange: true, Trend: []float64{3, 5, 4, 8, 9}} + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + StatCard(c, "12,480", opts) + }, 400, 200) + for _, s := range []string{"Requests", "12,480", "+12.4%"} { + if !tt.HasText(s) { + t.Fatalf("missing %q in %q", s, tt.Texts()) + } + } + opts.Change = -0.05 + tt.Frame() + if !tt.HasText("-5.0%") { + t.Fatalf("fall: %q", tt.Texts()) + } + opts.HasChange = false + tt.Frame() + if tt.HasText("-5.0%") { + t.Fatal("change shown without HasChange") + } +} + +func TestStatCardLoadingErrorSameSize(t *testing.T) { + status, retries := DataReady, 0 + var h []float32 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Column(c).AlignItems(ui.Start).Children(func() { + r := StatCard(c, "42 ms", StatCardOptions{Label: "Latency", Status: status, Error: "timeout"}) + h = append(h, r.Element.Bounds().H) + if r.Retry { + retries++ + } + }) + }, 400, 300) + tt.Frame() + ready := h[len(h)-1] + status = DataLoading + tt.Frame() + tt.Frame() + if !tt.HasText("Loading") || h[len(h)-1] != ready { + t.Fatalf("loading: %q height %v vs %v", tt.Texts(), h[len(h)-1], ready) + } + status = DataFailed + tt.Frame() + tt.Frame() + if !tt.HasText("timeout") || h[len(h)-1] != ready { + t.Fatalf("failed: %q height %v vs %v", tt.Texts(), h[len(h)-1], ready) + } + _ = tt.Click("Retry") + if retries != 1 { + t.Fatalf("retries %d", retries) + } +} + +func TestStatCardSnapshot(t *testing.T) { + snapshot(t, "stat_card", 640, 150, func(c *ui.Context) { + ui.Row(c).Padding(16).Gap(12).Children(func() { + StatCard(c, "12,480", StatCardOptions{Label: "Requests", Change: 0.124, HasChange: true, Trend: []float64{3, 5, 4, 8, 6, 9}}) + StatCard(c, "182 ms", StatCardOptions{Label: "p95 latency", Change: 0.08, HasChange: true, LowerIsBetter: true, Trend: []float64{120, 140, 130, 182}}) + StatCard(c, "", StatCardOptions{Label: "Errors", Status: DataFailed, Error: "Source unreachable"}) + }) + }) +} diff --git a/testdata/snapshots/darwin/alert_list-dark.png b/testdata/snapshots/darwin/alert_list-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..ee4869672ab346641ebd1369b203c415e14e803a GIT binary patch literal 25065 zcmdSAWmH^U*DY8`kf6aG5=cmJcXxsXcXxMpcL)$5xVyW%yF+kycc<=oUjObldfeN6 zyMOcTxA_AiPYE(KQW?p|40uT#5fT!biZ3q4hJ0f=KW}48 z?s>;{0)~oW#0fiLzX}Nn`MPw>bso-$vpbauQ95%}x<}>;T!L}>2nCU*L>>);q$3R1 zMx#Uo%&xF|aq*o#tK9>r<}gWW!otmHhA}05@rJ-MB^_Mm^#_Ng^RHX#(Ex+!Kv4gi zkBc#=%KD6G(oaXq=>02Z4UykNSk0I;K!`8zI{S1cmk&ADCLi<#b2Oj;t8Vzhs8kT6 z!D%2uL)!U%g{)o{@pRq!-Lsp3!1VK57(+LM$8fI2U)FD@@v+>tnmKxFZu#!sGO)=; zIT5mBaQzm&c65`TqNuQSuf4i4jTt@=MOdf5(2h=yx3i@9y&+dR)dZh|slBLZ_gVk8 zOJ|+BeQd86K56@n^;28XW5f1sfk%7!By~GFy42mtrIqvZkUTH=^))P8aGH zQ;^lvx?TMohV{w*HUy3j1RL~4`l@18mFGR-wF}2l`&+r5{%s(FSDTku>QtsIMw!yH z3~1Rx|70y~1k#BL0mTdkL)YE#2?D(WXN^0Rq7Ely<68e{janWk)W zeo@nBYUcF%b;0;4qPvh`1w(f8hJT2+cX+S)Zljoyp;*MX?U z<7|FinHufY4iaWsXT4ETK)g!a;HbFtd#DK(9jo6 zeZ{2ZHDlqty7Uppj9Uc`0cdLEiRsqw3~8#EQ5uO74R0SQ>XS_naZh2 zjn#UQI^(TcB)VM~?$T?_w|h~7EB%6pF(Bk%FPw4%e1mCoFmadWo|2*-fQo`S@zwn< zbjW#MX1D=7dm(X~V7Jb`{t_z}De_H~u0UrAqhxN&u8ydrT{^4*@&3T$Z&ODK|y^j8*8bjNCk2ATb`8We#`FqqVik`?cS~t z{Kxz?PIg+>d+Yaol#hxohbHN*1O2HBQbzRQo-e#Awjpz1zm%y9b%Oa;Wm6 zlJIpzNN6#ifVTUDtijliB%cZ~Z8O<^%FJeG)R`qB)SA>a+Eiqhxj)Zs=}=@MVkfjg zfBG0fg8%W@vm#sEbR0Z{5X|pf8Ij-<>qyr{eHzBY%97?qj7tE?Lee!ZE1PA z1K%^r*HBP^4WcvE+t@k?d3$?%M)7K>sR{edS$6+;P0E|rJq{JegYa^`C59Z_;;^U| z`UD9o9L4~FS`@hmm#3%SgGf-}nmN=kG3UyO&{2g*mTni<)?h%BofbmgHOU|lY0&cW z9~C%gF?sosQQSB}0*i?>Ff(|K)Zzq!F94Mop`Zv6@rG&k(7=_cJ#t_>GV(@uC-dvd$J=r&*K8Vl9hK(QwpSyqE-01-W)F~r60RqX% zF}S=w7PNkEbz4@{#CZ^FPk9t{3DQLV%=G zo7oK2X0_o{dQh1V`Tzaoor!ug@(Vj(==KH??waSV&7k{%e%t?*TQri&MI$xcmYjW; zWghN}G#m&-M#LGH6d%6WD<(dcO2?zqd!b8jo(KhkhJ*<2`4}UXMyRQRt-Kkfxg1E+#eRh@RywY&9DcAbsB3V3c#QfqGIGlM zfWQI+4IBYXnXoV4Tsxs5yN0HaZ~Y}G>F|do5-a+w{@iVBo?l(rt!Y^u>nnM+$+p=APGV>55wNhNl1^*CIgpJBZea|cJK<$1&C8qX>A^jI79~NO66O2mjQgGr zL+5Mf51%ZMAOw*Bq+~%!f2pRGnci1;LWzUTGelI>7TuPGgUO8O4D50eJYQx%OHO2l zbcP~@VHEt1xMTxD{0;j9zSfqM>x2hS_d6m&_HKS9rTVHW)!MZM!`$t=GJ3MuQ5jJg z@)Hifv>V9$d=Uif(ekpA^_p`WkdJ_@g}F609Q5gF+V`+9m5Q#Zg&OtKM-e^(dH5P<^Gsi{lg|Bm$_7;M;F)5N!RY3=o1t=lii-m)XK zlqw~Jj=aXlW@|1=N{g{I-VqZ8@yXh<&%>#d=393xJ5bpc`1@Pl?Aza;aEI0Tba%^9 zO316@BcSX1x4(Hk>lPC9TQ8%4GN%-rEYxK_#Xv*)MmMxXH*h9Pd`2CO)cBIz^%*S` zUr!kg?!mF@k^mVN^g~r~n_=2ica}*SMvPWD!)Z~IvoEp{Z*MO7_b*C;9~ zsvz=_f}uJ$)J^|YZFy4;;rk16(#|&)rztP}8`B#O7JQHnUjd`q;`6keTsT4%jW-%J z*P4)KPIb|xB0Fnt3A3}~-Q7J@QIJd|7g2@~WODM6$>&mI%XjbkR2N-4w4`7_7Y`mo z=V>j-@?XQsX{2>$c(<<~7oD#f&e=lhuL3+>UaGH(3E_j=AI&aXn`Y$Y7)B_2nUC;P zq(%}3dizT&F^nARYHQOu>_2bMYwD&+umi!CNd_gFm7WE4_wXa!r^_rsa#&Th-(QG0 zVd*3>jRat@5lD4K{0 ztyZpY-A6}4Tv`V=<7ujk)>LP^P0m@-+V4f60>+z~*?I11(dwJ+r8Dz-=n@>rn5D~I z4x;!Z3OjmvXY8C+XM6Ddm$p^G7bCKsB{pvfOM=vd=KR`IltZ&)2M4Qw3)F}Z)xacJ z(OT_JMCJS5QFY_%v%B|cWDKE9t9{(lBl`|#tDdI*-r*RouEwNsA4K}${4nS<^0BaO zVJMwjnRHeNP$r1uytos2Ng4*#)b|qu!-?nTsKmG;7T4Z$cJHg09|xSRO|9OG?;Q*e zq82H97Zu$n=0l6K(RcvrjPo zwzE#xqaB!5<=IAY$F(-Dpij@GIO4YDl{=@JA%cZ zonrnEWVum74VR~K8qeK^xS`;3jq&Qi()%#3#UGkb$8TP~J)1^RSC111RW4lZ2c~7e zK<3{{?r!d183h5SL$hQpskc`KDN>di5%E#8EF#c~Os6PMbHwVK&g>VXwXi$HhE(d_ z&2w|Nxw@h>Sn-n7O}=!5lMXz%6TTf9c=H;I-?Z+`F28xDGbzT$P7fp5a{iWdm}>R+ zKLvJr@SOc!`d^*3k08@pW)$>f7wf_e+Af1M-Gy3ssvCzf_G0 zotSR;9?j+EmTcrN4m{_1v6#rTFM8GC1ODXkDHW-G&0_vO1K02T8frV*K7cAsBrlj) zCq}0~S(|en);F-dqGRdk&SSn>5OJiKh$%~?c6_~Kz(WZ|Nm1rrAD!?yKmC?2(2H&mr9tmh5oYeXN$gmc-C}Cr|T{qLT>xcPKD~{KI`}_IHWKnrVPLz!dHaa1B4X zLBwB5KW%blZrBn$Z@z2qE_-59BTS)AzNgk_?$v6q)V-QTiYw^XYA3M^bZ1ZHU{U{t zN9`M!j|CA;TWTCKiw(M{EH}C;w;Yjvi%1|*lASA<@+VBSE1{%J!HXX<;8sk|PW6Du zxl={K8}j&}EZEaJ~uxK^Yk`s1Mck|0dCWbt93-fUn1D@yugI&VmM)@uAygI3l&f<2u?skxG-AmKGa_4bS`~lWuC#?EG-^ z@)4@~+vCe!I+1>4S6r|ig+y9}D0xjN{c%E_H-ru^?CY$Q$HHvXP>Gjq66ND88 z%pRPk&1LCbnWQ>BIg*V|yV8q3m??6VtaLO*#um*U^%(=2_#9BtJxC^R4%{2Ou-sWh zj2U&hFux+^l}=D!Ro%A!=)>rrVCvvYG;)T#1nYL9>!ek1L+J-<8f z27Ued6^}Rq+1%80esPgncHq~nq&Ke&C1p+O3aspP43qQ-w7pp{SJwmkR_t~)9ML73i9vv&k~ul04f06g?C5Tvvfcp}mpWHg zh6F_!kriNNuLqVS<&`Au2Ch&MAL~Y1PHfSnVZlK=4@(fuE8N;WL7fD5aCF~_-d9n! z;u?+?z{;10W>2mMN-iiUnVLK^nlcGW`{0(9!8mXv2_5a)P}SAy3+9)$1Ym`PASHF1 z7$#QX4U)y$h);uT%Pjc?Ioy~OM@gb2NZ~#ul~R{_ht;*>_M06*rt5t{2TiUlKI%_b z9Dwl+0(X78XujmfdQh&wdSCbk1bDb_T{dQt@ZLPS4|&t!Cx^v*l4ppKUBNb+plw_> z-rXSs#>lkwS|~5jHJV%}Pq*IEL6;$mMNAy%TY^XoCmI<cUlC(jv+*JGUNdZoa;mfA4Gye)+7_{B%5#@zV;l;bOEzE76{OcIJ6^79QKM zHSi9aJtNMMhdlhd(nW)P#04%oBeTbNdYQ^I0#1>6v3z{ANCm3*o=1EAdnAy85B_o= zkBXpr$s`=&ER&g9vd~A^cD|oCbl_XkRwy!Tz53AmtS&VkTL>CU9R{_<8O@bwqt#-= zhLAna!0yp?x{ zarIh3RI*PQ@*`ICN(*!gv!g{v*|9?(d(w{oSa9Qwt&VaPZA`^1A97F>yYG$mGz;?a zAqula+f4o_0DHW=?2at>qLwIoZf>9E7Z(0dQrbP*GBDTwNWsI%NF^jHYJYqOb-?J1 z;C{D~R}gis+42;Abfh+Pb+4?dq_xX1JvBO7SX~X{*cxEFSA9jnli_{t#oviz&CI-% zmio-hj0ovV=RB^^FK+}@a=0ryJX|8m!AZpJq*R;P*}Wnp9Jn;|+*)02lv|TSW{60w z7VHGu*Jq-2=~s9W9R2?J`8km~QlJdv;NZl}!g7Ci=hb0bz(>-#X>(>pN~ZVn(KIjI z>PJ19%_k!qP<3f(MOoV~5ciQpemsSw}!S{nV3%H-{LyUjM?qK?}Z`6V8PtQztwHMC;`XoXI>f9U|hySOi>9-gy z9>4&AYcg5GeoZlAzOiir-UFjRIhre%+2rx&d!qnJ)WPT>J(~MX8&+n8n4UPeCylsq z`d+beX=;l~JA!au-}KeaHbLF+Gswp=P9W#CpDbWPbBlLgOiJq4g%Mvtbba+3hf6oH z83!5J&cOb|20h3pJTVr?_Oml`a~04+F*eTQlN0(jZMGXq)3_-ecCUmf6KI*}=}}iz zpgka(vu>Sjg+AGuzi?0pn|5Z>ex&A38QHa5$HV8bzXDQ_{GcdWnj%!oZwYbFd{U9~ z>+9f+uJg#qF&Q%)lNWYT4r+Gbih=FZ#X4IfmBH>p)d4i;Qk8bNRg33&LSMd0OLNJG z(IImsS_A^h^f5y&!*mYM#id~`?%kEs8wq_V4>GTpu^n|Fk6Lc%50J!baGfb$g|`I~ z?>~+_1bZio$-iSXmZzBxWb{O?@<)twr8g0v-I(gEc~bCn&JD7&nCHD3E6P0iPviI< zFRg?_i}LScJ40FAV1OcJX5Ny>(A1y#J84U)N}`B1{m0q0PM5>R36@~dqp8_7(}1j@ z`&|eq5C9O2G1oawM7d~wjN%X1eNFXL58{KvJ>%;7kmi1VbR6E{q^Mx#Pm9|6A{-X= z4z4&K`+Q&B%v9a{II60S^S5t*vQSr;c{Bpk*J0gqs~D{ucCNKKQ52b#v4pso%lb+? zCc4`BsWwfeY?&O=Zhj%C@7(Bo0s>wdTQ+R8GKtw+SBb2Pk|Tp!x2tm2*7p+1X)r1A8xr4jTvkxxQIPW7OZ$6`U#+G1UC6q^oYS-G_ zh3z8PvT5k*9yY5mojo1MPUVlaU<(CSJlN^VoL}dfn|ASgWeZp@I};6eDAP?Z`i^Yp zVL~@*1K54u!6IxMcMtO ztGrog^V;8pi^@v<2DX1&T3T0xxDX(_M*0;~e093x_GV9$Nf&|ybN8JO&RcV<<`RX&R505dCM^RGTQ5zB>}L8 z#?}?Tc2P*wbYGN39vT8NKl7pH2B?DVx{yM6eJA0!-%AX~#?AIdkU;^23W z4L=_?Y2H0Tgbr*cicT!d%^#ibqryGhyVC=Eoz_l5DmXjuRuvc$*Ji0}x;TWO>`+(t$w=DM5#a+-^!;`@ z)QK;1A5bs=B?EFMESCef4mv56D%#;>Rb1P|bJ+Kh?S+bdH9lF>$9OUaRzqJtR9qe@ z5fPCz=^TB`-alm%zo~5LE&mAz2SFmf!+C$v0$&+~_p0hHT4AGK_vVG%a@%*+@&t3~ zCuSr_iX1GTQ{vXl?%o&{eqyjX#_POmy=;j0pQ!SiGyFx(ofCy{C?CniqX!pJMX6*w z9G#S~aBI7&FfM3ve%^L(dX6!%T<`Snf1S_<+2vpyVquoyf^8*YGE@!kXy(hixZT}K zEV`Ah@x>u68BupR8$M5{<41McCVwKj8=ov#L~fk}2tgDaw@&K|A9b~iG%oMY1|Az< zm5oI_1?3U&yTS|;l)2#%k}@;-jF_2;xt!i}>Z6hf=v0u9@bElzbHi+8>^?u&^lsV^ zw^3pI1&e3YgK}<%uZee7+`rP2QzpY>*`EyK0hIvK1TzlVk3*T^Aq-+;cYx032NscV+5CXSj@g?-kjH& zw1O}LQ8aNb?o~w$Eo!Z^6ZS8qjx6H8WAf`?JAeKJ)A7)hd1o+wm(4)%G8C!`_HFf8@T_nVE$jf%fGmd z|AQ~xh$ijq<@Ki6c`m?96Cp#zOHoIK?y|^03xV!#y)Y%9x!<3`rdxIo0Q_!tZa|yB z;>0GGx%q$=1Vx~pp;fm5Gq)j&PCY9t|G(~H!#fS+eQ(I5s^#UV;c;BUGI}Ta`$@I3 zlTz-3duU{Vx4`h@pT~R%ce2^!*XIH8}KzHvy=d|n2+;M|I1jwx3(!XcufHV#k zWMb-BZ#j{57d6gn<@YbPba# zd>(6hZX9aQcUJM)SNMfhAE(ZRzu~G%QCom~sHXqwo8}2VTJchVuSBc!PS5wUVr?J^jpUjY#NIR%!NAcf~?Wa|;CM zshMN@#Zxi@(S=KEE^Y~|DPwwmpwXrW4r8GgPL0+XJ8=`a zc}eXSsWjCcrN;hTwyPRD?4HtIo&EiB!b|aPNm9~Kg7}x;nuK}_cIjkbU>M=eur{<>TAZ6(STMFSd)eIwLx8x> zkI|(t?{9DI*))LA#cZ745L{APiukOW;-7ZwjV&~(s0acvDPkfyKJCD7V+R@xT~KhKn@lo;Xtm@Y$AKl|I^r#5bwyr9iI{e3MFIgw*4K%(Uu?! z9jDv%}rQVRrt@t^LUHuChtrro$(SCMOjnU|-n`4~((1t0qj- z1ipyV90BAL#xOTOtRbBA^lOoBc+Pe2*Tjuu-7&t38^=;jeqbXzr|tr_pt$%4&<5M+ zUJ5Obc9Q|WjSp>12#;Ru?rtGQ7$3<)TO$T&a%M(d_7?i}-mDnRKQqE$WXrF+To$6*RwPYQ8ZY0l~o}uHjKd$aPP~k<)24J&e=Umb7~}% z)ko%sUsToIf#3{;P$TKb(UDcdfSlLJTJ>(-&a#tKAa1E8rFd0+ogQC;j2>uV=Zx9i z^`fO&zMr8Lg47)w1{Y76J~b~rq9B}*}go($CoMiM|g3OvzRwJz-iJ#^=xS zz#ijX_2hlc&MJoinVcVBz(Jevf|iyzL7-#+Ri-{!VAjoP=M&VQL0P`1kpH$`q4kZF z)c#?A289TF9N2?ni{jeK`)fS|UxHu+c%K)F$?K%=>K&4c5kI`q-W|Tu!T@>0-(rYA zf{n4VGrw$T&35Ghc=Ti3?#_-uAWz|qvT0!j0_bO4oMtnf+*fG>kdJRV@;oxps}>^Y zYlU0)uHEjGuGmxe{K|7-@#VwIA8O7(LVkUD3Ep6RgUo`$OdVrOcu?d0!6j!~-pwc{ z$fvnrl)MhnUS1#I523hkPIuSbLrCBf2U@f*o`uTjVV;^3y<6z*kH`=h_0hyd9Lio` zqld1p%pCAQ8#S3e6Mm3F?R8NJFfn2~mo9&V`LwmQk8A4D_h}N96b=tf%2cX#en;lv zrS)&xO$Co7*8bSRwQpMCHR(pA{h{?3FtS}&%X55uoER5JT~Z1O;wrPYv3H97hz0@y z{*UmaRGBKO*jguEnY$?k%!lzO*vH4m1yjfrYUgJGkPxzJQ^m%_n3@@})+}<-$Rsd{ z&Bu#@(|OO)%X(br=f?}JU0k)4a=9UWeMYuN1_v{%h4>T$Ca|CV(C+Nnq7fuSkew^5 z7#ixTQQ@Eg@3{U({SmHv`+ABLLK zXoPzPFYULIAE-RsaL|xL$=UP9mT-I*F1EkaZ))_xGTByzGz_ZobIeZj2{6qZ(c#}w zp=IS1NMa1w2yV2c(1W&ArN+j_fK?+N6#tVJ6-+6_3kiXDA=(*^sW%{ zNsmkTy6oxc$(zIWZgQf4=GAH2R#sfrlzrYDmF{Tjb7hakj}G+Up0O16+Y&I%?2iH{ z&=oOOCVWp$o5HW6T}2s=mS$isHfkQk% z7%)L35}$zW3+x;Nr%lD z)~b{eGqX^k?D(c{^T_|{{%5&14h9!Bs3z4kl%__l-huw8s0PTS8CVq3#;Sif#>X3z ziog;HFg93ii*xVDsuCAfx8IF@=O}X=%!w#3r@-d^SS)4;+vh(Js6|9WL_=DL@tIv! zRbId6EIoZ*$)UU;Tl$7`_%PFxlqG!WmiQf^-o{h&))aXp1#`m^u#lB2*m;B`Hc7d#qE=xtO37nm2Y}^R=bs< zqZGex=nfjB?}n}IAzZNTy2qufu6R;t41| zQ1rn182UgkIP-8dq$f}&EjPFD#}5@CZ3;7r$$CauUHA5RE|FHOpA)>s4@;kCcCM9R z6-03faqm)St?6zE?~kmv`5#ciEcRFn7S$^cjHjw$yosw!*NlA zmErz(4vFa~)JTf)J78d4n4i`xbZUBcyAsZw|PxSi1X7;v&;f{4JF&12i%h;aa=0CF8F?)? zkeLB6;p;2GC`nLjDgoqA0G6Ow4eZ(c`aNvo`AG|zM}l8kNhwm=eSU3iW(H7sb#7k* zC1u&fl2#eU9l(Tr2fqJP%vZHz#7k5D$o%5sYz?hjq2^86o|WFhoSI|HPn6!5+=Hm0 z_48Pt4dY)4#y7av0yEfKsBt-U=MMGni>H>`YeFCMaX+|QX}zwu5tN2AMa(jbsxBQ& zAKaFmlul-~D#3VFNPb0A=czFxT#BFWsh17}l{1AP-P9pvC zloKuk0?GHQdGgo`U6?T1ruEs*XG{=e72PFz9E2q+i3kRZP4SunWYmIqS}!~LtBq@G z)OdM=vNn@rNyWM0ZCw@9`rRuYy5Q^DzNh%N!{3jDfNwzRC~nU-Z_9fM1+?=tNPuv| zA0Z*AIYO@BtBft7qfOWJX8!?FYu2f%J#6z`^d}h>g@vCS$J~HIFVH<|4IQ_Q%*HRj zn!*T6P(oGFzxV|u11jP32QXvb<^D)C=FSw?vUd3kk9YC*YOu^RI~3aYzL-%xCeej{ zg;wa9F1*`u!m>V|=DH+2Ka%M0_`*ERh%ueJNDOeu!S1SFKh`CJIyb}I*TySa(zJu? z5T`d$`X3jn+iDoEt+ar6vGyu@Vr+Tt!9%u#x-qyJ#YSMYV-w5^=oUPZ-rLzZyk~Bx zMr6z!Eu&J$BFH8oUts(*a~H5#Zn>%{3yaQ?aW6OYJ{a4Jez9-6D{gp9d0bC3m%nrS z5wc5tX?+b2q{8&YB{{{|$TX(mmYDmg_3Xym4@KQNbSDGm1LYw@wOuOVDeaq^=8c7=MIkX7 z7m&#}B(1tVHl)y2eE5+shc%HQui3+9f4Hzab-h?-`K2Gm!1eJ3_FbY^OV|GO?v8=6 z6jBuftLNH6BKYF6VnM$9iwra_t(W zMd>IfN6WO;`Ugb?MLC63be6@H*~VK{{F2K)v_|nR{nb>$P@$xgm6sT4luz9&NDfMj9$ zjuz9ncS1YS%%M%*Nf8zXe~q%k+?xjhAo z#AA1HM&P1=d2bbGLQSd7f>-zQBxHv;RemiZUPpg&b@poet; z?8SyP4-WfEIsWplVQC~9_A)y~1Z-kwWYlLHJgyLr>J*hDVwW-29 z-dfM6trr6IBni(<5@TZ=pQXz)8iJcbKd3n|S%fteT?T4b81Ap(@1Jio4wp3rT2fMF zndzF9W=mpHSJlulyUwp&UbQ4KbN8;y9-QQr0N#I7o;(RJOWw}DTL9_CVM62OcIvz@ zR;;dId619y8~_mZ%U@Wghxmn)zLTK=Ggch`o}vXAgS=>a9C{-hHCv~E(8b~*T^aSos{JVCYP|L|rd6d0oyiZC zo3dXtTI>oWIrMJ~m+)~CXBk;Ed6p-(%aPK22R7eii7veJId~{cJxCC#?rnP!OwTY` zUbNP30DGw;ul8}YclPKhRYoU8CM9Pf#}8niaJ!z{thkVykF|T<+ys9voxj_^R@Oe< zzc3UJNPeoX*lS5SC=rv^uH$ZNtbPYVrC%fh*{~En{K*>#Y90>W?r-^XOs4gK-$h5REkIg8QR!7GMu&gm~MIG*nKiO*34ccO73e}Ux$COeBmA1U{> zfNImwacpoCIS*6ui%1aQ75qhI?s&YlROI%~7&H`GL=uG5nsu~ftk2g-JH)Dys_m|X z%ZqDr+Txo`UFXm_&BNpND?-Au%nC~um(|i*UY2WrUUqXANF|ku#Ehok;I4R^E)o$% zH#F3>KOEmy4iV@;L9BdGi*OyJK@$-g{)8t~1fQ45y zO27i5V6);2r;&?q?@bC}8@DeqY5SW6MY4U5o>h}wd2{IJRTL0$itbn%A$j%m2Wa-+ zZm*MmdD8v)_a&4NAW_Cxc?4PBwYkyhxzQ^DtAyy=%Kmy|+eB#uXg|)~&nV`_ZtSlR zcR+{9=2On|AI!A~hP@g8Zwo^FC+7nB<`gANwXcte>sg;WmA*L88o$0X@1+lp1G`@e z6S2K+Ox|yU6ViFOj#y1iJ(NKxx%hpIm_c>#<5whsCPQXsX9uTbJY1FJ6s?!r zC2u1s*4c%*xp^+HyH6~LYQ|DQVu&eRA1$t+FI&~+8Ko4H_(;S=N~}#V;48xz>S_gA zBcx(^%JQ;;2DVu}8X)n}3QMMD=MFyaZgCX>JPDikhv$9Ts_8jx?>A_`hTBP|9aN8I z%`NwfEe-T3IFSEq%|$r)qXv(*dLRslqBHVzOQt2OOwj}y#te^{S}I#o?!Rg-vc3zE zpsv|;7kuhuBM3=n-&&{*dlI>P>7tWx1lcsbGtu-bC><}s#gy1@riyC&62Ct?*HcvL zX=Gj=)dL~BzXG{83SaMldF7uCLlaGWV9IXy+i5tBoZO3AC=t8jrbk-XJGUl3SF3$k znL~kBxC8Ax21a>?(jeqPw&#}TLKzYO`|rCA8=+cmlT6G90bmAjjJhldC-W>6!r}_j z1i?MHxCh*rUyR`kjB3bbaFiA0-N92N(&+~G$N*X=%aa%uR$VA&W-44a0tahIk=IyS zUER!LGI{`%p|q6QCZazDc{37D%1yZyMvm=$5dmOd64XB` zA=v%^4u8IH)2`JZ?z4L0!j(Q^1DhQ7K-GM-9=B+oSAqgkL1VL>9M$^2BdM5(Lcef< z@SBYPDz~fIm-XGLrp=e0C>WgtNEPkr>k6&(kU0Eye#!U&nhKZ3vihWtr@azd=QUC7 z^-=qOC~aPb&y8S;w?$L*gRosxroTS5+Y#pk!rKQh0P$A#tNO3g`~q>@-P{I)gzM#@ z6V46(4<*HCN%8QnTj@pn&N5R<%H05OO2WepU<%~!Ehj6_%GSABZ81;Bx4(e;m8OTV z#QYc)8a{(VTwd@M0JkdMT7C-fgN@J64#md@hg-lVceO+QR$+n7<{DN~t1|%hapUL+ zcp=dyw(x}01SJKenyO+qxshEviDAm%MU{AE#g5kBej~f{bVmcxw{r^0GYfNC_j7W* zw9d%E>i54MRVQ&gHPe97lQiu_IQ(0+{rTasS%pUR25(Ki3<0ai$;Vj+I2Vt>2R=iJ zz8=Hggg@uAhqc2;c;xQOAEJ5yh*;1Ms;cDm^XOs?Gd*cjeV*xLMsLK9RAL_G=EXZm z?&Kxb3@M~aXNIXsJagWah57c(yP?cxmxke?>6yckWT zQi+_3->7ERI;{>X#wk6dwW z6M)&Kr24aGs&6bPkKL>}f$pSkobg`xOqc$N#FepRX^ekiT%f1>x6{3DTlp7Zxv+y> zUWcj=H6(DKXwsrXGB~tPeU?7tR%G0t&fm}ziR=Y20hJeI64U3&BkOn_ICHrDO zqN;eaN#HqO&K?ahDg|a*1F?G@mpKLOEbU^B35N>dmFpfPIe!UYId zH6vlBVc%)j6W;v^PLP*v-tVu_CF1FlCUt*)j?P_mQStYxtcdbyi@`?Kxpx{RfgyI6 zpgjXacJbz-Vfp%~(|I<=jS(Yxb^LUESP>L=?QqUcrFuFb86JId*DcI>ITeVWzgzP+ zCZdQDU#Gg6l#`E$YPwS%wg!6I5LE|9h5)t=%-#o}ul&%EMO>Q=SrHM4$gsKJhX;p- z?ASCy5dq2~lHkndX$uU9^2p!80`IA{U)yr2xKy>uD>AODjZi~{Ac?>Y7@e_O7|Ii$ zeGhVP)A5yQcZ_uR76kaEr6UC_4-XCw&)qG_5iuB=3M$U2mAB+}hBQ4V^4d$C@it#C zWfZ#Iw2A04qEC1o!D%w?=B+4)m{kdjYrSVq&eQnplC6$b(`CaH{XdnoEv1k+q;~JO zs`-}yH}Hn){iFRflE7j8fY&oyLA-G~jKcd|7QbEHmjo_jg4gp>8E6!w%akiMV0hqO z#iHFaTxiHHk@#-{igWX2E&l!MwGOuKpPw#Yk-?@6Mhlka^30yp?-OZVx=l)oh>sj8 z+g3DBEXy+g#;LxLL%R%Cx8*QeEn6jrF@y@4S?Pk|d7sZe`H{%oR&E#}W{esk{ya}% z5c(8KHZ$}5Qpf$5Jb`45Jm?1;-j#J$Mq)6Kk;i5WXmVfwDMC1zHy+8^uSr&)l;czY z0>B)tF%6Ku@eGeGPLCRRZ-p)(L;uAl)A#y-QL%kr_Yc*|M$W_J($o+U{X zT}}g(h;1t(YwiyZC%Yv^{Xqcl1UEy!Q-l#Vd-!*W0q!IeQ8ST^zWqqG;<2kndUm#e zt!YzJQ5Bo}%hb8}&_TKnpe`Hk%}hk_{WopfI9+O zEPuMR{JnGUA5=fDppX3-H{K@O2|yFw-`ksI%u{{~W2G!}ljC&m2cX=hCz$!F8Ema` zJJ^5!+F$heTL~mUR^m1|HUJP>@QCN)*lSy(!+njeMJ7#VaI7nFEM+8@Y(V~029pC|om4*x$Lc!7a?Fi5ROA{GXIB!Dt)ZWrfB$dR!Uh><9HNI^=f6-He;WjI?)|Ny^1q1=-UblIU(m$jBrTF6+q#Q5Wy5o<6=!N)Y$pmjbWsVR)e5e6alah*L`Y`_>Y`uq}cnppLDT{n4AVflF?vCsl`XJZ#f%j_!0SFAMG{gR#(*50Gox5_$A8h! z|3+va99^%0$lJe++Ttw?D-N<;n5+rgYS^E_}~JYH{+o+KYXegrE9 zK)%4kU8H*s2wkyQ4hL9wsBWF^?wzr4QF3a_%IYp+%L1*K7@zw@qr<#7+@9{c#8UOWXZa0rl{S?}MX2!lB|S}HGlag2QJofaTQ&CBJf9HyKpq6q$`%Qp`!OTkl%9PC0U~@LOPZ# z&jw`*tX9Xn)-Y5Wb({us`PfyPT3>>z@aws~*I+@vak0>F*p~ikP0M~-io9Vo2E+c< zfG&~vGBB_lDIhyKJf4^s*VFIwHyznOG(})!h=h%mz!ER7^l{RYvNCErJL0oHSez@S zD$2^v>gvG-1>yewV=IG(knNq5eAplz-qGfY3T?c$xfJ?Q2g8_k08z|EUerrxuv~de0Xi3Atg*gioD&Wf^|?JMW;VvUQ6ek|04)at1*`kRTvInxtd~kSrO5CP!K_tfp$w-zcp-EzcBuPz%yJzOUJ2UUyKW@Fcb?d!4RMFK%ov?eKKHvV< z`Yp7yh};6^vBuFOdVNK{^+gs$Uh(6asII zKu83=r;9D6K3b73ZU;e$Nz`U$nPh2xEB->@8R_mm1iaSSqZ%6gK)XvydJ__ooIuIZ zE@W?R!q&`XkCaxePfG$=n>idF_l~Tb;@xY19t>z>io$!XO|?dl!hH+rt-Ihb+jNVB zsn$NHp$v$OWwJUNIT#RtXq>nDKgkfFAc&CA1!Nw8JzNJ3#UsxViOT3t9bEx`^;BKx zKN|jn;Fqr`n4AJu72}~BqTQJlCkk8?v=s6cRl)`Wa6MUzR(GyP(#E@Wutm;Ra)uar zKH!uAC$qQX_Un%$Z6N;70e1?Mkq)lV_LZF@*4X_=NTGTAR59<*Ru1lu9*I4|np4QY z@k1QVeCF6sW5N3}KhHyr;j2{f6m_(wk=N4+=S{?W(S!H4{W|vIPRHdh42MJJA%8Kv zIgpq#Y! z8uM!_Do&2~#63OhGoK@LF5KI@re}NP8AY%DQ8YwgQ-zN53211^t;@q~>`lhC5j{(R z9t0b6)z?G2ZSeRLK?(-?OjFZNKZXkR>YT58)NMY*~Ta_irM!BefE@X!Lkec!knDaoE$q<)$PEb7ITXg4h{~& zS8N14%nid6o4Bu}mlE$8VVE^xK*xs+Aob!JGvX1A>za0AoAI9KdZOTE7 zvCF97dcUD2+5#@)s=DpmFDPs=h&M7a5jkJO$lpZSw667wG9mcpW`wz%_2yUJ7I$)K zrE~Ci(_y0t)yO_`5(P~J-6G zPA4Omrt%fL?dP{WB8eLlPFlb?cY($>-sMo;x$o;r`==pNCR^XO_&vFU^ZfRX(%TxX zv8FG4W8&hpG?{9v-SMysHrCeH4C>!ajf{v)`xgPry(-h$a~umC9D#RpKyjAgbi8u% zK!4c`01O(V719c0+gN1bHyKo|e$NE}CTcCMtxaF8R!?ACe6_FC(hEcmccV)yKaU0E@qRV&OKLGtOj3%K#@#UCf{&+PV5sh0W|9b8Ly36~rp*t^ zk4=8PY{Ibal~=|Kj+)6{sLJ?nZ%;cY7KsDd z+fnCzdnO~WDd4d3K4d%{WnhjN_t|P2ud&>mbTH>lZwnT4G#w(*n<5pS2$g5Guvqc( zY{-U>aC9D+ZiMQ}$-X42tle5$8|d?{b3Mf< z7jfLmjRQ|`-lWwBn7Ftzp3eEim6f*Jt4;3PJLoiA5xT*8{!sflM5E=l8YFoZU+v{J z&2K3~fZg+Q$1MAT6v2K%yMfPx3~6pwHq&s{^FlAOuyu+iMAC?OPiWe3R$kH8@Vj6s z5gi_WtdFq$CUoJETgs z5?^|OMMq$E#cC)^FWcc{yf``y%}BuJE(0GAVIvn&uq^f)>h3xc%FfR}%D%t$8918+=o*`DDW*=dsZIOw}PxoGBwE*b(VeQL7z$Z9ECmrfG>bm zKu_y?fBH>MPJQi=kp0`C*rf&^3z|She;@z~%2J846w&R7q930Ut~Timlik64@yf#g zmdXEAv&S%2|4#73^ct8fo_RRSonCxnEn!jg7wzUHb}(}txbc0o-FlL7Qd~_<6Gl}n z&{#6n{zXr<@o0HFICr`0INVJOp9ZCr#qL*o5(@Zw zN*VuCP(VE^7~SzmwbIGO*=xV#cj!7MXtA4p$5a>>Uw$e-+m*IQSyED3%(%IH4Wjzr zVECAyl00~}wt{#|bAI;|v!CCx zQa`H$-T6p&FNiL!TFd(Ndb)W+mek|qo570O9bQ>3e0+xFe9c84nZKWf`Egs0M&c0_ z@m*JaoX>GOMJSr;buO)aA{=_%EJOqYl1)r3O{POv#nF3jdE6>qoKYCHJ0QG5k3q`- zZ>#2X|7`S207B6lYdO}d3*eKdp0m|i{D21v_^zrGU>qE`$IgjTn<}oA89dl)VK~`o z+d5b^4w`#~KyFnz%Rx}HA-%OFv4l-YE#18zp6;kHF0crLyPtf`z?04%f8~zA2hAoD z)De?EBx(6yv^{XoyPIZF0Yba1Y)jOATR|}?n|uVPV4|qFm`hJMzp83Cl&jIWOw()I zFR(z}T1x7TRm3k!N|l3y_F)ggV=qCm3l_8sj>2-dv^tZEU_A(meP?Itvf|?0JlVJS z1p4~P%y<87(aOp+3TwfPzofo$8GI0LZ%=$0TgR_(Qt4Xq|2`7=ZLCc$ zzk(k)EbEiA?w|UUgx;?ai)eItP+4U)y28KHVzj!lfB6Z|q4#%nSe<||tudvLi8H^j zp2FvQ!LlQk{JA

DmQg~Q)#-bl-`u%$&*>oT! ziF%gX+*>IpU`Ru2@&??xaE&T^dj1K|95E*5l19iL9xgyahPsE05kB;IAHBvY#gX>m z;rMX47U)QU#j#AA6E}#9b8!&>fkF^uWp6r^-nc)u(`=*TuV!edZ=@C##TEsq_k=gm zV6hk%XZ1-~Ew#R+DSQYu-)Kz2+oi7@<;I5cQzi}jkK1{7M~k_vq{-`jNF77@{8vU(1W*~c}Z7Z{m>VSemI#|L-b83FR#n$>=k3N9up4r-N zZEl|XEvp1d2DC5E&dJJm96kD?NXf-j%j&q4nY+?cQGuvIt7HDsx~%gHUbCTkqK@vw z$c*zn9{mJa#%%$o;bD7UPW^Q0WPeFIZ@_Y5VrOxx4WlTSd}l1nQ#OercYe)cauiOD zD3oy}wE<=resQ9vS`vX5vxxnO?jzpbNL#;MxEj6yBstmNW#%)rsnpV>jK9wq(7mEb ziDc7wm+ybvtZMbr_z9V0Ot@UV`Wz58#H}NV8L*%_k2_&t5Ka^n6>q(f$G9*mwAKa8 zmS5lW*UE=F+{RT;j~|kfVyU(2wo+DARr#G8(!G(NrD=&)RHN~6F=bEuCoH>`E=Xp{ z%#&T;-uC~waM;!Lo$WrMz(p!cT6}7J`oQuIdShCc|E{^|SczGfsP9=U=8E#xO>I5c^XdcMQ_{nBR8UA*-Ib zKTK`OU~@|&4i)jP0Xb`vH3CE@Zxi1%t?g2me4z3Plzg3s9p$fIC`cZ3v34( zxH5Q>8-2cFawnvu96H+dzg`j`%bGy?|7>LU@<9!XnX&lxVwZp82oBD*FFGJfWE6DFfV8*WZF10*UfA`pUi&I{ ztt%p()@@sv^=&T(1`@RJU<})Bo$oWE6_w_2q2Hy}ogxZyt+Jm%ze=rkL*?8-Oj}EZ z<8LiX@UN22RryE_CuzI~fl@2T>fzH@O0q^npakdKm$ zgtCU25WGC}5?C<)XM(FmWXl6gw5*Y&z;rP&hR+697?Q)QKB3~r#nrfS@h5i_VS((N zs(#2h5qVl{PzblpYrR*%$2a#xNDfa<^1%Js*ke}eVGN<|arPcIps|Wu8u%zz9aNgE z)2g}ZZfqUnW4wYFF#!{eiTB|QscWzc+)G_Y?{n{6)sWidUUq*HW_I#B&=e~DqdY_K;ylne~Afuk25v(Sc27~1aKDojRydt zh*0d4s~6EvPTEj~?MyrWDyuSy<19$*<1o)BD~98bTqbcz#$>1(o7al#lZhw+j_fXv zYVPCrUJoS+ub*W!i+;CWCR~QLIK{&;QYh3W3plbjg))nZDztwL6PlZT#_f4`kMZ`S zcekh$`J|tkn)JlnV`5G1RF7p2Co!Q79O~+dZf&4m?r^N}zqIsJ?Nx{$T(XoHYPKXG zh(J;zFxU=he=@dksao=>rZ*SJj36%>$6GE5A5C)BPZG33S9k+3U^gMkU4j+GY6?T7 z=2u~fdB5}T$)r`8)qmPr5IDXbn(2M;(X~E@W+{?0L=#z_uZiLo?rjK*3hd|SE6Bj> zGHY}V_N2x5NTdaQ>HnHhtbe6KEUgfy^iSKIJ3)g%p|~&NBf@Pvqec72L04Cwj$HSO z%9*WTZb%h(nV(tfjEAGcG85j`*~iCV*N#}Jin(rRv^yHjuc2`un{$e5<80&YY$JN% z{$^3>;M@LWXNI`ngStwPFLCvSiS<2W5fSD6Vr<@kx5SRM*sXu%9 z_mKjB_S?mRLHqN|h4+x{A#%ZOJM2|YOdc_v!?mB|+}lU;($eO$&d0V%9%7OKDWw*| zrhBmiN`gFJn_P-h65c5~Dd)9Y6jL)25iV2~#HS=hy#}%5U1DF?k}3-*7!Q)eHi+(T zzJIXym9fpGVH{zVg8RqzdL1T=_hP%fm$9-wsjy#ah4ywZU3>s{8$#Zh?fQQ2PzBD$48kF%TYJ(eka(`0|&LgGk@U8@Yjp_=a>2)f49#4 z|Kw@?I_3Y{EclNP&A;Ou%&s|7{=Yo&e_{B4_F$aBp)>B+a(OHYx52+~fD~j@q{|+f Gy!ac_l)d5r literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/alert_list-light.png b/testdata/snapshots/darwin/alert_list-light.png new file mode 100644 index 0000000000000000000000000000000000000000..ab19dc55a52e9d34000b612ae02e5650fb33640d GIT binary patch literal 24423 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 GLjMCg{(g)A literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/stat_card-dark.png b/testdata/snapshots/darwin/stat_card-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..197be7a7afaf3f952d02a122c5e25a492fbb4328 GIT binary patch literal 17500 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 literal 0 HcmV?d00001 From 0726d687159b825bfee8cf71362b84fc97404722 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 5 Oct 2026 14:47:28 -0400 Subject: [PATCH 03/14] wip-devtools --- incident_card.go | 114 ++++++++++++++++++ incident_card_test.go | 55 +++++++++ messages_devtools.go | 36 ++++-- service_status.go | 91 ++++++++++++++ service_status_test.go | 57 +++++++++ .../snapshots/darwin/incident_card-dark.png | Bin 0 -> 31767 bytes .../snapshots/darwin/incident_card-light.png | Bin 0 -> 30898 bytes .../snapshots/darwin/service_status-dark.png | Bin 0 -> 20330 bytes .../snapshots/darwin/service_status-light.png | Bin 0 -> 19649 bytes 9 files changed, 345 insertions(+), 8 deletions(-) create mode 100644 incident_card.go create mode 100644 incident_card_test.go create mode 100644 service_status.go create mode 100644 service_status_test.go create mode 100644 testdata/snapshots/darwin/incident_card-dark.png create mode 100644 testdata/snapshots/darwin/incident_card-light.png create mode 100644 testdata/snapshots/darwin/service_status-dark.png create mode 100644 testdata/snapshots/darwin/service_status-light.png diff --git a/incident_card.go b/incident_card.go new file mode 100644 index 0000000..25b6281 --- /dev/null +++ b/incident_card.go @@ -0,0 +1,114 @@ +package mujica + +import ( + "fmt" + "time" + + "github.com/egoist/mygo/ui" + + "mujicaui/internal" + "mujicaui/theme" +) + +// IncidentStatus is where an incident stands. The zero value is IncidentInvestigating. +type IncidentStatus int + +const ( + IncidentInvestigating IncidentStatus = iota + IncidentIdentified + IncidentMonitoring + IncidentResolved +) + +// IncidentImpact is how badly users are affected. The zero value is ImpactNone. +type IncidentImpact int + +const ( + ImpactNone IncidentImpact = iota + ImpactMinor + ImpactMajor + ImpactCritical +) + +// IncidentUpdate is one timeline entry, oldest first. +type IncidentUpdate struct { + At time.Time + Status IncidentStatus + Text string +} + +// Incident is what the caller reports about an incident. +type Incident struct { + Title string + Status IncidentStatus + Impact IncidentImpact + Started time.Time + Updates []IncidentUpdate +} + +// IncidentCard shows an incident's title, status, impact and update timeline. +func IncidentCard(c *ui.Context, inc Incident) *ui.Element { + if inc.Title == "" { + panic("mujica: IncidentCard needs a Title") + } + status := incidentStatusWord(c, inc.Status) + if inc.Impact < ImpactNone || inc.Impact > ImpactCritical { + panic(fmt.Sprintf("mujica: unknown IncidentImpact %d", int(inc.Impact))) + } + k := Tokens(c) + u := Density(c).Unit() + sev := [...]Severity{SeverityInfo, SeverityWarning, SeverityError, SeverityError}[inc.Impact] + impact := msg(c, [...]string{"incident_card.none", "incident_card.minor", "incident_card.major", "incident_card.critical"}[inc.Impact]) + card := agentCard(c).Label(inc.Title + ", " + status) + card.Children(func() { + ui.Row(c).Gap(u * 2).AlignItems(ui.Center).Children(func() { + devtoolsTitle(c, inc.Title).Grow(1).Shrink(1) + if inc.Status == IncidentResolved { + devtoolsSeverity(c, SeveritySuccess, status) + } else { + devtoolsSeverity(c, SeverityWarning, status) + } + }) + ui.Row(c).Gap(u * 3).AlignItems(ui.Center).Children(func() { + devtoolsSeverity(c, sev, fmt.Sprintf(msg(c, "incident_card.impact"), impact)) + if !inc.Started.IsZero() { + agentCaption(c, fmt.Sprintf(msg(c, "incident_card.started"), inc.Started.Format("2006-01-02 15:04"))) + } + }) + for i, up := range inc.Updates { + incidentUpdate(c, k, up, i == len(inc.Updates)-1) + } + }) + return card +} + +// incidentUpdate draws one timeline entry on a diamond rail. +func incidentUpdate(c *ui.Context, k theme.Tokens, up IncidentUpdate, last bool) { + u := Density(c).Unit() + ui.Row(c).Gap(u * 3).AlignItems(ui.Stretch).Children(func() { + ui.Box(c).Width(12).Shrink(0).Draw(func(p *ui.Painter, r ui.Rect) { + cx := internal.Snap(p, r.X+r.W/2) + cy := internal.Snap(p, r.Y+9) + if !last { + p.Line(cx, cy+5, cx, r.Y+r.H, 1, k.ControlBorder) + } + p.FillPath(internal.Diamond(cx, cy, 9), k.Ornament) + }) + ui.Column(c).Grow(1).Shrink(1).Gap(u / 2).Padding(0, 0, u*2, 0).Children(func() { + ui.Row(c).Gap(u * 2).Children(func() { + ui.Text(c, incidentStatusWord(c, up.Status)).FontWeight(600) + if !up.At.IsZero() { + agentCaption(c, up.At.Format("15:04")) + } + }) + ui.Text(c, up.Text).TextColor(k.TextMuted) + }) + }) +} + +func incidentStatusWord(c *ui.Context, s IncidentStatus) string { + if s < IncidentInvestigating || s > IncidentResolved { + panic(fmt.Sprintf("mujica: unknown IncidentStatus %d", int(s))) + } + return msg(c, [...]string{"incident_card.investigating", "incident_card.identified", "incident_card.monitoring", "incident_card.resolved"}[s]) +} diff --git a/incident_card_test.go b/incident_card_test.go new file mode 100644 index 0000000..98fa5f1 --- /dev/null +++ b/incident_card_test.go @@ -0,0 +1,55 @@ +package mujica + +import ( + "testing" + "time" + + "github.com/egoist/mygo/ui" +) + +func incidentSample() Incident { + t0 := time.Date(2026, 10, 5, 21, 4, 0, 0, time.UTC) + return Incident{Title: "Checkout errors in eu-west", Status: IncidentMonitoring, Impact: ImpactMajor, Started: t0, + Updates: []IncidentUpdate{ + {At: t0, Status: IncidentInvestigating, Text: "Error rate rose to 8%."}, + {At: t0.Add(12 * time.Minute), Status: IncidentIdentified, Text: "A bad config push to the payment proxy."}, + {At: t0.Add(30 * time.Minute), Status: IncidentMonitoring, Text: "Rolled back; errors are falling."}, + }} +} + +func TestIncidentCardShowsStatusImpactTimeline(t *testing.T) { + inc := incidentSample() + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + IncidentCard(c, inc) + }, 560, 500) + for _, s := range []string{"Checkout errors in eu-west", "Monitoring", "Impact: major", "Started 2026-10-05 21:04", + "Investigating", "21:16", "Rolled back; errors are falling."} { + if !tt.HasText(s) { + t.Fatalf("missing %q in %q", s, tt.Texts()) + } + } + inc.Status = IncidentResolved + tt.Frame() + if !tt.HasText("Resolved") { + t.Fatal("status did not update") + } +} + +func TestIncidentCardRejectsUnknownStatus(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("no panic") + } + }() + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + IncidentCard(c, Incident{Title: "x", Status: 9}) + }, 200, 200) +} + +func TestIncidentCardSnapshot(t *testing.T) { + snapshot(t, "incident_card", 540, 360, func(c *ui.Context) { + ui.Column(c).Padding(16).Children(func() { IncidentCard(c, incidentSample()) }) + }) +} diff --git a/messages_devtools.go b/messages_devtools.go index 5d9d5a6..a1373e8 100644 --- a/messages_devtools.go +++ b/messages_devtools.go @@ -11,13 +11,33 @@ func init() { "stat_card.change": {"Change %s", "变化 %s"}, "devtools.ago": {"%s ago", "%s 前"}, - "alert_list.info": {"Info", "提示"}, - "alert_list.warning": {"Warning", "警告"}, - "alert_list.critical": {"Critical", "严重"}, - "alert_list.ack": {"Acknowledge", "确认"}, - "alert_list.acked": {"Acknowledged", "已确认"}, - "alert_list.silence": {"Silence 1 h", "静默 1 小时"}, - "alert_list.silenced": {"Silenced until %s", "静默至 %s"}, - "alert_list.empty": {"No alerts", "没有告警"}, + "alert_list.info": {"Info", "提示"}, + "alert_list.warning": {"Warning", "警告"}, + "alert_list.critical": {"Critical", "严重"}, + "alert_list.ack": {"Acknowledge", "确认"}, + "alert_list.acked": {"Acknowledged", "已确认"}, + "alert_list.silence": {"Silence 1 h", "静默 1 小时"}, + "alert_list.silenced": {"Silenced until %s", "静默至 %s"}, + "incident_card.investigating": {"Investigating", "调查中"}, + "incident_card.identified": {"Identified", "已定位"}, + "incident_card.monitoring": {"Monitoring", "观察中"}, + "incident_card.resolved": {"Resolved", "已解决"}, + "incident_card.none": {"none", "无"}, + "incident_card.minor": {"minor", "轻微"}, + "incident_card.major": {"major", "严重"}, + "incident_card.critical": {"critical", "致命"}, + "incident_card.impact": {"Impact: %s", "影响:%s"}, + "incident_card.started": {"Started %s", "开始于 %s"}, + + "service_status.operational": {"Operational", "正常"}, + "service_status.degraded": {"Degraded", "降级"}, + "service_status.outage": {"Outage", "中断"}, + "service_status.all_ok": {"All systems operational", "所有服务正常"}, + "service_status.some_degraded": {"Some services degraded", "部分服务降级"}, + "service_status.some_down": {"Some services down", "部分服务中断"}, + "service_status.counts": {"%d up · %d degraded · %d down", "%d 正常 · %d 降级 · %d 中断"}, + "service_status.empty": {"No services", "没有服务"}, + + "alert_list.empty": {"No alerts", "没有告警"}, }) } diff --git a/service_status.go b/service_status.go new file mode 100644 index 0000000..e2a9c91 --- /dev/null +++ b/service_status.go @@ -0,0 +1,91 @@ +package mujica + +import ( + "fmt" + + "github.com/egoist/mygo/ui" + + "mujicaui/theme" +) + +// ServiceHealth is a service's state. The zero value is ServiceOperational. +type ServiceHealth int + +const ( + ServiceOperational ServiceHealth = iota + ServiceDegraded + ServiceOutage +) + +// ServiceItem is one service the caller reports. +type ServiceItem struct { + Name string + Health ServiceHealth + // Note explains a degraded or down service. + Note string +} + +// ServiceStatusOptions configure a ServiceStatus. +type ServiceStatusOptions struct { + // MaxHeight caps the scrolling list; zero means 360. + MaxHeight float32 +} + +// ServiceStatus lists services as operational, degraded or down, under an overall summary. +func ServiceStatus(c *ui.Context, services []ServiceItem, opts ServiceStatusOptions) *ui.Element { + if opts.MaxHeight < 0 { + panic("mujica: ServiceStatus MaxHeight is negative") + } + worst, counts := ServiceOperational, [3]int{} + for i, s := range services { + if s.Name == "" || s.Health < ServiceOperational || s.Health > ServiceOutage { + panic(fmt.Sprintf("mujica: ServiceStatus service %d needs a Name and a known Health", i)) + } + worst = max(worst, s.Health) + counts[s.Health]++ + } + maxH := opts.MaxHeight + if maxH == 0 { + maxH = 360 + } + k := Tokens(c) + u := Density(c).Unit() + summary := msg(c, [...]string{"service_status.all_ok", "service_status.some_degraded", "service_status.some_down"}[worst]) + col := ui.Column(c).Shrink(0).Gap(u * 2).Label(summary) + col.Children(func() { + ui.Row(c).Gap(u * 3).AlignItems(ui.Center).Padding(u*3).Radius(theme.CardRadius).Border(theme.BorderWidth, k.Border). + Background(k.Surface).Role(ui.RoleStatus).Label(summary).Children(func() { + devtoolsSeverity(c, serviceSeverity(worst), summary) + ui.Box(c).Grow(1) + agentCaption(c, fmt.Sprintf(msg(c, "service_status.counts"), counts[0], counts[1], counts[2])) + }) + if len(services) == 0 { + agentCaption(c, msg(c, "service_status.empty")) + return + } + agentRows(c, len(services), func(i int) { + s := services[i] + word := serviceWord(c, s.Health) + ui.Row(c).Key(i).Gap(u * 3).AlignItems(ui.Center).MinHeight(Density(c).ControlHeight()).Padding(u, u*3). + Label(s.Name + ", " + word).Children(func() { + ui.Column(c).Grow(1).Shrink(1).Children(func() { + ui.Text(c, s.Name).SingleLine() + if s.Note != "" { + agentCaption(c, s.Note) + } + }) + devtoolsSeverity(c, serviceSeverity(s.Health), word) + }) + }).Shrink(1).MaxHeight(maxH).Background(k.Surface).Border(theme.BorderWidth, k.Border). + Radius(theme.CardRadius).Dividers(theme.BorderWidth, k.Border) + }) + return col +} + +func serviceSeverity(h ServiceHealth) Severity { + return [...]Severity{SeveritySuccess, SeverityWarning, SeverityError}[h] +} + +func serviceWord(c *ui.Context, h ServiceHealth) string { + return msg(c, [...]string{"service_status.operational", "service_status.degraded", "service_status.outage"}[h]) +} diff --git a/service_status_test.go b/service_status_test.go new file mode 100644 index 0000000..6d9a07b --- /dev/null +++ b/service_status_test.go @@ -0,0 +1,57 @@ +package mujica + +import ( + "fmt" + "testing" + + "github.com/egoist/mygo/ui" +) + +var servicesSample = []ServiceItem{ + {Name: "API", Health: ServiceOperational}, + {Name: "Search", Health: ServiceDegraded, Note: "Indexing is slow"}, + {Name: "Payments", Health: ServiceOutage, Note: "Provider unreachable"}, +} + +func TestServiceStatusSummaryAndRows(t *testing.T) { + services := servicesSample + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ServiceStatus(c, services, ServiceStatusOptions{}) + }, 520, 400) + for _, s := range []string{"Some services down", "1 up · 1 degraded · 1 down", "Degraded", "Outage", "Provider unreachable"} { + if !tt.HasText(s) { + t.Fatalf("missing %q in %q", s, tt.Texts()) + } + } + services = servicesSample[:1] + tt.Frame() + if !tt.HasText("All systems operational") { + t.Fatalf("all ok: %q", tt.Texts()) + } + services = nil + tt.Frame() + if !tt.HasText("No services") { + t.Fatal("empty") + } +} + +func TestServiceStatusManyVirtualized(t *testing.T) { + many := make([]ServiceItem, 100000) + for i := range many { + many[i] = ServiceItem{Name: fmt.Sprint("svc ", i)} + } + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ServiceStatus(c, many, ServiceStatusOptions{}) + }, 520, 400) + if n := len(tt.Texts()); n > 300 { + t.Fatalf("%d texts", n) + } +} + +func TestServiceStatusSnapshot(t *testing.T) { + snapshot(t, "service_status", 520, 280, func(c *ui.Context) { + ui.Column(c).Padding(16).Children(func() { ServiceStatus(c, servicesSample, ServiceStatusOptions{}) }) + }) +} diff --git a/testdata/snapshots/darwin/incident_card-dark.png b/testdata/snapshots/darwin/incident_card-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..464c30672a927e565126315fc37639872023f387 GIT binary patch literal 31767 zcmdSAg4yE<(1WS4J&eh zV4)m^A4>4OVL@eo<2{>-VTjwE3l={=9CmdO#0;HaN1gW~;}e4~#$cDuuY_u-w~IqNwlglm{m%~1pdyKPcoBYKDDNf?OXwhH)HN+cu1xbw#Hw~e50V2kfRAx3KU8)weVg&VsR34i%Dxo2| z_49G9T5iSTM{4@!8h1+>|apO41m4`GP+_Y=?)THZYyf5DTY!A4V{dN=79 zFtNHGFa1;kkr8z*Hb`=Pq`S5mSq()+yK9hO_&PsvoFfdld6c}rd%+N^sCcXhe|5nM zX-nb>-|d4ZzNC=%vg}2S^@t?I%)P!-cvDnVyYWOnkNa$PKoxGMEw>(&OPbb|k7u+6 zo<((?XJHa>dnY`2B$eHzTnHQ4gTyUQqw5}N4F^3J!$)_l`6Xqo*O%Y)%+s`6yzU3% zO|3=X%rEt^LhK#>PAM)j!uQgkJI%?W!E%m1=tu<%eK=hla&t;%x(&WwkM26PCK+qQ zoKC0CeDbCn6RriBYN%4g&ph-V-Lq8Lg$Vw-8S_WUYbxO z6b>)-o*8HcD9iPAopBS#<@&TDxqZTy7p>Z6PF_=>z!;rtZzjBqYn{tj3A-XL8fc z9DRsMI$aj|347RJZTiC>gq#<<=jiCH!@tc&)*3wb!ia2bCu7v=G|RbN@zq<5Iv(6R zYo8An3vHQjWKqJ6^w&xi6;Z*a)o-AvlF+edaGaSQXuN1smnCm;WB7wTCDl+jjhFuc z-PTqswLtf~BLbZu?Aao|YbDGiZQ`Imoy)I3nhGl(J{@bnkO`5qCE;B zmq*l=H~g^LY-n(H;y5YMG}S@>X3_5-QHiU7^p6-o=Hd4G8!P84`JlXK9IkRF3zjrg`1Ac`f)-Q$=~jJ=HByB znO~Pr@0}on!~Mft%!SYu4N4Ga(CEA+`t!vg*hBi{WT(mZQhsRbttoAA*Kl$0+TUM_ zm@fS8Wp@P5nhWKT*PGS$n6|R+45wNo26PuxkWx|FuBeDzqe71SeVf#3qj=y=lM&uS z#=9gIkU|w#!X-aZCN)vkGa@O2-Z?SjNi~~=4la8cX}qMY>^Kd-UZ?4z$+vH5 z=UScn4!E562eX%u{|}jy>u{ZXmq`Lro+e2Q1^#z zkr@ERcWP>AQlUk>cj6Or%~=EO{K}&XVkiF)`fdo5p?G_TQlodDOeP`!+s4+R*u71N z+-1;B;~Qpx%Bh^flqw`0{Y3?8;bNWGdbX2TjlY*u9sG=$C=m8gWVTu2G>R!BaVE z6N)HwM{k}C88pUp<-TXS3k~JSsj;AHR<^qQT*+8L{1c|)cIBE&CzFxdlAro9%!4g> zTv`2dIkXw)2!Ldi6Apu=t%6fjNs*o#s!Jk5=|uYW`~X+JYNB0H{1tf()=dGu(fNt1 zTSA-3tON63Va-n7J9`R)=7#Ar4i;b9X=S3ZjaA;pU*nr)heXhLhrN5KJiT=^M`|r} zzRs@&Jr9SZkyOJoKMULWa&oTdhh{eyAaDaALbe?bEAPS{DK^@0arjg(!^ZPq%WazkXX+1?Va@6WMX zKK)z*WTecdqYK_k*d&KLmD$_`z?dwKPW#)5pi)V&NuC<;SJ%`Du(dIaeulN?%Vp#2 z99tL|;Jx-2%K49wrzYI*^nu*wBLao^2z~G82r=h{8!tu)TMR^z>OM{<6~#d1`)4cO zdx{P(*QkfnDq~5feDzg~=dJFG|6~ge_Aqj5EiR8=ho+kh2PgGS!+@#_eLmG1){Ztd z`h9)wakWW#(qQaIG7$2+9*3MZ~jmB311IWTNkCVc&Gq zYHmyII*AWb+?}5b*0S{zxjTOp{Rsy$O^*d3pV-Qhpy>XybH<({ul*y6s2plx(la%k zTZPLtQu62BhOY75T#k;(koG!@Z=tzf$BDasY(dC(_AqY^KYH3Hz`Oho8%DU!IZPJ5 zeq0tSj5NvLiIr{*l%EUAT-^RWp2~rvfglfWua3tF6i>T(a*uA!yS9^ebVU8imFFYD zjUEn+z=y<;M&v$&laxg4Gq87%N*cACA~FPY4P|drnF0VN6UiL*LReg(xwk%cRs1c@ zf(JWiZ3_zxwTCETu>^`L8n1G(5A}uh&%`RfOhF0FuhF4|6Y_b~IKO^KqV|rCQ2$k)>3lw6rd3x7kUN47F42e; zB1U|?FDc(|D}T=0t8<^8RS`zsa-{iHkzK_?%FKN+W;|9$4+GW1J^`UXg=UdPSdd>| zSJzNir>e53p`}s0+2M)<^&@okQi0)#>ZUd!vWi(mb5^0O%T?7Sjt2Bs2k%)7wEpjVB5U6`JJEYd(bqGP5cc80=AB=4Ekm~p6VP% zw+8`!=XxRTttni~SWZ-i1oLML3=Nxs`cF@Z!-*y>7=g*^N!eS*y@7X9dWf}WLKDNI z&15LVON%At!fiIEI{p%xs{MNHQw&RsOh2390ScQ-)n~1^?fBXNKR9+cdGfZZhN;*0 zB1s?NG5NO?ZUd~Ewhfm($$>~NUW)UviDMuv@}13drhv!p*?UfYK0G%R#zA0J?}vYj zYRea$^*kjNmD!(2iE2PnN&pPB5Vsah84A@u<&z3tafNR%U^5y5ltL-i7EyhEtG48u z7@4RnF20y{7cUyoFL*G<#;KW$x+jcC#Lzh(x3aM*FD?^&XFD zlxVYOzaG#5__;LNaC1YCd!lYIaJm%da00g8f=l_WUQkLQE^Oq$8((k8+x|Dv zX-cilQ6e)moahx&%s{aMnBd$#T-QSpS!lr1D}q)Hhi1;7%@5a`pz9$Y#XC(;xtJ z)+D;MU2FN3PDQ>{puIy(8Z=m^JVt5`zwW7H0%xLl$yQgJlQXnp(@(zqjx@cGkHA5O zn+UK0&uNST{IZcd(3j2xa{kPB*xu=RyxjT$2#tQ^DmK2i`S^?-qJ-BqXpx{sm)ty0 z9aCTOWNnh5b;RAlBmAB$Rurc~12Jc4uCB!5MfX~Kkiv)>+{;BqELR3EBLa@Nl_RAJ zR@AjBbQ;Zff~vs3>#S}NR&+W=Y;(dB_DKHzL&!CDe>LPqb0;HrM~#<20ulh`L0`zx zAt6%nIXOAW-@(a1QE7U5`r~dlI5x|uigV59ve-ZNJH+Ty*EKO@KvzUF-{NIwS-INt zA#X-BgkR;o?0T?{K@{tFe*(mIV?lAkM;hV-x6FR~SbdQnwd6wLmNHI|c8QfO!- zb?z>sp+_Jjb=2DHjjZ;S->F?mQE~lUwK?G~`^?!` zFo*gvONIhOSb6*!<6z%#=_xaJ$$CQ5d+l$K+?oB&4l*QTf0SXq0QmKe?Y>@cJmz{mxRaHc8a6|s3eM2<%toNr!5rlA+P3sxA=^|*f)Gm9QL(EIByPnxw$!TGUR92Y;!w)InxL;wHn?zN?!{I zv(Jl;hELF*X?ayMBAuU^dD#)oNlM}d{N9g9jw5|<3vnU;J4hRa5=pMH?CyN#;mF)K z{kR-~MJe7IF>>+*x5!8*;&R62_HbuQhVnfidlT`~;m`)w9{?SFl{agCe5?T`BOV^q z-z*jW=())G*h^xksv22XMEQL4cfb@)V{*6Bd~tOCaoV8g!|i*J-O!NOp24bF|GS%( z)|IUzH@7ABfTW<5+TlP1DMyh#d!6^mA=F}~DhXee)N-gSbqWO_U@CzPFn|2gB|vBzlA`gt!AqAI5#l2CB>|5!$L`@IHsAsSG-zc zKFi~r!}MN_MiicR7KQgQ)v5ZQdniyQL#jRpPO~fqBq1PXslK=+i^@8^?~X*4ixc?P zdj-X|f{JRtH-c^-BbPOx@o1RWFW#wIl|H`MT}o$@iyB8;Hm+f_u8jyJecXwji!1ZS z$+d8~P5Q}1ZxV0$bCj*O-;Qo>gQkb$d(|6Z7Xmuw{Td3|RChgvEMYLjj&=OAG!G@Uo*>#ZDRB~X-90lglV?Z(=%#iEqA zckcuKxBI(f-WPtmE1;wFsq3klA@oRoA?jhCiTtzMviM1MwGafq+mqX2MK3}$k1+%c z{8hNlYjwGUF__KLQ?5m1G9!d%!3CY;XMnBx{qD9B%tYVsW`ijPhG-H-_me8uRvO;f z&$y^LxR!jt9QYm@6C*)iqBE>M z_q)_SHJ3@@Y-E;(E-sxlH2TkuXaG%byfffvIOI&<(Ljw~Z*!ckw5Yn3q6R=%&(w17ztgW%? zHiVFZe^NlFkp7HIJyG3L7~$jz39ReyieDhWRbO^OBNK}rTN_YG z$!P|I-$l@nk%O_ZtfyyaOiY6Sk;o#Zg#gmt(Qg_xs*@8A-hf~0qFRwQhK7=hxwXq{ z9fky#P+x@yM@9mqa)66-2v?`gL@-QjYXua^-W-*Pr{ ze9{=PxI{=4iI7S*yEn+aHx<9&p@RLkpuk=DOJ$CK(F9)?bdqm&avGzH7rITK(mqQ> zUyZeRCeLuibCo1CrnX%-bke6@t|)0MHx4eY*=;H`15N5vxzM=BWgzflAQ_gvX)6GZ z{u2s0EI-Blv!GIr@~wS+7ksH{>!y>81GJSN5DzOy{r}&Ckxy#S8@rD@bN~@1v!nEv zF8~2&W#wP3KRye1-jZ%UhCBa-{#U{`M@i$Tw84lfJLxaI)C4zY?#^RjJ$kCz)X zV}rntSYbV_G`XCrw6to-bfBG5wjx9B+vktMrC`nvf1&U_%?HP_t-!(RenLc(Y7!UgU00{y#+ToappH+EBgp?w z$U&$+u=fo4_0KHQ(9sOS0{w`F{0cT#7U&bCs+mNJfv~|cHkU_uVKo>Tauo4kI9DCu zFO)QVh6ZC);yEJdM#ne6@8eVNnj=zaIOb~g zn=AX3`81pbDyqYKwh$4ri`5QnWeA7hom*e)t_dV!B$#RDqNuA|@IeOT_I1-!?u;4$ zAnv)Ed?XPLY%yOYkWmZfI|F~LSNS~GGj`WiJsV8UP;J1Y5c}=qTHxL9%RLfQISd?s z+znM@%U>}H?zGgOk^Z$H>}Z*Z@(P}~8#7UL4k$tO$PNMGI2Hoe(18jT>i94b$ehz) z#uW5zOg;gYV>rxK0uQAcG(9ByQg_%^mUt)T(;Xfa~cxs(iDGDwva|g;$Fnmy*T+be!cD!EqB;aln3oayL z0Q}-0df!73crh?L&ldwJ#J<)R%AN_k>)*-XTbE~a;;+|PBSHx(GE*3?yZQ?dm1#9w zX&+Hls|%5pY_vi~w5`0WuioXCK{?;%?Zcaql4nKcKh877+4WiUid1Oe*adHs8%HFEyIS~8( z*VJMtQJ4VM)N3RhJz>S47tm*^g4h89gHb*NpbQ~ej`rPXHh~`kkX9&8&*#z<)Z6hV zuEAkU9h3Psw?(sT8aH{D5NjXv>dDwVthN+6(7v*2%SQjBB`iGf?#`ivdO@#cYHqs2 z)+Z{M@6_cZvUnQ;;P)yD&22+2weJ8Mem8vdYc~|0)qv6RN!DIo2{qFc5?~b-wJ0WY zRZ|zD&r!Q`-+;ULc8;eSEDivWc)ts4ubF&4&;*O5vaWSTX=Povz}G^a)0pg05#p}CEF|jL==#3ZpaGChb{edlk~69$LnZEto6N+OaurCH zU#EMzX?ry~&*Ny&c6ma^;NTtDSPj|PH-Ea>A9eF$g-olxl)X7;2j)SB6rC;SbX=_V zEe-jJkQMTe+Sr&|+nDpZaBZwvA;st1&Y#Fb0J7*JC1odN+PoQP-9(m(k>T;rOIBKx z4?p%;uX}|9v@`I38r9Sn^cPq*QUt{oDp6^%dpYmTEqmDPwrR|+Y2#_Fu3S>`yT9FG zGd-Ui(IzwZPH!}XZ;pX%yDBOJ2;$??)ZAV_^5py(S$Q2NqJ{&bqNz(Pyh1#9Mt$Pa z^q>F|ElE7yGflJCNvzbjsZSU5a-N7;#U&t8fvHU4!TS0m-im-S?|)C`haClQxe3r< z5;nTreX(`T!4^7tVCUT)`sC)0ftK`O1_PKfgBKPT&r$f7m;m-15QQ4LzeLk55%ju> zk!D zOG#P;_!2iBmKdcTecP6sLqukicSgg7LK@#hyQN~nvWP)pK6gH15&8W!9L{c+h~VDN_v`tciQVI7MBGL`l;p_}FLxjDAHQ2)KZN zQ!TB@0)G9Oqf<)xT2{8{#RWdRTC%B)U-fllttCu-%g$6a`2^KPA)Eg+HKWD^ z*%~YdWtUjqqbK^Ul_E>0r#IM*&~2 z<@4Lz49Z!mxBvKdgR2v*9u}{UOjtoMcIfIA4`rn?k&J{4Bw(%Om57;Uajws8p)P#V z@kUNhV#=7wR7R*tSvvA!$82+{kKg{lz(-M1)yBtD+;lA%%I3>uUh#3v0~;m^N~DzT zcpB^Y#sJPbf;vJ&Sf>{+Wu@Jc0^->VH4EEyY*w?Mn3GpK4+H>xeh8&|X{f5CK@J32 zVqpP7i^!x`&Z`NJLx%6&5MFzJG5P|4eYgWd#faVNNLg4!7HqPxp;185zHshkuLjn?mgH9|@(H7>{COnbNE|afc=DS~AApo2zVU`!@NrBFr z!AbY&D)vQn#dH7eaf4BYZ&1Yx9G>f0^^#9(oy_V69vYUvJw)Uy;iPB}ly9=|&J53? zSw=_%4I)88y}QwED@r0uiyxYtsUZ^x`@BEW<^S+|Dl+GexB1=jEx`jN2*!>;J)cEygG%cbS*Tm2)M94p_P-6fkhyw zw%T4p&~jmItTR}5)jvCmyP%H&hn_B2mR;)yw-!!r?v?kpS9TfX{~$FxrDy4UrS&-@ zW3yt>92hbbQ{`IOSyFZ~BGl!=<2W$ZL;0Z<|g~Web!-np)+;v1+}x*BIE})|fAt*k|U4{qy0s=Nwfg zN)uHPl#3vwvt&rdQ+~Psz?j*x!-ZG(0B2y{oW+_`*1r+-@z2XM!78lINfql*fG)O&77$5OHVAN%x&JqaRnyah$0c&I4(@Ig;8SC1+U0FZeKX5~Y^);~b9 z%-7ClQFnCwx_npR=z*&=EwE=c<-%g$)^7~%?HjPh7h;*(DNGf{0lSIa>+&Id( zE%b>D7s-{Om^DL;lZZ-Z`bA392m+;u6_9MRFa?W{jqKfI;&1<#X?Vv={c5df3CG;W z1iPUkc_ngm9@TJ&+OQ?iqZqO@&8`2eg+2A1yeT!sG_jM-h%pLl>|jF9U%ky`4a(7b zAtXo|yK)rlabhnd1a@~VF%%{1y+B(+9j%H%Mg*7P7yKy**hY zGv-JI^fF58b+%w!BjHrtdDcM#ri$k#6Y0PJ9x;o9bnsy0t8RSs{O<&iYM`iry_RRI zm@xt2wmArhKL1Ce016U0Ap0q(?88@ObL1CexF>kfg2Oe%|_o?rjXdW*(h*rn> zSH}tZRkB-5)&x(7H(su{<+%$6{+AlHB>;b8@wt6o445-JmQXvE(oA!};^;vD;Opyj z@$hP%Pxc>g%-$#C94|1@;T!8oQDBaZhtjFksQ#%{*Zg8i-C>mhL7S?nHhd3`Z*S{o z`k5e;xx;#9%zJnjKlg%Rq>6LN2_#D(aCe&=o;%x{k&jq*mX0Cpu3#j}#;j*z!8_mK zvc{?zxwzgTz`zV{X|YXaWBx_Cy1C&mM6&L6jro421af*yMuLQp^7$)RG%fb@>VWGG znS&IfA1w51uD3pJQG^5;opJ3al6rFL*XLK5U>t{3@M8#CZ(dK_)0>i$h?5n`U7@t6 zaqtQr3N9vA-rI@Zxsuct5Ngl!IGxznvh5AwscaMjpKzb+2qv~L-XVn9M7ub{rX zpq`GI#?@yIoZB5iR6 zXmp%GNO|RiV~t>PAv;sdU*U<(6U+e{-#w6Kq4g>??2lK*O_$x(A^uC*nSLul0M=;6 zT=9%re0^jtLkTgzg{J$aF6E*pxB`FT zsN?Lv|B@{Gevcu*XyW0KAVu1?4?XW6+!Itf`6($6HoM&q%zZZ;3?}&w*GBPC&9PSx zBbxiKZmenxS<}g5rd^y*H*>R0=O>0VwWS3%*GgFZ$aQ}U#Iq=Qy_MYANP*`Me14ag zx0_(c#leaoBC5_}GIIRkByRhMp21(Y3xgGCFu?_tBPlZYWj{#@DF$pNVuq9}q%nvN z0c-Am{@U%jXqqGjW{m%Rb_|0O(r`EKiC*=l%8Kc1<1x87ur-L|@KqUKmR75}Uo3oUNka(*F)*rEu#IgJtWSp-2`e)e4`uv^d z(XS)Zrbu9_cMAeX7_5J1?6$&#&$o6rHBY;(C!#I=)2BM3R`Ko6#*7G$PKVCot;-J~ z$$PR5ovA-QwlE&kl8@&w0D-yD(d#5S&K5K5aEYi%iPMgHiwodB%zG$gFJoo+=A(ly zhacs0tFoLeA|ewJ$vEi(JCu+jKTdKNV^oOP2P8|O6~Yu&waCoWl6=X~rV%3jXW-4} zQtNCLMFs1RtxGZ_$RL0|YP67`V06vT05xquc{UNX%5^hrT}12r!l}xtrl;0w1kDAc zg=PIh@QI27G7?DyT!d(KZWBNXK8Tgrk9gIaV%RPLvOX8%{=M^^(edw?{S9$vreg?p z`iI5kotpBT#XX&%q^0lPQf3*C&s$}`1H0SabJ|yW4fVmTT{^g!h&l{5H{k!pZbU0ss;bhl zs*<_>aiFg+XkO?PE|>_$(2&#uQBe?xgf0TD=&lG1@XiPfiTi9%rtx;0u(9#JyVHHt zc^M3Wx|$@T)n$22S->1mjHY3p`SEypD8$-UhXkc>-aWmzScLPz^YttI`E2mDJM|L} zx>qJFpZq?8XJ&*d+Bk(7I|}oK0t26#%Rsp?BUTP-9xNQ8l657S950@Ui0n^lpE~1H zL_pUsc2eI;I?lOMUpku35BB)sw}VwIaFv5#yhmGJesXN=pU3-t-+C}60fxnpQ3kiX0K=;-lXuE>;vqKFlhiidV?c0E|GXwzGv&Ueph$Av!;b7ndnlV{N(2f<#d~ zsp6~6+O;5rCn7#wWicL)ou3+iV-xRdsb!yIp=<`D%bol;pLqmL^SZF%oSFf#FJHct zd;=>2tdE2rff4cT#>|ST+Re)98VbPaxtnxjYvX1^Pbb;vr-SiTe}5k)4MPK^6M0WW zh8Ls4&`{}~O37DWPFMi4GG$N@l##J<FX0sy+L>KsZutZNqa`d%H;n~oTWRbjS-@s+w>@nPd zJT8|JQQA0{D_>gol&gco8=uE*0!?$fCnsGp|M+qG~7Kt4=Qtm*biL7 zwc(YMI01>gY1N8#7GAHK1D!4=wi7E$!yz)z(dEM9Nd0NEdm~^AxUy_7NSdZ_O~^I0 zGIMhjK1&!BmFpq!Uwb}gEJj09A-leOn@%A6$iPH`27}=*p4{|H_Uqd2&i4oyc(*Bv z$^^V@19gE&+Z|?u(8rUuUPPRkT}#NMl+g7+D#afmnpIk#io;i@()| zkOvdt|Avj!9BuB-S813G47`Pf%|T%vbL}{D7Ocgb7$y%c+Lo5m0QEMrg}(2Gfb0fU z92SqJ2gOG#)Wqs)#j^)9L&LlorMMj8+4c6xnDbI$N*T3MV2dGWC=WtF&BhcwOz=O) zNoesz#t$Et-wSy|j4SJE)LU)}PW8=ZXqW%=QqNVOQng(XY!5C=dxrkh{IEtHYi5^J z1Y|!chC>AEn~YQ-OUo=%<)abQ6B*Fi*7^G zLHT4=4UO~V&H3)`jroLcVA^qb;QjV|)-1j2@}aFnBEuE;@Ln176e)>K;nb9h9 z|CI&DTINau5VnlPKgZzo5x@YX&fupt%R@6zHE_@;NCJ>5H*kC*yrDm^Ih=+BbZv_k22cT0Qvxm` zdDHMIV*_FiZ~9jGuaT(iYPw!$I9wU@Xb}++gv0x=UjwJ#ZYNzatnX&Tej&ggh>Fh5 zv!5-s%Y%SQS9n@lkt+C))w5hxAVXR6qni#&B^Glk1!l9*S<#MK9?@q zkQOC}Ak@^<40U?DPVw^yBItRyceJ%RJd|C9fl$ACJQl@3;l*IrgQPVy+@0%}*4FMK zCowNRuDgaFLzh=HEp;IXvml(f-q)VbptES%jnmR)R>Zv}Fn3L-vX8k|I!&8sbhsy%!LqR)tfoUsu+2@zO{q5P z_Rhyj?Qn5{g~#D_ey|h~F~T1c@xK!e6gA-LSe&qIGeh9KeNUpmIpvTZ@sUslGVvdE z{4ik436|ezY^X|-*u_OVfT*pV2mIt^e{@j{`sLf#r^Ik-CZ?LN@OG3*HNKRg3Xd1) zCG!@zxTWu}4Hder8C+}{rwf$0_1}|2i+im_Y&% zr3q|fUXfXv|76VeUbmYo_a3pVw0{#_VPBLgs8<4AL@+5UCn%}hRRx_u{g1~9=ic-b zC{SU;D^UMiImU-(9nJ&(!IgF7EBZTIUu=eX1+g_Ue>$pGmcnT`B5>&gP|&1~(5)?h z(w7`7bYn}~Ki@}Y`zZnfW=@Xib`oO%2;fG2R+1PU`lc>o9@gkH&{}rm7wt1-DPDwOkNc{*YDYxcGss_<43= zcDXa7<%j`0KE1*fx!{r05q^LSBW>#af?dDJGmYEWI0ajEm=x6eDVsZcpGxH3;&AKp z>2?fjff(5R`$H2(>r`&;!Lh$JR(vB6(xNgi3b^tk(zvX$QcZ`h5NBv+agmwTII4 zuEE@EIzA`Mg3swubKLkbjH+STc|Yl^ah3HD5F8vle=U89&$Z~XH?_U{+<58X>25bR zUQ%j#W3$oW_A}Ju8sYSR!H@sI!RItZ2*0+rx3)Gfi_7r~Xt2b(ie{A{LiqpS+b{nE z8vpO;_A|ux5uSF%Y2acqH<&-=!htJ0(G67}O4YJ5E%w%6SRe%@)GXxsg(ZNf#h!Z< zJ;J0hw7R-s&g&Eb_~pO2dL%7;B-fp}F1F2j1jO=D9shG~<20rf?vl6FMjS<65y972 zNGuu&X0+nN<;%z5Muxbkeff7lr_TEL44hCcsdOzV%PTbuj7_#5B)y+VANc_^rTg`` zyX}P01exQ2`*7N!GV1sNj{INjJTX+Dh#&+q+M2+h@#ylLV-xE{8xK&ba^1WE+=_{P zz~O6$Z^=b@hfcxaT&$<9^E##RBpg>pW=bX#0|}aJU$TtP(Q4j(^q!{>dzlVCibTb- z^M}$hi1`p)L@<6|;eGAsSiN)HAcrjmQ2W7V-*5{-6VELr2!<`S$ ztKNtD;?&-VYfW{-+tselD8^u#9Vd0d%4jd^r?&lZLTR-kvVJ#2!-8s0#l^@;np$N% zgWZH~Mu{WV{2rQ${j{SYN$HR&Tc11CDX_;nLJ+;7=b(fKi?`IZGiA_2UJu3^N zgtX+{gHw^A`t5rt8+Zc?;`P8 z=1mV}Q383hjFF)I@X?XF?Pu4$R{dxa{ei<0h4v4j;`(-FE}kAE#wFT?tD&J4mC*#L zeMgM0&~T6B;{UbF(nq~_f;W_d#fc8lPcGnO;NxZ%k zfdaKC@86n{65`__Crmz0n@!N5z1Mmw3%jY0grA<5%wFKagMT-1b&$5ulHS(&C9^n1 z5@KvrP$<+3Tw5&O`!;9gV`wJq9Wc8Hk~aZ+LHYalJGk$6c@I++3_rlHJpCC3u(MsR z`vy^qNfq4KwsvA+ldH)OYc^}ok#4*{Llj7<05w0lvp%tK(RGzw)eadm(zT5Zjor7> z^6KxS!QXi~U-^7WvZj}d!DLW#YiV9uT}DgzM;bKdZUWK4XUd?>rK28&bV7(HBQFPD zOpX*z4vdf8ZQbR!Xp#C3XV8rIAl}Mnd5T05G=LuAj-0cZHRf8^C=8R)ZU>T{bIdE8 z=pEN6P-?I8PSDf=Ds^zs7fR3NaTYWkHP7lav2g`~ndWFBeZ(#1pHiim;k{4CH}Rl9 zGW4D5s&R3NGDv;lZ;-PZ6krWXS8Ai)OZ-K}m@%unDV_fkC|~aU^}o)`bgSS#x1gx; zZe$#16Zdq@btNAg{=LMs;qS}>ABRih^c`ZAUNaIJ_dDiZ%KCgWb9T~LKe#LD|WA?LddMbFA zCXVVeL`1qjmLq)L$MOo#Z13`Cv@0mdAvHO<-5K7R^v&FaV z)v*Ly09pY82uMf?k7#PFe6wJ!7cb25hiI zUaQlkhlfG|et(k?a|vou7|}_D+Z(&}YF9%1yw)3h{&iRN@`26QUa=o(y!gZ5APWgr z7xR~dB!pn0Wzs~qCazgCaJpfHcXW0+IXcNbxsTWaY!ZX+-p_q0VS4l%*5BM6K{z5s zv%lbb?c0Qx%H282%XU&tsy#flwDgn0xa=a#F3)c6A9hkwEY7a&NXt)ByxaI}1c!i_5bVlQ;3SMcYkDIrZfD=9-F} zpJZK&3o9o_!(Jsyl~`Cf4%^%VVxmz|F@NCsp8JX!r zMvO0CzhVsbb8wD*T-mnn{WUMDw>-s7qzxNiZMqOL=bW5#lJq)j{}vmP-wPH4EDV2U z`&D-R`1rCU^GK2D>&^xrgX?1P)t@w0=ND}^S)Jb8rXzO8ni87@yQQ*xayUD@%GTBV z_jf}%F%MhcPwa7#iLPv=0@&|Yz4qGWs*eu{L_rPv)(u-J-dEET!|f#hV{6!cura${ z;+`FOyea|{CYZWts8If>v7ZZ>9A}{QfLxTsYI}7%0TGF8C%31wVCe$23SQ5py*Ut< zvfK2=f*7-p1bZt5le$Hh{gXo7n3tr7~R0TvrY{QDohsSU}A z*0w6|AO&z2ZB? zdpxAB*L3_SlEW-4AR`I#;qlbvRRrtd$O)eJJslSc+{PD1LRJ=NNT39r5q&3Ow;HU~ zn{TJSTU??;M~fL&M>+d&+$e$5g3|8OQcq7 z`?m0mzQrwc&h2^EcRR@RwvF_*1Ad>N|Fm@E@BfXC|M9y2vE%bC4 zbg&`0TPyni@6?5U1QL+nS33p$9~sGi1C8H^Wtfyhx~W4tW&I;J0)3LOb%zK#8Mx)M z9cw>~uG62FTlIhOp3BsY!#hl2*_;iu70k;?Tu6GQ6_VK6HD_OKXlx`Lv@eO05f<*{ z&9c%R*w@JbV?|utebIlTpRSe2M0&?CAj=xB^~= zN($Q?{KsV1Ns?Iwq=vWD*xS`bjJ)U#NG6X>4yF-uS=T`^u;|wr<-dxCVE3 zch>_1cM0wq+}#og?$Wpu+=4rV;2zu|I5ZBy?bZ3dbMLsn-?&eH+^!nkbkW^awQKFQ z=3aZwRg<3n2I4?IHyOwmRIfF)*dA9pE*xr7e0AlV6|e{DJ3I{zS~%@nRLd!Bbi5(l zp#EdawRnuA{>UV1jWyh25IdXG`P9R_IJAbA{k^cT`}ml+96^sbMp~{Po7Mr7kl*Az zy%aVdZ%z$6?qcKOifd&3sr)QGZdmB(;Df}@J5VQv;__G-yR)+!>CaDZ5Gb^@f$<%@ zS5W#r;pp@S(b`C@BiJk zuT~0XW>)-48Xt7ECsTOegw{x!isG`G=yYbRbMFRA-A zWHV63kpi3WFdW@?Zlhei{Pk&;tz2?glM(#Z{BpjFDNNqPqow}qC16wTcXF~`Dg(NHk%Ia+VC0EGnECQM`(;l|&yk?d zY-nWhF>(P%Y)<8gH-9xW*Dp1S-e^Iou(QjSu*frG`Iii>_MKi#V+|>1Z+*|l6!2m{ODs2+WulU3_V|S2#P~(Kidd7!RB{GFFx7Hz z@51tkk+JdnzVB2rDyI0U0aSpP@BGk2VWFcI1r;TCy{o!1JP7h^$*H7)0%ynwPW1Dl z!BhZ&=q8P-s> zpL01tpQ55C~ zlI7ykk~EL*5Xxfa>`_#V8J>U#GNA~el+e&!K;vj@EpTS6RBF*i^e3gJHkX&%&}#Te zxvsTcFLnwGC@bG)g3ob+$yFRzEd~Lq={+Lyp8drAS7(0-+5JlD>xmlZ=k0c<{091Mm*hqZL=u&E=wb|o&GS$^2_+h#_=>dKx$zGdp zDT+q2_+f@^zEtMskRh@YHfBZ-FYDFSWgrP{3~Z%qF_Ch8HZ~3rH8-y)3Sj}u(=bi2 zF(JFL0Rv;l^P1{=QP85(hN#<7=Hp*5q(F+bC*Hr(1oJVWL*9$s>kq12jv2zbs|OO!_xd zc6@h1CFayMrC{LUYrnTM-ln1d`r9==t&~&SYy`WloB^oi;mE4>UIE)oIncr)X03y% zTz!`m8ykD>Ejz(@oBUV4cnI8SP~U&F=KsA{QdmZWU>)7`Me%z>c-?O$d8 zUY@lIwvPq_clPi+?Um`UxN%4@ASa(>&O{o2N`@F15+6)Gja=Pbu4L9tPH$)xs2;Ls zdj=ymo4Oc z%?kDw_-!rpQZAAr>jooTn+1w7v8f+ilyqqckJxjOVoKlblnd8fYF?yYudD=a6r% zA>bVdAdQ@b&4JH|@lg6)uYD|t?qs8TW%YAT&LD+-7Pl8q%DElP`1t*^P58&!wZFgT zWUS~pkgTT#cJ`j*{jzCXG!87Nst-FoF|o7c5#?~%pJ{@$53cKTb*>3uBkk1!lmT_M zfJ?pG?yip7gWWwGoSo?>tR3ipE+Dm+rN25fNOy8-zwva>b#>orSyDScZ>o$SCH^X^ zUf$STa5VS8L?0Cv4pXB;+VYn3-g5Mit(++p=yGJj?)aeZ!mPBE+{42^Og>rc{+zGM z^!>P`WKLBicDC+dZ7w3n>q)~@+F6k*jNrJ+=X7y*Yw_#Isq41%ozcFLDRtSB*2+{rt-7i^@S1md9GVp?eusdB6br-3-h!V~(6i&SS7kzr zpa8}2)WK=UTV|p>6o}$WVEG7Qc(*Vi9#%nVB^9{9$gw8r=;%DC_4lp`9tmj_xom*O z*ff@P1e`hL&(>b3R9q_(LlLo!&h@nw=~~cuFUY(a$-gD%B}rL@=H5rJFQrbX|ZIug2}6A-0}paT7#1Tbj8|%%w`P@J8&4iVFhM6mmtD|mZlHX&h zgapd3Z|_dqj33==LGt!*fiW=E4$UTephF0ooetn#aQw$Ih z0k_~0lY`}vqZLq5|9DwCYL-^Mvs>W3fQ1HxP+AnV)kV>kX3yhi4so}hp##NT&#$eA z5!D38bD@_@mYjb)DO4Df5#hodajs~;R?wRwDij`P=@Vw^39u@Ez%i$iwA5&BiH-uK zp?dN&N%X9L8`_V~ zFblsqG`5xkPB#7q;FWMg{cST!Wbfjf^)BEuYHKDy1)+qu{hM;vwr8zg5@fDfv6FFb zK64DZ-{(7)PIiVrm@pJdw#}(d?W-PPLq0a$fPiaPB1D5jnCx%GoD3e@0VWbBIwjyj zx@kmWBld0VEe?O0ck01E>8Q(23+@smbVZ{iq38 zOQU^cLs5XIW1!c2V-;;aZiKh>ccAA^kqJdzk{pp;j(=e1vtlk1A9cP8`;T+HloV~i zSv0jYGAPp>5;T~%z?%NyB9nJW;1OniNPKhAJULPI;S zgk@DKXgR*V$Gb2syPZ6vYjLE^QX5IJ+&qYcqlBu)+ zKGmAl=wnHX@Y^3{F{gOohe3ZCnqT9|bW@p4mKIYl>*%IxX$c7~0YCubJj)gZ&@lA{ z``y2g1>S-USbJP5f5)cfm8#NP>>HZm-;QiKwfjAQK9_n~NPmS32->I2C};4~w*&py zJLY?Wfd+ldF@MljAr4I&LGULdOg8gVt<>6TknSsRnDKc)y(ING@?jbHjqi0o zccxjQV>LIgC5)C|wT)43@4Aw}A;uU{q{8kx%&FKHqZ_`bDnOsYtF=r6$`XC}3f7)q z;F-kAQf6nr?=9%##GV*o^1a@}E9{euFyqWP$$2k^6M1Xa|YQ6}}F-JsDOsCfYrB>qwA!{xDW)8>vlC5CwPd8ygeXd|AGjvwBB{BGo= zKK$y83Hnt@4AG>=#!99njFm1{udZee{W((FELMOtU81_Qtb;|d0+1de%X0dtihjx^ z7W9Us)y>cGVf4i(Q>y7(^IdkRheeZ;M@ST@S2MFX?rl;;DXC0xH#K98VILFeU!A9X zaFLWA8VpkH&If_KEpaeR#Yr7&CaO08Qt-gIfp<;v>2N4M=yy6Y>gVA z4DWggF zi{1iR%c0$!Hb#cPkyZFNsXyN#8WMOICrN=$r7EO%y<+$TQMO;_zRezSV&|YEMJ8lo zNl*ZJ`G_=Oqp7c~nbs7^h;y zDWDvln`?Zuh>99}gn=fULhF%gs0o0|dPz~k{E69`HZ|gN6UJ)m$t?gF{*ryg4Ea@FxqhIcgh}nNk z%x8G>SP2P?pRyua4~O)fot(jfbo6yUgof_!;@!~Ct}iKnFeI{5AfeTGiR$v!pvMMc zU;=29sO1ViT$-O%8E_{bI92@pxHPb^%!{V$T%KENCh@~=5aCY1=Zm}xsb5!3Z5&IO zk#%SX>7y`r%ID|lkEjT%Y3dRh2d4fYT`J>SR1 zgu%jlx4G3=tW&RgfBvSv{NvYmS!U5dNH!-Fzrd1`a?}|E%#ZEb#6Hgg)p`yTfLy7J z=K0l}TmK#dJ{&k7Y$Vt|b{KJkY+~QXi`7r&4jG;Hase9A@ZnvI0$=cz2H2}wJ2?e~ zHN(r-*T?Ws(6%hpMKS8-{+%4i1L5Gwz-y$AOmr&r6;-5Pc*&10R?F|h!B2)p0ix1m zeXdbPyO8HD7MAg#R%a>7MBs!}(1dc-0GJerIPHU}83YWy1C1%)vY#of;r-~~ zg@Ogg(xiu^U8fAYHx`x$JY9dtc))}ZqHd^!^!0HwdQ3#^h0*|l zf0&+wwYj)DWh7N3+L;ORG= z#j8Y`058YZ$tNys8Kj1pEH=bfW7x#ZTVL4TT!fsLd;tDPJ2>#@ z>ys@QuCkwzQE#+*XRjvr5ZQ2-Bm3{zqsy%M?xl+ZC2QSuLbiC`0~K)fgdtby5^8FT zpByX+ejkg7h$Lqjrvy}j`o5n}8ps+a*ITbzjm&9UdKfx?u6@_H>-tc@7JoLnChLE! zt!Z$!vsa_K#^oJrx>Uf{vwZpCvErLIVJxp8@9WXxP(&b*=O6~Q`r5j_1IwRcq4?j< z*Yy@)2$x|R-~P{L+lR+{x%%IR zmIwQ_x^2Q-0o5kX5#CvY!$tJi_ywean`tsCRl05d2UzzlmDMCUZx6TJ%Riu5uV7h@667&J!LO!5`jDThOVp({2mwI+hqL`y%165RJSNE7nO+HWoBNQ%zju?P|%zCw#)Ss zj8^JC4bJ?10!gqcI#SN7{+o7aFy)6j&Xze@J)M6gb@rdEHjSS=YsBv7kDjdF{AcFp zGjek?MDT!;+kf0WfHY&l#|_^jZfqca#nsiJqrXB+P4_3;rTw4CSH05*zMJ+b6UkTjyoF(rFRc$c7a*(1nwY zazEC#apzPHpoi*!WgLUU+sE4?RnCY1xO$P`69$&m)eZoMjMGiLBdP=J?5ym!fhm47 zej#%#no3kWkcPml%svR9{FVQbiuyR$Q=(${QUC3>SEW;A4F&a2xQz61W+uRV7*PaY zMaj?Ebp;OHeotE#WB)ZjtN(+k%h>pBGZjTDsMWivyvdMjr_@HBn)NprAbDY2GpVY^ z=K#4#r80W4Pv~pslDMv^9cQ^Oo8_dpoKwallzc%X4A@byYojvFGf}n^)bwRT@)1_= zxIg`!fV(Y&D|q+cwpL6&xpq&UG*vl_epqfPT-Z4LN(Rahs%`!gl!kRKl&Np@ zb)R4#pSnpVpG2pwMbFuDxbm+3jEvy_L#y&%X@`3pWzQUMnbiLwu(>kG4+bM?G3u0? z6#wfFm1_0>`uYE4IQ}2n2)0HCAN4BFhU5%Se@6)K0~92?hf~=w0D&?$;~9f20{D+n z?hMuF^ojh8@Gpe5Y<$K2Q{U;FFzK0+4dFFIdcm%mn(jI^#T7V0(L67H4{bLM1;sVW z3`OP4x36-KE9r{+p8~r4wx1ZmQH?iPHIwu>H_HpeDSELyJlehm(I*R6-+Lp_ z-apu{*7-eDcr4mEvStfmftV*IL=9di0fA?VAUEiHUA^~t^}wJ%6=7_%x9RhQz6aAn ziT$f>T2a^OH;@=7L3&mfIu7p4#F-WbHI zh+gl{pYNyZQi#z5Q85SAQi2ThpvZInKdH|65vod!lxxH>@zE2hR za&HqfA6JxdSyH5>I`B8zC@w77$Gr@Q)L|y5u7zJOnEr&k-=C^&lq_Ft;rov$DKS$1 zqtZ@Y_Na`ZK&pm-jv5Hi7RpRTEY;c7s+VUHa>}WwXwxQhWr);@x%5``P}4#E0IXU_ zhzK5D-(1vbuL7HN>c`;9&%u?#%1VI8IQZb*ifrKOhAd#;9NQ$Nuit^B$yi+Pho;If zFM|TNbFF08)dlO?Wm;CNE_)kvEh*B%2F@4wQ-nNeg_ILL;yA08<|_$ z)If3t9&!eD@gQ8VX$r%v_fOu4$5Mud^$G*ptT@J71cV8st$Ic`i|-db&VNR7S^I>+ z8J#)413&}@+nf3n3e>A8?!Hy!qEl1`uJUBxzYP_zR>Hb5W0?k~_&s5F_&puvJ@m7A zCU8Z=0c&PPM$FwE)aUecS2<%Si1tT}9b?z<(}B%$quZ`U_I914>9UrpAr}{g`Mfm1U%-2U0qi zaLB)gqC{OXEIz&)U8#X$KF2xFsdB3y1IyxWbkLyNQx!WK8=5|{(IUKhc0P?3 zyi|jyVrf94y6hJbb4s^C8oZQ7g;r9mZPcN>;u}6-mSo7Er+t4G#d9pWYHMvya-;tJ z@%4Gm1T^2a=8u-8RI1?~W!9i$!Ot4Bb%po$)1s{Y>x?w=NP?8wBK$*|RACrHGBnSa zl1!Wh#98L%8ExI`LyA23w$_FR9KNrN2>Rso?fq_C`#$po$_fcwhXZQV zUz#d51{OCLcMu3(NIyWND`lQ(m;jP>YbUxc5%-V$LzPYtEjCd61LoqtRnXF(`RCG1JT!(kqRxy3rY46#aokOZ?Ljy znJVU2gHDa0&$S)#{k0u)r2o05mvm$##3MK(`PDi#y0(u|NJ4)Fn`srrCHd( zOif7za*d$R7q=uVt}CaxItE4_N;7LS)6)dlhf^lMxRm$s31e=tTR%l*z4^aEhxeV4 zB2TNC0@F;trpJwv{}Hjq$ZF<}rh0NS*<8MhD$EfK=x(|xaNY7AJi2e2t`yV5#Uj}{ zu~#XbjgC@~DukQaRH>+N0)iZG;Nx`ni;dqMNlh0`V?Fog^=5Tb(~aoHJCLkcU{nl= zL6kyGuBe+Rx5dnrr2;6wQsMUA<-Pq*Kv$aqiIDzr4^5>C)Mpi5$I5D~cc1sgWhbUq z+8=`)-kJ~8a@RNM`laAu@0q}(Q5D94vKnmAQG{nn3iY7sUGH1jowBOdgUor{iklxNj@4R#g2?|cVq_ntx9IZ8%O)L@Zoy}|YB`;oVFm#d+eY7kKN z6j%*v;uLB9s`Q1rS7kgo@KN@?P$tHAVe7^6718fgjA|kC{E8Aq)81ilNhMaN;ZOOz zQHAB2BJ&{7_vI6{B7RIctRpgcU~Y`p!GI-sMC|26A+c<$N$OH9v0!g+{u(PSi2@%5 zj!SVzbb;gC7DK>SixK=qrdWO5FIX(^BO@6j`L}fn8OiY`V_QflrI=W)L*AXR1|?X& zXQRzJViuSW9=|}_A$ZOS(@kZ5As4o zV;NY#LH!MbmQ{m(d9VWqa83LVWclv_WtQGWX7>GDTs+gyrM0CBnNQ>RT(b`i=6`PH zHfKfoJllatcyuJzZA2O$czqUvB`Wvs*}o(-RJi**W8>Y+P#33@=Wz6wZv|6c(ob3q z%T?d{VVvIg{@SqhFC@(?8=`|;Mm~z>l^Hkx@d%lI+(jTfkMuefaPBH;emdmf;N?U@ zx|nK9SX`UI#6H|)IAvz3B+q&2@7-8fUB8iijO?bRc94vWP7Y}b_3>RGn7J_MA6sYP z5LKRYZYjCNwK=}dneLwd<2gE{@oQy4%YToN;2-h$6}KwX=lUqfpQ>5zC_+1P9*?X3 zT|Ohj*Xi~z{(Ol0D)F)4HuZ9Yo`>j3Wd@sLu{#E%=iRQXG*Ndd>TrC-SlThf z+A&|lnC4o94Ye$;8%kS`h-kW*{Fl*&pIg!eCc^gU9ChkW!A5j;U{IwNh$j|CC`wR^jsIHdCEZ|C@ucQJbl-+Pw|H z&z_RiKRZ7b5f;?aSWGZht@4W^C$JdArukiD147)kSTqugH8z+27Vv znNlJ|ss7=?d(XnMwVj^<4AdD38(Vb3fVreb8AqA;cJnjosyOlzkt30Ha(hxngbz8Y zSS7ct$Lk>V_YV-Zl$fx4etYxQOw;q;NOT(QpwN{Mkk%e321{9jH8Hl@*C2I7~@cUXCLLt{J0=Q?u5o*lF5IN(_7! zHS%)uf1y*;Fqp1G_<(joQW(w2`7pgzpoyqaZJfM8rJbUs>E|bVd%irm zmiYbqx6YzUKNtJCl^8zvAxxpGHvn~J7fYDLSVc5)^=-NKv` z|LxD2qn}WR!s1njuSKQ02Ifwo5*mb0h&y>+XwRAExP<)BtcHe?l9U!C1%%+Gc9)#^GSx0=}l zg(=?#xCtTW$5@2KOO=P`WW;*l_Lv=$x9M7CVaPN|lTgt#HBEo+(-r5(L&O2;P&Waz zp8MU!uBbr5*jqOEjoAT1dB!BXp&L|`SBO`hsorT$$DWvwe*&XvMN$+*Ebk2{wbdhL zFLrUZia`-QYIm0R1d)%gY6)@%?#HHWO=rxuo_G%!Coz2Z+w%lN2p?Ax&;HrtJ#{0F z0@h-!T+i(UN#A8$yzegTfaOH>%)tqJFgbo$fdBJuih2w8F687Z`S1ZAYpV7!+Dx4& z-Jtzh4J&i1l9HjMshpIWjir*q&hKCB^YxRe*c^YidCPtkQ2H<~@6m~PXpHgvWqY_F zJ>t9FrtquZIFASFh7o3D$1$JR870E@HudYtxbbloaL1s^n(DWQ_-mkooDi9AOcTCR zsP;|o1P@Ejix9ZkP)Ylm8BZbI6JminH+o0Caa)}h|SDf2oV zR^{!VNl1MwbGvhz`a@L4PS=|`=NcG0`wQWOsI)F#^C_kx^&9EK?0k6ELPGkYd3>CL zVdN2|{Q>@FKU=}SqnQ?C^%1{H3@IIR0Ul})I>eeg^BS+GWF~Lg6i)KaX9VX3t`MKb z;e77xEk=OD>X-3o-OTVZ^KqBUXcL&1bYj&f;7Tf3s7V)xyx zenU7t*4N?(zGfy&r254wY@~b7=4QCG2>C`@skhJS<(&4j>FL?qE&|L%M0AN4R^oMA zDJo#S^_2yJ!*c~NsO;Y@G^SeKZu~aXfCtFbf`|qBcbrSCx!dnBLOA$>3+KtSoV-s( zS!k0ZZJKtlup*3rCu%v)Lm*Md%ePW6KxUjZtlG2XFUJzcg^yxgcSz^c1`PC91>wTN zq=J{5@C0+L#_i2e1Owddg@0|bxvS;hh}c*O+27MTVm_xo^*?6gH*E30US1|gUQQQ? zBX7#eDI0hH`IF%W7=)%k|IDv_Es4_EL+(sH5r-2CpZTth?m)&Of@)diwUitIPQitR zX&yn|v-1PVVqr;qUN6;lJH^WR?bd3REZ>-=i8eTBXg}S-@2U5r_;N`w;$?FXq84Pg zg_;F67_irvuOuEU67fSA+cXriyHgpdPi;#nfp(G11`#H<^WLCCU$aS_s)>_Rsb)D` zxHK;7g_K$fwH$4-jlr(Z3h7&fyrE12+#n#Fh_gd9KvND_abDt(B@6(548uysAnf;bF z1Vl|=OQ}6#C-}vQy}h&z-23Zk5XDBu)|*{&0@W&~hsMko7h*XQP0zbtQhy|>fZ0Dv z>&Z*&jczZM1qij+axaD89v$BE2a_vc?Eo^Cjpe#!{Gu8=`$6{8R}mphGfM5^5`c3< z<>FRs6bd++hbOU|CR5H=(>qThBHFaRhiM_9?Lw+D&)jL#(f?{=wt7bPur7K8OxYi;>FT*&}w#$lqHM`tT4`n<`0dGopN;F z3D%HLO~#v@UFO$emI|1TxYz-Yr9Jk;25o+;S@>Z*EuznT2M4joT-4%_HyVT&srb%2 zz^0EEzn%NoB|F-=S}&4Ky`qXI0I~nFbqx?WeNC73aWd9WsKfwCb2S?2=+!Z@gj$+& zaSLCCHyk{{0p=%NdA2z8;p?DO=$J1KiQziHI41KreEJhKX=PpG3+xz1Ty|9m6(caG zMG8^@fuz3Nj4=Tqz-f5A%*hrF8LgtKyhtw#T#R;HK~5F~&-Z77UrfX`6byKthBuw zt42~MXl2c;q`COM^qMTOXD~Kp`kENI3+7B%5l{|FFE6nZxQ8v*+w&q1As*XlF~}hT zp&Kp0drtk>O6a+I95Jd8?beg=hTD-M3tTY4BPk*eUO`|vwz3`@jP!{=w9#)7T%Q;? znmXvo%vJ}yg4oMqh~awu4xLKh6&`a?@6}>U(=!f)NOju%&Er}#*XH`MyxG?`v)!M$ z+Gs9EYKz}d0=rL|0X4YGc_@2eyT&mCjMuV^7R2Nxk+l15KCeojV5Jkn!2y1hlK#`! z-fnp`mMr#ov^9N+Sf`plZ4wqc<`%$-j_^kWITgX^3RB!pe<-9!!FV9I>q={L?s?3d zP9hlhsx`Y{D-phW=Jg9jvM>atz@%oEc@hmlf)puqr} zYD-nIX?oyQU}wz~1&Jk|lipJRo*w$CW+JS&zY*W#E=OFdT+8K@q)Z4M0p?v-?)K6$ z(>x>O%tz{AJQY`Q0`Xy78BLq<(Gk?knE`ZB=Yb4hLcZ1Hb0eA4>_o zR+;iXtP_3|b{769;^-JEzfZR(E!fo%>i&v=m0(6U`+`1pjA=%3Q{bZ*%`jil4OHMp z4lp?!gDH!h=lZ;fJ@IUK+dkc(8GNOVog4n7hPRX2s)&|K*DSxPuFbV!& D+6N|l literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/incident_card-light.png b/testdata/snapshots/darwin/incident_card-light.png new file mode 100644 index 0000000000000000000000000000000000000000..0a9a82216470b741350338569ff4e7ffbdf3a124 GIT binary patch literal 30898 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/service_status-light.png b/testdata/snapshots/darwin/service_status-light.png new file mode 100644 index 0000000000000000000000000000000000000000..0f232c8b0d744e1953aa75e677164f44aed3117b GIT binary patch literal 19649 zcmd42Ra{%&9w!>0NO5=f;_hCgxD_bwZpGb-yA>$zUfkW?-QC?ClAHgTbI#1eJlxOB zeYjc4PIj`h_s`|K)(%%vkV1mThX()vNYdZLRR91;H~;_w7Y-8qO=C_f8vq~|CM_=d z-96)c|n)ApZnsR^y|?Vb*_>u&HRSZ(MY!@?R81bLj_GWU^`TyVglm zY@FvPR4f*%Wt26mi;Bmom68WBo(Bnj5+kLgL`?uWVN@an`YdQ@J$(4sXBUr*v^*Ui zN?#q>Ann^}a{dw_MTHB33wH;w01yFC-Tyy*Q)EUo+3oo%zP47bLdQ{-1@vWP+a}Ws z08>AlO*oFr7Hrj1ZSsS)lmq`}G^{7*;^N}+yqa*J@#uD(785a02=<;gX9~gx`PfN7 z(+4?FwCe0km2R~Si)br zhPDL>J3`?CVEiEr0RLaUJ?9&c3h(-&UiOO{4Xl4>mB!C{zg0dn4&8m;6;g6Ej>M@E0>Bx{KsAlf^x6*VMk*# zZlhyRgQlqGZb&{Ym3oa~OzON{k4@g5`ZuMZVO>eP=^C%e#~0}eMI2qCz6%74(&%|u zIZ}BB=C#@>+)h!L2Uco0nJ!3%-Wb0dlZUgx?VrTxRWYN5d%wRA@5i@tj_~tg-YO_E z6DwciTE$EgxN{x-L+x)8UaVG{Np8CF0!U=E%tHb!>{gn}_uI#{!*bNXG7p< zyJ8pCg;2)JWwEp&tDt;Ef25-PA4o;)twfH5F2%~FlT`%Swln;2NVxr6RSnfT(>dX; zSB4nc=ftVP1*@llEA{eu@t!?J59yPae%1cEA=N}&HtvFA9bbESIV%-5Vjl82R5orX@!e%*@Hp!lQLy4#P*E4H~= zO$IJyz*lJy=Wmk?TRY64slm_?D*_eZtHO2ikD%#>3H~CI*W`;njG!r|_sgM}S@@VL z|K-o6TBfU$Om8@Y%drR|aM0~U(uUK=k6eSwUV#=*xgqlmF0$#-U9nrE1Fn1TXkwaI zw#tj#ZJn>R&)3rED>mzIth$^JyV)45=+daHOZKLp#J(}wcD)P`pV&hmCL`Ke3@^Lo zv$C`AW;`$cpT?K-5Pm9 z=0ITc?sJV+phmz+l*t}*F}hLTQLc6SI@0mnqgvN5Of@P?j~nBff7EqZ=iIY>&`(mL zI5Izs@fTEfCD`9-=9^pHOEQ3;&t_@*WGsN_v+`)l=L#T_I5HOPF28= zWTK1+1^tY8eE>e2Y0=F*AP4DFCHx| zGuFnAY;0xpbV#0Gl?`**Q~2Fx((ST{`-x+HnSzw8R2&bJI5HKuFBJo)4ad9-oZjaLFo_K5|FTw?a64l@2ijq zcO`xvim=;YV`oQ5V=-KO_rMLY1pR3yOQH#W zGK=R0n5_7Xa`r?&h8-6%B*`?XIj^d#N=E1M;)vTCs1p;7W8kJQt!b6NxcQvLT6S6t*q4(%9@D$>2o*m6ah@-!_UjUcb_ zv>4RZhlkAsk{B&Me)yD77d6@0C7`OZrgd#WU0x9{yZwDTp3i93d9*f_^vQem3MHFn z{h5KA{D;4~m+8Y`at1-~aIiw+uK{72&E?ie9{Ml1u#NrDBZlVpvl$(4 z+tLG{B`f4G!RIrfe!$tiwCf(ECw>|MllhMR$6)fJ^-I3{CHUlwwi;aZq(KE;SY`vh zl_zm;6*@MCeVq>rn`w1l;BtT2KP89O94U5pd>S5UWHL8pW_MT`)=(cCXx=+33Goc`uK%fi!3jc(y$hsac; zsYc1^&XYJ=A@b$TGp6Dq76j~6Z;};@qIiE8V&8_OA7tK0bDMI6oyt>x<-G$TpAk{J>y3GWWI`yFB_TRs&!Qd zR!3felJm(D!c+N+{}L|oi$+;6G?;uuujSj>Sq!OI`7AC@IY)>rRR7HH?c#|x@D{FdGv zZUanqx^%qv72#iKZ22my61zK3Y?r$v7+-WAi%Lrsnwl)-#_-&Y+} zLjnTA*MtoaI-Yl&Mv;kVz+@_1v|w5*Y8X=b#`zYOxnIS*Dr;8#g=$KZGkbHN5O@gG zE9PeJvQF+buxL)``&paa>c&5kVT5y>T>~`UL$Fvz2_+FCpolktrb6zSa$DkL>k!bY zI>>iXuKk7H<^VMNez&&Ni}Ds(q?^UV*v`AxIyDR3|6&kmd(0IvpjFKS*~d~@epcji zMnHfs;PE7vM@=a3U0tW$*iGOhU`Sb3JTK>SPY-iP7-||$BQ0>O>e{zae70PDO0zyL zDE?3?$kz8s&_hZp{2QhKDa?8pwNa{h z&=5u2x%}t)^Gy+gelyQ*dc_(n49v68=5B&YkW%mu0cw_NF@{E`YWtKCcW+Vc^7-&F zBp+ydTG+1V-G__}&ATHsb9WhoYDcHbO6&PjuMy|8_P?jAheQ~=Tz^6m{_13|2nl4i zE;TzZq%Iq?IJ;zJC|xF^3iXP{>K^BzCCP`jlYKiNR(W83Xd_Qq)at&pyHOucG1f z`w|1EvdQbw`#P4mL_tXzhWPjiEfWqXgBEyF0G<9N?9Uznk!X7i2T|sSAl+LIAVC=+ zrt9hA)=-$xiC#9QtLbk}!Go4iu;`O^3y)Xd(M50}xDXhMyz0ZMAP$#E~4CgUa=zV7iS z>jeF_K{KDWSM#>pX%M@*ozAf3qXV65uW#I0!nUcHR2JMkKeV4Dmwkc|9 zD6H+o%lskkAUwya!@WCgog5NjNbLk-r zR!mX1(<+b|R)hWc;W-AE%k;`MT4%eN`bL#z|X_|vY*-i zb-qxE1!SFtLLB>j6xvGQeOX@~{Aw=8VGyLeTXnfm((eyo<17h?LRx0( z;|N)|3dVlZ2$!3qpj^`NVy~)Pk?P~|rHh4~{Ybi}-?s~A(n*Mez;&j}uS_UY$BFCU z{{Fg-9pw3BRdP7qZX8f`WV3FCJI$VPe$yC;bmq5y_Gor-GWvLsLc(i0eYUaHY)<%@ zY+t|wvbTC2rL%-LGo#MZ>r&Wl+XoJDPvNs=8yVfRtG=<;WYWSFjQ~hXN_wCnwv&(R z@?o{^%b-^WmSisjkKFDzI10lU3`lTuQ3nKCo&@Mu`rKpP^!f-B5<&&$QXPft1&P21 z+U;GNXU^iaX82lpX=jy0N6+YEV2~rmunD{=7L+l)?hijGEEK+21D%!5 znJZGF!#2}sRSb@fj;4(;Ff%63??lre_6c~j?>ilVFXv>eYDX z2?GNXzA??|!LK)Clil|UFP*%(JfyH$DbQ%Q@&wy#40|AJ%rDn;R3J$HMii37l{B0d z>)LX)y^CpA?BqH|0{cp*&?!?KAq~uD^26`%M>>5;HVM1~PLt(XPWoV@x~f~+HVk#f zxBGo=b}MM#bZM{gGmFD_eelmLNv!VMF+Dq75{XbcH^^n~0P+4o1AGRs!p5aEJE~uZ zVzRq`^{H?+VxO;@PZN}kEAI(=#F{xNM%uhfoxS}Qj6P|RPhIjM-(q;#DXDqxBtc#a zRWyy6rE4YpI=G{O&!*s7zU;7j;ZQ)ff(7ZHO8y0uZ_^@IPISa z0yhEUrHA(8>DHE}rk17+j~!h{pvQEXLakcwmB|ZzL|#lWi`TO%l6T*$hjT(Hb90)b z9?{^i;VvL*m-SrhLvnI*?@`v-c{k17FEMHkPA=ajpHTJkhK454*(escZ0p(z@q@zn zrJa_x+x4zxoq??R*revJ7W8RzliRV%1JCO-WNvkB`?}Blac;*a8xxSzK&$D(6Xv^O zDMqCCpgeU}^t_^x#p(gLd>W1R@``b;uHt5yE30xh*WajYtC7S7;UHvE#;Ga16>9&*9y}jBb=OMx|?2 z6wO!^E$Vq#mePV2Gq|@XRfoY9ttykw^Lu(#c*u4pxYf~XcNoiGcY#~y1gS2Nly_wksS zJq~jV!3Vld-FXGZSCdWL!LFS3u%Aa0v$MZljdA3w_<=$>>_GPWUmHOap-==ippH~( z0T6$zF0}Xgu25HiXxCZtgp13^!+h$~mdDU~#_HA*PR)5$-)0*tBEciktVQ2@=vVUi zkdmKWX`nYmuOEnglHY@A>W2UXHr0Xz>FVVR4`;+<*E1CMFoC(MOjqp4!1c?~?#H*a z#ARcD5z;Bil0nodV1@zi;Ni~@Ofh1wZxr6%{{pv6A^yL@qC#lExd}w#ylfDN#I>9? zet~AU8Vd<74`Kgvg)DDeM3@f}2&h!Cg@@>EedJ%@0}TQ%9Be(MlVv3M zZxF$79I2w6XX`$8R4A|<0_B6k6OrDm2@=GPlPrfkkP%^sB9CBG-2+2lL(z(d`086x zR4iM0nyk&b@>KEE?CVMZ+Vkk>peq#1KRvlxB8MX0#rlWc_mQ3`GSw1nMA?u9TDoW< z?~b8mV-YxIOODgwcQS;39dbg}bAe|!_0ub4jeR{UyMK-SKY-Z(8IIal2iceZ?uU3S z+}s1&h5-c)hCe>VZ#4M#Kn*|bj}IAbp%-vDfB+!X2F2ze47{_|5m`?fsv`;<^_#)unE#tD4sLK9T`V# zWt&b4s6=!&4Y?XEIMjc8Y5~nRH8ijAlLb-EnrZM4E!1Jl`w0Chx$Q61D9=@93^VuB z0SbE^5n(ShG~95qvq7nd_lu^HqDGMag06-(4#z~GMPo&0mSj*Sg@<|gg=AyFA^Q0j zV+gu3CR5HX>^Ie4{;1uu%(F0(A>^iJ{$IA(5n(BA(g-lnJ1Uxcc)K*c zF6umFdz6%wO^t1jsuwqx+=S7fw6#NTYa;@_Dk}rkuBf`ERL+|oo{?pcU&TUBXn)+WV7~~~Y>OF^-F0Z#&vttFI zhL#Nb_HO5yMHPzuA`#Rr zhNTrXF`aKM#;sOqU6ll_yf#yJG;q& zyOgFb61*PdFH&lA^FMy*GJV1&YmPpTiWVUai|**SK)@l6P07fR2?7_tCv(LF>8TT| z?H+r{73q$S#N1qLVc@VYhLoa2D?oyCVq^IvO9v1ksp@KOv`TC2tSj7jX&%-WJ|-aa z6YQ!LSJb^5$MF&qKu#r)D>r3}kqkn8K~t6nqkO7;-DKmbM(8(+ejnjl;mfX4Aq2^&{c6&D;?|G znXpR?)$dG?TGF}?CP=pA%#LD{P_axE$ms$ew(xrQ*K;D*>;!4b9zf{jk$HogOWHB6w^cg6+aeoY%H{g{)5#%T ztEv{?s)Cg6L=|nW(lEOVlAzE_Xhwb1bFCAi{GW@86{=v#RZp;8QS-q7u1gB8y>(2e z@)h*7Tmx!K(>FZBqCy$e>ErP*v!@hgC*Tl1lvSPb z63{c@@|9@ke=Z1HFDj{^Jz-TAu3Op}hnIHS*z;f3$_f-0t=QU)0-PD{LkOHLpyv5_h7B?msBzA1iz63%_&OIC>6mEBcs~Hxz^fQM=C15 zV6pJXw!QOnj<}%fYZ3~I{AX{W*rCLf6cmIu@pj~3v0UG;obu7R;X;o2h0>IcIc-lr z@Oa7DbsEO00ZIf_;-AFT)i5)`8gxRE?@#;=wxNf4&Nd!F`gXSh@RZ2FAaF9@dE#@K zf~!N`=j-@qU}u3HEA)0u1+RtJ$2MIzLKdfGV2i& z`uSvJyuS{Ng+F1oG&hzdkNjvF9$dIob-3HJbCqOFWYto+4|SGvMntw-Mg|}**0o3M zK3-0;=9})ue3DVI6x4~SOgrqOc`<$9>>t>aXQ8B|bmUo&iyI>&OOZGN%rqH=cC&aj zwp3&uOBMYU=viL2Sk6-eM;2l;m)9UI>aXpWbJiC}wm4blNtpew(8$UGb7K{zZ1X1= zVH#lq(wffnBCZMg_ZAHmdpewp+%ldD~Z+^ zEvLP_I6Eu8fi(7&o18j5tPaI*`WH9-;zJYJ!bd?Mw3*K$c6M3m=(>GqC|P4^u~ zvjo7?G+T>nZTuJ{=8)a{;ei!!#M#Wgba_o(u{~DGcv2}T@kvPu;D}9eEIY&Jm8(RH z3O`KgICkf{+e_}|;29GV>YY<`bMe?g7jYjA+yqCTFv@Fa@76ur6EE>3St3e zX^-V;H9pro64VLaI5EE59NeZ$cDYos>TfORBJ()(?YRM9; z9rq5PO!DMDf~f0wa5`IXoMr1ry>9*K`Em}8lD!Y6`0vDF;y21jOZL6G*~tNMT+5Tq zZ*3{MTFxF#TkamvyI6tfzp5iQbO%aer)0`4{t}hpe#actU_I?i++~zMLNo4BQk8Ou z@^*J@�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 Date: Mon, 5 Oct 2026 14:48:37 -0400 Subject: [PATCH 04/14] wip-devtools --- messages_devtools.go | 5 + testdata/snapshots/darwin/uptime_bar-dark.png | Bin 0 -> 5669 bytes .../snapshots/darwin/uptime_bar-light.png | Bin 0 -> 5438 bytes uptime_bar.go | 96 ++++++++++++++++++ uptime_bar_test.go | 87 ++++++++++++++++ 5 files changed, 188 insertions(+) create mode 100644 testdata/snapshots/darwin/uptime_bar-dark.png create mode 100644 testdata/snapshots/darwin/uptime_bar-light.png create mode 100644 uptime_bar.go create mode 100644 uptime_bar_test.go diff --git a/messages_devtools.go b/messages_devtools.go index a1373e8..bd12df1 100644 --- a/messages_devtools.go +++ b/messages_devtools.go @@ -38,6 +38,11 @@ func init() { "service_status.counts": {"%d up · %d degraded · %d down", "%d 正常 · %d 降级 · %d 中断"}, "service_status.empty": {"No services", "没有服务"}, + "uptime_bar.ok": {"Operational", "正常"}, + "uptime_bar.degraded": {"Degraded", "降级"}, + "uptime_bar.down": {"Outage", "中断"}, + "uptime_bar.nodata": {"No data", "无数据"}, + "alert_list.empty": {"No alerts", "没有告警"}, }) } diff --git a/testdata/snapshots/darwin/uptime_bar-dark.png b/testdata/snapshots/darwin/uptime_bar-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..03b3e10231577be93183a40fbfacafad8687113d GIT binary patch literal 5669 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 V@8TU?!ppw^RV6LO3b19^-vC>vBG~`{ literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/uptime_bar-light.png b/testdata/snapshots/darwin/uptime_bar-light.png new file mode 100644 index 0000000000000000000000000000000000000000..d01b9308a9416c0212d95864769b8fc210a3fc1c GIT binary patch literal 5438 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 literal 0 HcmV?d00001 diff --git a/uptime_bar.go b/uptime_bar.go new file mode 100644 index 0000000..9c1fe37 --- /dev/null +++ b/uptime_bar.go @@ -0,0 +1,96 @@ +package mujica + +import ( + "fmt" + "math" + + "github.com/egoist/mygo/ui" + + "mujicaui/internal" +) + +// UptimeDay is one day's availability, 0 to 1; NaN means no data. +type UptimeDay struct { + Date Date + Uptime float64 + // Note says what happened, as "12 min outage". + Note string +} + +// UptimeBarOptions configure an UptimeBar; Frame.Height 0 means 36. +type UptimeBarOptions struct { + Frame ChartFrame + // Degraded and Down are the uptime limits below which a day is marked; zero means 0.999 and 0.95. + Degraded, Down float64 +} + +// UptimeBar draws one bar per day by availability; hover or arrows read a day's detail. +func UptimeBar(c *ui.Context, days []UptimeDay, opts UptimeBarOptions) ChartResult { + deg, down := opts.Degraded, opts.Down + if deg == 0 { + deg = 0.999 + } + if down == 0 { + down = 0.95 + } + if !(down < deg && deg <= 1 && down > 0) { + panic("mujica: UptimeBar needs 0 < Down < Degraded <= 1") + } + for i, d := range days { + if !math.IsNaN(d.Uptime) && (d.Uptime < 0 || d.Uptime > 1) { + panic(fmt.Sprintf("mujica: UptimeBar day %d uptime %v outside 0..1", i, d.Uptime)) + } + } + if opts.Frame.Height == 0 { + opts.Frame.Height = 36 + } + k := Tokens(c) + ch := newChart(c, opts.Frame) + n := len(days) + band := func(r ui.Rect) internal.Band { return internal.Band{N: n, R0: r.X, R1: r.X + r.W, Pad: 0.25} } + level := func(v float64) int { // 0 ok, 1 degraded, 2 down, 3 no data + switch { + case math.IsNaN(v): + return 3 + case v < down: + return 2 + case v < deg: + return 1 + } + return 0 + } + words := [...]string{msg(c, "uptime_bar.ok"), msg(c, "uptime_bar.degraded"), msg(c, "uptime_bar.down"), msg(c, "uptime_bar.nodata")} + res, _ := ch.build(chartSpec{ + n: n, + draw: func(p *ui.Painter, r ui.Rect, active int) { + b := band(r) + for i, d := range days { + x, w := internal.Snap(p, b.Start(i)), max(b.Width(), 1) + lv := level(d.Uptime) + col := [...]ui.Color{k.Success, k.Warning, k.Danger, k.Border}[lv] + // Shorter bars mark bad days, so color is not the only cue. + h := r.H * [...]float32{1, 0.75, 0.5, 1}[lv] + p.Fill(ui.Rect{X: x, Y: r.Y + r.H - h, W: w, H: h}, col, 1) + if i == active { + p.Stroke(ui.Rect{X: x - 2, Y: r.Y - 2, W: w + 4, H: r.H + 4}, k.Focus, 2, 1.5) + } + } + }, + pick: func(r ui.Rect, x, _ float32) int { + b := band(r) + return max(0, min(n-1, int((x-r.X)/b.Step()))) + }, + readout: func(i int) string { + d := days[i] + s := fmt.Sprintf("%s · %s", d.Date.String(), words[level(d.Uptime)]) + if !math.IsNaN(d.Uptime) { + s += fmt.Sprintf(" · %.2f%%", d.Uptime*100) + } + if d.Note != "" { + s += " · " + d.Note + } + return s + }, + }) + return res +} diff --git a/uptime_bar_test.go b/uptime_bar_test.go new file mode 100644 index 0000000..fdbff9f --- /dev/null +++ b/uptime_bar_test.go @@ -0,0 +1,87 @@ +package mujica + +import ( + "math" + "testing" + + "github.com/egoist/mygo/ui" +) + +func uptimeSample(n int) []UptimeDay { + days := make([]UptimeDay, n) + start := Date{Year: 2026, Month: 9, Day: 1} + for i := range days { + days[i] = UptimeDay{Date: start.AddDays(i), Uptime: 1} + } + days[3] = UptimeDay{Date: start.AddDays(3), Uptime: 0.9975, Note: "slow search"} + days[7] = UptimeDay{Date: start.AddDays(7), Uptime: 0.91, Note: "2 h outage"} + days[9].Uptime = math.NaN() + return days +} + +func uptimeTester(days []UptimeDay, frame ChartFrame, points *[]int) *ui.Tester { + if frame.Label == "" { + frame.Label = "API uptime" + } + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Column(c).Padding(10).Children(func() { + r := UptimeBar(c, days, UptimeBarOptions{Frame: frame}) + *points = append(*points, r.Point) + }) + }, 520, 140) +} + +func TestUptimeBarKeyboardReadout(t *testing.T) { + var pts []int + tt := uptimeTester(uptimeSample(30), ChartFrame{}, &pts) + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyRight) + for range 7 { + tt.Key(0, ui.KeyRight) + } + if !tt.HasText("2026-09-08 · Outage · 91.00% · 2 h outage") { + t.Fatalf("readout: %q", tt.Texts()) + } + tt.Key(0, ui.KeyRight) + tt.Key(0, ui.KeyRight) + if !tt.HasText("2026-09-10 · No data") { + t.Fatalf("no data readout: %q", tt.Texts()) + } +} + +func TestUptimeBarHover(t *testing.T) { + var pts []int + tt := uptimeTester(uptimeSample(30), ChartFrame{}, &pts) + // The 4th of 30 bars across 500 DIPs starting at x = 10. + tt.Move(10+500*3.5/30, 30) + if pts[len(pts)-1] != 3 || !tt.HasText("2026-09-04 · Degraded · 99.75% · slow search") { + t.Fatalf("hover point %d: %q", pts[len(pts)-1], tt.Texts()) + } +} + +func TestUptimeBarStatesAndRules(t *testing.T) { + var pts []int + tt := uptimeTester(nil, ChartFrame{}, &pts) + if !tt.HasText("No data") { + t.Fatalf("empty: %q", tt.Texts()) + } + tt = uptimeTester(uptimeSample(12), ChartFrame{Status: DataLoading}, &pts) + if !tt.HasText("Loading") { + t.Fatalf("loading: %q", tt.Texts()) + } + defer func() { + if recover() == nil { + t.Fatal("uptime above 1 did not panic") + } + }() + uptimeTester([]UptimeDay{{Uptime: 1.2}}, ChartFrame{}, &pts) +} + +func TestUptimeBarSnapshot(t *testing.T) { + snapshot(t, "uptime_bar", 520, 110, func(c *ui.Context) { + ui.Column(c).Padding(10).Children(func() { + UptimeBar(c, uptimeSample(45), UptimeBarOptions{Frame: ChartFrame{Label: "API uptime"}}) + }) + }) +} From b92744f6edb46f63cdf8f355e35301395c502ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 5 Oct 2026 14:49:26 -0400 Subject: [PATCH 05/14] wip-devtools --- messages_devtools.go | 4 + resource_gauge.go | 69 ++++++++++++++++++ resource_gauge_test.go | 56 ++++++++++++++ .../snapshots/darwin/resource_gauge-dark.png | Bin 0 -> 27529 bytes .../snapshots/darwin/resource_gauge-light.png | Bin 0 -> 26776 bytes 5 files changed, 129 insertions(+) create mode 100644 resource_gauge.go create mode 100644 resource_gauge_test.go create mode 100644 testdata/snapshots/darwin/resource_gauge-dark.png create mode 100644 testdata/snapshots/darwin/resource_gauge-light.png diff --git a/messages_devtools.go b/messages_devtools.go index bd12df1..ca629ba 100644 --- a/messages_devtools.go +++ b/messages_devtools.go @@ -43,6 +43,10 @@ func init() { "uptime_bar.down": {"Outage", "中断"}, "uptime_bar.nodata": {"No data", "无数据"}, + "resource_gauge.normal": {"Normal", "正常"}, + "resource_gauge.high": {"High", "偏高"}, + "resource_gauge.critical": {"Critical", "危险"}, + "alert_list.empty": {"No alerts", "没有告警"}, }) } diff --git a/resource_gauge.go b/resource_gauge.go new file mode 100644 index 0000000..14a6f54 --- /dev/null +++ b/resource_gauge.go @@ -0,0 +1,69 @@ +package mujica + +import ( + "fmt" + "math" + + "github.com/egoist/mygo/ui" +) + +// ResourceReading is one resource's current use. +type ResourceReading struct { + // Name is required, as "CPU". + Name string + Used, Total float64 + // Unit follows the amounts, as "GB"; empty shows percent only. + Unit string +} + +// ResourceGaugeOptions configure a ResourceGauge; thresholds are fractions of Total. +type ResourceGaugeOptions struct { + // Warn and Critical default to 0.7 and 0.9. + Warn, Critical float64 + // Height of each dial; zero means 150. + Height float32 +} + +// ResourceGauge shows CPU, memory and disk dials with named normal, high and critical bands. +func ResourceGauge(c *ui.Context, readings []ResourceReading, opts ResourceGaugeOptions) *ui.Element { + warn, crit := opts.Warn, opts.Critical + if warn == 0 { + warn = 0.7 + } + if crit == 0 { + crit = 0.9 + } + if !(0 < warn && warn < crit && crit < 1) || opts.Height < 0 { + panic("mujica: ResourceGauge needs 0 < Warn < Critical < 1 and Height >= 0") + } + h := opts.Height + if h == 0 { + h = 150 + } + u := Density(c).Unit() + row := ui.Row(c).Wrap().Gap(u * 4).Shrink(0) + row.Children(func() { + for i, r := range readings { + if r.Name == "" || !(r.Total > 0) || r.Used < 0 || r.Used > r.Total || math.IsNaN(r.Used) { + panic(fmt.Sprintf("mujica: ResourceGauge reading %d needs a Name and 0 <= Used <= Total", i)) + } + frac := r.Used / r.Total + ui.Column(c).Key(r.Name).Width(200).Gap(u).AlignItems(ui.Center).Children(func() { + ui.Text(c, r.Name).FontWeight(600) + Gauge(c, frac*100, GaugeOptions{ + Frame: ChartFrame{Label: r.Name, Height: h}, Min: 0, Max: 100, Unit: "%", + Format: func(v float64) string { return fmt.Sprintf("%.0f", v) }, + Segments: []GaugeSegment{ + {To: warn * 100, Label: msg(c, "resource_gauge.normal"), Tone: GaugeGood}, + {To: crit * 100, Label: msg(c, "resource_gauge.high"), Tone: GaugeWarning}, + {To: 100, Label: msg(c, "resource_gauge.critical"), Tone: GaugeDanger}, + }, + }) + if r.Unit != "" { + agentCaption(c, fmt.Sprintf("%s / %s %s", sandboxNum(r.Used), sandboxNum(r.Total), r.Unit)) + } + }) + } + }) + return row +} diff --git a/resource_gauge_test.go b/resource_gauge_test.go new file mode 100644 index 0000000..bc03728 --- /dev/null +++ b/resource_gauge_test.go @@ -0,0 +1,56 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +var resourceSample = []ResourceReading{ + {Name: "CPU", Used: 45, Total: 100}, + {Name: "Memory", Used: 6.4, Total: 8, Unit: "GB"}, + {Name: "Disk", Used: 470, Total: 500, Unit: "GB"}, +} + +func TestResourceGaugeThresholdsAndKeyboard(t *testing.T) { + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ResourceGauge(c, resourceSample, ResourceGaugeOptions{}) + }, 700, 300) + if !tt.HasText("6.4 / 8 GB") || !tt.HasText("470 / 500 GB") { + t.Fatalf("texts %q", tt.Texts()) + } + tt.Key(0, ui.KeyTab) // CPU dial + tt.Key(0, ui.KeyRight) + if !tt.HasText("45 % · Normal") { + t.Fatalf("CPU readout: %q", tt.Texts()) + } + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyRight) + if !tt.HasText("80 % · High") { + t.Fatalf("memory readout: %q", tt.Texts()) + } + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyRight) + if !tt.HasText("94 % · Critical") { + t.Fatalf("disk readout: %q", tt.Texts()) + } +} + +func TestResourceGaugeRejectsBadReading(t *testing.T) { + defer func() { + if recover() == nil { + t.Fatal("no panic") + } + }() + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ResourceGauge(c, []ResourceReading{{Name: "CPU", Used: 5, Total: 4}}, ResourceGaugeOptions{}) + }, 200, 200) +} + +func TestResourceGaugeSnapshot(t *testing.T) { + snapshot(t, "resource_gauge", 680, 250, func(c *ui.Context) { + ui.Column(c).Padding(16).Children(func() { ResourceGauge(c, resourceSample, ResourceGaugeOptions{}) }) + }) +} diff --git a/testdata/snapshots/darwin/resource_gauge-dark.png b/testdata/snapshots/darwin/resource_gauge-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..1996758b09944d6447680e3e4fd540e09eb1ac21 GIT binary patch literal 27529 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 UDjIJ3dt@aitt?d~Ve;jF0r7xbO8@`> literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/resource_gauge-light.png b/testdata/snapshots/darwin/resource_gauge-light.png new file mode 100644 index 0000000000000000000000000000000000000000..0bad7e24cd51cb07e4e5e83d63de772375627660 GIT binary patch literal 26776 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 b!EF#CmcnJ)e?+hUouXEhQ Date: Mon, 5 Oct 2026 14:50:28 -0400 Subject: [PATCH 06/14] wip-devtools --- system_monitor.go | 76 ++++++++++++++++ system_monitor_test.go | 82 ++++++++++++++++++ .../snapshots/darwin/system_monitor-dark.png | Bin 0 -> 29032 bytes .../snapshots/darwin/system_monitor-light.png | Bin 0 -> 28012 bytes 4 files changed, 158 insertions(+) create mode 100644 system_monitor.go create mode 100644 system_monitor_test.go create mode 100644 testdata/snapshots/darwin/system_monitor-dark.png create mode 100644 testdata/snapshots/darwin/system_monitor-light.png diff --git a/system_monitor.go b/system_monitor.go new file mode 100644 index 0000000..52f2ff2 --- /dev/null +++ b/system_monitor.go @@ -0,0 +1,76 @@ +package mujica + +import ( + "fmt" + "math" + "time" + + "github.com/egoist/mygo/ui" + + "mujicaui/theme" +) + +// MonitorMetric is one sampled resource; Samples line up with the shared times. +type MonitorMetric struct { + Name string + Unit string + Samples []float64 +} + +// SystemMonitorOptions configure a SystemMonitor. +type SystemMonitorOptions struct { + // Height of each chart; zero means 110. + Height float32 + Status DataStatus + Error string +} + +// SystemMonitorResult reports this frame's events. +type SystemMonitorResult struct { + Element *ui.Element + Retry bool +} + +// SystemMonitor stacks a live line chart per metric over one time axis, sampled by the caller. +func SystemMonitor(c *ui.Context, times []time.Time, metrics []MonitorMetric, opts SystemMonitorOptions) SystemMonitorResult { + if opts.Height < 0 { + panic("mujica: SystemMonitor Height is negative") + } + for i, m := range metrics { + if m.Name == "" || len(m.Samples) != len(times) { + panic(fmt.Sprintf("mujica: SystemMonitor metric %d needs a Name and one sample per time", i)) + } + } + h := opts.Height + if h == 0 { + h = 110 + } + k := Tokens(c) + u := Density(c).Unit() + var r SystemMonitorResult + r.Element = ui.Column(c).Shrink(0).Gap(u * 3) + r.Element.Children(func() { + for _, m := range metrics { + ui.Column(c).Key(m.Name).Gap(u).Children(func() { + ui.Row(c).Gap(u * 2).AlignItems(ui.Center).Children(func() { + ui.Text(c, m.Name).FontWeight(600).Grow(1) + last := math.NaN() + if n := len(m.Samples); n > 0 { + last = m.Samples[n-1] + } + if !math.IsNaN(last) && opts.Status == DataReady { + ui.Text(c, fmt.Sprintf("%s %s", sandboxNum(last), m.Unit)).FontFeatures("tnum"). + FontSize(FontSize(c, theme.CaptionSize+1)).TextColor(k.Text).Role(ui.RoleStatus) + } + }) + unit := m.Unit + res := LineChart(c, []ChartSeries{{Name: m.Name, Values: m.Samples}}, LineChartOptions{ + Frame: ChartFrame{Label: m.Name, Height: h, Status: opts.Status, Error: opts.Error}, + Times: times, Format: func(v float64) string { return sandboxNum(v) + " " + unit }, + }) + r.Retry = r.Retry || res.Retried() + }) + } + }) + return r +} diff --git a/system_monitor_test.go b/system_monitor_test.go new file mode 100644 index 0000000..cbc7edf --- /dev/null +++ b/system_monitor_test.go @@ -0,0 +1,82 @@ +package mujica + +import ( + "math" + "strings" + "testing" + "time" + + "github.com/egoist/mygo/ui" +) + +func monitorSample(n int) ([]time.Time, []MonitorMetric) { + t0 := time.Date(2026, 10, 5, 21, 0, 0, 0, time.UTC) + times := make([]time.Time, n) + cpu, mem := make([]float64, n), make([]float64, n) + for i := range n { + times[i] = t0.Add(time.Duration(i) * 5 * time.Second) + cpu[i] = 40 + 20*math.Sin(float64(i)/4) + mem[i] = 3.2 + float64(i)*0.02 + } + return times, []MonitorMetric{{Name: "CPU", Unit: "%", Samples: cpu}, {Name: "Memory", Unit: "GB", Samples: mem}} +} + +func TestSystemMonitorLiveAndKeyboard(t *testing.T) { + times, metrics := monitorSample(20) + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + SystemMonitor(c, times, metrics, SystemMonitorOptions{}) + }, 600, 420) + if !tt.HasText("3.6 GB") || !tt.HasText("20.0 %") { + t.Fatalf("latest samples missing: %q", tt.Texts()) + } + times, metrics = monitorSample(21) + tt.Frame() + if !tt.HasText("20.8 %") || tt.HasText("20.0 %") { + t.Fatalf("new sample: %q", tt.Texts()) + } + tt.Key(0, ui.KeyTab) // the legend key + tt.Key(0, ui.KeyTab) // the plot + tt.Key(0, ui.KeyEnd) + found := false + for _, s := range tt.Texts() { + found = found || strings.Contains(s, "21:01:40") + } + if !found { + t.Fatalf("keyboard readout: %q", tt.Texts()) + } +} + +func TestSystemMonitorStatesAndRules(t *testing.T) { + times, metrics := monitorSample(5) + retries := 0 + tt := ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if SystemMonitor(c, times, metrics[:1], SystemMonitorOptions{Status: DataFailed, Error: "agent offline"}).Retry { + retries++ + } + }, 600, 300) + if !tt.HasText("agent offline") { + t.Fatalf("error: %q", tt.Texts()) + } + _ = tt.Click("Retry") + if retries != 1 { + t.Fatal("retry not reported") + } + defer func() { + if recover() == nil { + t.Fatal("mismatched samples did not panic") + } + }() + ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + SystemMonitor(c, times, []MonitorMetric{{Name: "x", Samples: []float64{1}}}, SystemMonitorOptions{}) + }, 200, 200) +} + +func TestSystemMonitorSnapshot(t *testing.T) { + times, metrics := monitorSample(40) + snapshot(t, "system_monitor", 560, 380, func(c *ui.Context) { + ui.Column(c).Padding(16).Children(func() { SystemMonitor(c, times, metrics, SystemMonitorOptions{}) }) + }) +} diff --git a/testdata/snapshots/darwin/system_monitor-dark.png b/testdata/snapshots/darwin/system_monitor-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..8123bcf19c7bb766870546afa342bd290a8b3034 GIT binary patch literal 29032 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/system_monitor-light.png b/testdata/snapshots/darwin/system_monitor-light.png new file mode 100644 index 0000000000000000000000000000000000000000..f8eabe15fa0c50c28698de5ba248b0195a696209 GIT binary patch literal 28012 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 Date: Mon, 5 Oct 2026 14:51:32 -0400 Subject: [PATCH 07/14] wip-devtools --- messages_devtools.go | 7 ++ refresh_interval_selector.go | 90 ++++++++++++++++++ refresh_interval_selector_test.go | 86 +++++++++++++++++ .../darwin/refresh_interval_selector-dark.png | Bin 0 -> 3892 bytes .../refresh_interval_selector-light.png | Bin 0 -> 3779 bytes 5 files changed, 183 insertions(+) create mode 100644 refresh_interval_selector.go create mode 100644 refresh_interval_selector_test.go create mode 100644 testdata/snapshots/darwin/refresh_interval_selector-dark.png create mode 100644 testdata/snapshots/darwin/refresh_interval_selector-light.png diff --git a/messages_devtools.go b/messages_devtools.go index ca629ba..9e2a822 100644 --- a/messages_devtools.go +++ b/messages_devtools.go @@ -47,6 +47,13 @@ func init() { "resource_gauge.high": {"High", "偏高"}, "resource_gauge.critical": {"Critical", "危险"}, + "refresh_interval_selector.off": {"Auto refresh off", "不自动刷新"}, + "refresh_interval_selector.refresh": {"Refresh", "刷新"}, + "refresh_interval_selector.refreshing": {"Refreshing", "正在刷新"}, + "refresh_interval_selector.seconds": {"Every %d s", "每 %d 秒"}, + "refresh_interval_selector.minutes": {"Every %d min", "每 %d 分钟"}, + "refresh_interval_selector.label": {"Refresh interval", "刷新间隔"}, + "alert_list.empty": {"No alerts", "没有告警"}, }) } diff --git a/refresh_interval_selector.go b/refresh_interval_selector.go new file mode 100644 index 0000000..f9a39e3 --- /dev/null +++ b/refresh_interval_selector.go @@ -0,0 +1,90 @@ +package mujica + +import ( + "fmt" + "log/slog" + "time" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" +) + +// RefreshIntervalSelectorOptions configure a RefreshIntervalSelector. +type RefreshIntervalSelectorOptions struct { + // Intervals to offer besides Off; nil uses 5 s, 10 s, 30 s, 1 m and 5 m. + Intervals []time.Duration + // Refreshing shows the refresh in progress and holds the timer. + Refreshing bool + Disabled bool +} + +// RefreshResult reports this frame's events. +type RefreshResult struct { + Element *ui.Element + // Refresh asks the caller to reload, by hand or because the interval elapsed. + Refresh bool + // Changed reports a new interval. + Changed bool +} + +type refreshState struct{ last time.Time } + +var refreshDefaults = []time.Duration{5 * time.Second, 10 * time.Second, 30 * time.Second, time.Minute, 5 * time.Minute} + +// RefreshIntervalSelector picks an auto-refresh interval, zero for off, and refreshes by hand. +func RefreshIntervalSelector(c *ui.Context, interval *time.Duration, opts RefreshIntervalSelectorOptions) RefreshResult { + if interval == nil { + panic("mujica: RefreshIntervalSelector needs an interval") + } + choices := opts.Intervals + if choices == nil { + choices = refreshDefaults + } + options := []SelectOption[time.Duration]{{Value: 0, Label: msg(c, "refresh_interval_selector.off")}} + known := *interval == 0 + for _, d := range choices { + if d < time.Second { + panic("mujica: RefreshIntervalSelector intervals must be at least a second") + } + known = known || d == *interval + label := fmt.Sprintf(msg(c, "refresh_interval_selector.seconds"), int(d/time.Second)) + if d%time.Minute == 0 { + label = fmt.Sprintf(msg(c, "refresh_interval_selector.minutes"), int(d/time.Minute)) + } + options = append(options, SelectOption[time.Duration]{Value: d, Label: label}) + } + if !known { + panic("mujica: RefreshIntervalSelector interval is not offered") + } + u := Density(c).Unit() + var r RefreshResult + r.Element = ui.Row(c).Shrink(0).Gap(u * 2).AlignItems(ui.Center).Disabled(opts.Disabled) + st := ui.Local(r.Element, "mujica.refresh", func() refreshState { return refreshState{last: c.Now()} }) + locked := r.Element.IsDisabled() + r.Element.Children(func() { + label := msg(c, "refresh_interval_selector.refresh") + if opts.Refreshing { + Spinner(c, SpinnerOptions{Size: 14, Label: msg(c, "refresh_interval_selector.refreshing")}) + } + if Button(c, label, ButtonOptions{Variant: Secondary, Icon: icons.Must("refresh-cw"), Disabled: locked || opts.Refreshing}).Clicked() && !locked { + r.Refresh = true + } + r.Changed = Select(c, interval, options, SelectOptions{Label: msg(c, "refresh_interval_selector.label"), Disabled: locked}).Changed() + }) + if r.Changed || opts.Refreshing { + st.last = c.Now() + } + if *interval > 0 && !locked && !opts.Refreshing && !r.Refresh { + if wait := *interval - c.Now().Sub(st.last); wait > 0 { + c.After(wait) + } else { + r.Refresh = true + } + } + if r.Refresh { + st.last = c.Now() + slog.Debug("mujica: refresh_interval_selector refresh", "interval", *interval) + } + return r +} diff --git a/refresh_interval_selector_test.go b/refresh_interval_selector_test.go new file mode 100644 index 0000000..c456eb6 --- /dev/null +++ b/refresh_interval_selector_test.go @@ -0,0 +1,86 @@ +package mujica + +import ( + "testing" + "time" + + "github.com/egoist/mygo/ui" +) + +func refreshTester(interval *time.Duration, opts *RefreshIntervalSelectorOptions, refreshes, changes *int) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + r := RefreshIntervalSelector(c, interval, *opts) + if r.Refresh { + *refreshes++ + } + if r.Changed { + *changes++ + } + }, 500, 300) +} + +func TestRefreshIntervalSelectorManualAndAuto(t *testing.T) { + interval := time.Duration(0) + opts := RefreshIntervalSelectorOptions{Intervals: []time.Duration{time.Second, time.Minute}} + refreshes, changes := 0, 0 + tt := refreshTester(&interval, &opts, &refreshes, &changes) + _ = tt.Click("Refresh") + if refreshes != 1 { + t.Fatalf("manual refreshes %d", refreshes) + } + time.Sleep(1100 * time.Millisecond) + tt.Frame() + if refreshes != 1 { + t.Fatal("refreshed while off") + } + _ = tt.Click("Refresh interval") + _ = tt.Click("Every 1 s") + if interval != time.Second || changes != 1 { + t.Fatalf("interval %v changes %d", interval, changes) + } + start := time.Now() + if !overlayUntil(tt, func() bool { return refreshes == 2 }) { + t.Fatal("no automatic refresh") + } + if time.Since(start) < 900*time.Millisecond { + t.Fatal("refreshed before the interval") + } +} + +func TestRefreshIntervalSelectorRefreshingAndDisabled(t *testing.T) { + interval := time.Second + opts := RefreshIntervalSelectorOptions{Intervals: []time.Duration{time.Second}, Refreshing: true} + refreshes, changes := 0, 0 + tt := refreshTester(&interval, &opts, &refreshes, &changes) + _ = tt.Click("Refresh") + time.Sleep(1100 * time.Millisecond) + tt.Frame() + if refreshes != 0 || !tt.HasText("Refresh") { + t.Fatalf("refreshing: %d", refreshes) + } + opts = RefreshIntervalSelectorOptions{Intervals: []time.Duration{time.Second}, Disabled: true} + tt.Frame() + time.Sleep(1100 * time.Millisecond) + tt.Frame() + _ = tt.Click("Refresh") + if refreshes != 0 { + t.Fatalf("disabled refreshed %d", refreshes) + } + defer func() { + if recover() == nil { + t.Fatal("unoffered interval did not panic") + } + }() + bad := 7 * time.Second + refreshTester(&bad, &RefreshIntervalSelectorOptions{}, &refreshes, &changes) +} + +func TestRefreshIntervalSelectorSnapshot(t *testing.T) { + snapshot(t, "refresh_interval_selector", 420, 80, func(c *ui.Context) { + interval := 30 * time.Second + ui.Column(c).Padding(16).Children(func() { + RefreshIntervalSelector(c, &interval, RefreshIntervalSelectorOptions{}) + }) + }) +} diff --git a/testdata/snapshots/darwin/refresh_interval_selector-dark.png b/testdata/snapshots/darwin/refresh_interval_selector-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..8d76da93afb44e48989444c0d6ea516786c4fcc1 GIT binary patch literal 3892 zcmc(ic{mi_8poAlBC^b&?8=_8CbDG=>QnED2He=t06k}f_zcE84`*u0#ep<(`GprdIXgeT)eRWsbqdftU%J}F(U7>#}MYEb>_OIOL7SU9(B zo+ELo>Pbn_Xssu`ILLCjoNafnvE6jn){3=RUoS56j8>VYuB~`Ms zgmM1ylaI?yJ*$73*7xm-D8l!wt|ie+qDwyuK<9{LS8RRFR zTS|(DuXZ1mmi7VOq^H*$L}fgC<`|WFNt<5~*x95V#`4b+U_{Bq<|S)Wb_38PTU%Rx zjbIFm+{xIN$2m@>TWEqkdI`%#^N&JJSH z9y17Ji~FOsX?pslIPv%XyHVWKtFWt)JZG2WAeyJ5%Y%o~@c{n$?TZ!`(r3OBq|Khi zJGJl4)(cq!LoC}j(F)4e^9;oC+vM-)jiH>-sJCUVJ~gfCe1n5h2E8R=>aUOyu?SaV zi&y1);_XA5dN|>ih_1|G3CS`e&dM8=bcQcP(BY#!FJM+$o_!JUpEW4G;9!uJ>(S1; z^T&G-2t!0ngWbAUN@xA``PdMu;B&-2!E~d&5KqX9Yg1UihTyZ6QYinoA(8BxD?ZIQ zHT^%|Gz3ziBJS?SCk~ATgg4V7+ijEz$;}jLk=VF6-d)Cq5Gnjd5=%wEBoqi|#~A_X z;gz=IyP-uh`hwe-_Pmg?&T%Rec@Bo5HO>#~U3{D9c&*vz5*IqZuah!(2N}RFz^M6O}HY& zp}8!fu#gU`(-)3!K6@SE=GKTDHWF#19Mng)GN%v>p)K`iKQ>dyQsIdOF3zmFZ_@Nh z1>2%E{L3Lhhk$picYIvyRgJtdze!JDf6kJvK|r3gg+n2*TiGf_M~kaFiRF!$rVo4E z8`ulmy4zMNm87MmlYGx7ptJtoUPh*b_;}b|z=Kw{rmv6R*KFO7N-bQ5jrECweAR|Ol@D>mk?79_ zi{+#n9baoj68T-4eZ=E*PD8pCawaB0APeJH>VZdY!bY$$)NbKG%mgt0pi!Vrm;>SR zywc=`7@{x{DI&Q!G`;uMK3LT@hUoy%@4tGKH*eZQ#F5BLtG9)~xmWI2wf|~}`0=y% zNx@@K_-Uk+l+{&g`#2s#ata$2w(h;(OT~%Zk3#5w+D@i@+JUOO3U<4j091^&5Ch093gSk)Fz$^d-k+Mq{ z&bx@(zTp%M4$jV-7jtuyfd8&wU@+VmPlse?%V_Qn;;vBze85Y5MsaQ#a=~y@`{3sHw(?#Ps>^ zjlLYX2-^oI5n5IQyRhC~5v2F`aRUY1xx8eXROD_oLDF*M{dKUsJvy8@)w2IVA~bUYpgt(?xC8OwCvQ?rh{mv`>6c(>|sS z7jEB1d!0?gBazY5j+^Jh2D?T&fPy>oy0Y;1(;|SmSEV&%!lk@2~hn3 zHDq+@2+CYpvGS)(uBqc9@+K#nQp-9%-oj!hsLE=(BTsE3^B+e~^)LiZnd^>g zo)s)w171#~k(&7B;ET)wV?Y!HHss5jb5-62mXcuX<+-RHva=H*eq{i9l}LPdjT=Yy ztk~LObh;m>WAO$qHs2{!`sg|d0|+>vdxQhI2d;gui+E@2>uU(};?vUCeqdy0hYwZ> zm_66FUaLC&d133$OFvvTsqIx`mH&2|M>!`w{<6<3+BK_r)}svA|ZzTC=%5(L|(iPA;u#S&?(-G%-wvAv`Fzm6$uChsOBgJL4u+1)i7QH8doGLW?xsj)y~(MhvDq`FG2no`G4~-HnGSD=D5nc1!-p9-PZPT>&)LYe~mb)9bjsbq&nh zT0OWJFgW{r*|>L8?L#;Uh}9`A4>3>*85Yp>cP>j&q*ZG+u5=k!{@C{V^?`@x_@$i4 znkzvG2Vt56 zXF5L@Y%2R7@DBG<)x!ING&ZZ(`|KD8Rds$x6|QQ~pdxJ*w+@LodaHOuZy!m!19sq2 z%&jyx8>gw}-S|73M4|(pHxR&5k}EOUA+LX7Ow7OOqkYl?>EonH#W1D# t3xnsfJExXt{?7>Zzb8Ry1i1hJ0FVAX zZ8HGiL;-7W0zAozv$n5h0RY}4eeJszPjYFw;r1v`W=}4`MQ3p7pj5};5pNY3@{g`M`Sbz;NL1o|sY&s5HyqDLQy3!-GDWbk&wixeIt94~Tj@|-}Djyp+Q&Uq(DJYuvq){1+7x8YL3i~m9(>7~ii zNANhDx(4Rwvc3THp6TQC`u&sq{O(8-TU!Ze@7&x;>r?;e-?+%xxVpOf?hZUKu#7+; z6un}HURycpF6n(){j z%UdAXnDF&^;t>3TcIwri7Sfr?YkVbmJU)M^J|?E=d+Y4Nh0x47UlGM+@sW=A)lnbTworzYxAzOn56mH#AQAPWtFp2#-ARh;>uZOC zOO_Vu%<8?qov-q{_feJpf8H}QQ&5F#$$}s6$o~rD>voGZ|45%Wu%j(NBs5r>alj*p zQi@19!ICjfi#9&-prfM$2ID2|5M@1z7Y!#Bbr8?{`uf&4hP8o1(>aA52-39XlkX-D z4*0sdWvzJLwdG~0YrS$0HmDmLxoPcOoVplH zczKgEGG08^5?7D3$9+(nKuoa9kPTc74Gq6` zZ*!A=@6TCMgqV(&A!RMmj!X5siTXPbK?Y=8Zq_>N8~Fl=`5Z^PeYDftc4uU2>Vdhr zFxc&(w=jPQ)pOq*Y2xYW34=M(j3wclLr|cOwgQS9v{0Ak?biZO*6~l-NKlRub!}A} zJNi+W9jije(u}Oe*OA876&<{{pDKu@yj2f3Q!=z(8`?DR^vo6z$Os*?9(30+sWD(n2kgzRonBl>7tmtHyV99G)hA zd+I|jDJ?xGj6ilbB)9hJVFhkM(C0_ca>=!|wHdph5V+Sairb&_V`0;gr_tV6p&_VI zHZ1#=9<%*@6<93A{a?vnJH;<|Y^zc~s{?A2`WLg}fcetyI ziho~AB0bUR*0-`+ZT8=*Sz1aj==y^<9v#*A1?u%CF|iVf{FQwxF*7ss@y@z}!nl2j z(}2VB#Kd!S#m6LCElb{ix1F zx4vQq*Eci)os~yIU{Y#=*3#$a=P9bVuUq8}?6kd2DwR+hSW<=)>zf+?EG8i#Ei)wC zB#PultuAde>l6lSzGSuD!om<7Qrr5vN0zvkSJ~(83d`%0lNRP?vJ#TTtTYZC@Z_P- zxGbEtd)fH%ih-Dz*t4^L&dtu|YM__vR|ar6pDPp@e-!s=z2`m=&djK9{N$0|CheH> zlyz`m`(%Ir(d*`}b}WJA6^e$-$%6$8}Fz-8?+r zm#Hc${1D9w zZEe}1y&<#x{C?gd8q$%+M-^-QQBqRIpJX&Iek8Uwgl_51I4$}!>Ku_0u-tdW#h1^u zPzDE)>Zw0qX_eL0Dj^Ex<>lo5G6uoa$jIWZix^tqEe5eNTb)$Ul@a8D2FWhHbrjNa z9Cn{g@09N;cWibv2~fV_Ls9QksqnuTb!2)P<-@aJZv=&^?>~!SrihLVY3AqWZwPt; zv8>r(Zhl@^=+a%M{Zv6ku9+?K1<-McMOpD$5$dvrxp`DAk@(wxuVr*JK^!)qB9xLg z) zii)fZ49dI_m?LkmgD64N+?7+UXfd|t1;i1@&04lg*?Or-0=2OCR?V6KtQ7sHm7boS zyLec5iouqK%#)q`?WqseUwZ~zIIi%RS?JX^V@KM0x8Gl$4RN|}ZEEUBgb8G^lgMuz z24+5?(Ct}|5+=^a8Tq3IkAE}uVFpu^<2D3mP5RC`3@j@vFTqgbPZ^n5xx-1|3u&W= zE`kFdf@kPyIRwJ@V!r`<2aML2J)!@H3wGhq?d{Eo#3$x;#mI1ZgO!NvLV2 mmGFO^hyE2T)8(QaD+BiS%<}J@ReQsNQ-Hn>OuHQF9P>Y$h(y}} literal 0 HcmV?d00001 From 3c8d15b97de1adff5948b8db683087db59b24f8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 5 Oct 2026 14:53:36 -0400 Subject: [PATCH 08/14] wip-devtools --- dashboard_filter_bar.go | 95 ++++++++++++++++++ dashboard_filter_bar_test.go | 73 ++++++++++++++ messages_devtools.go | 12 +++ refresh_interval_selector.go | 4 +- .../darwin/dashboard_filter_bar-dark.png | Bin 0 -> 6339 bytes .../darwin/dashboard_filter_bar-light.png | Bin 0 -> 6241 bytes 6 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 dashboard_filter_bar.go create mode 100644 dashboard_filter_bar_test.go create mode 100644 testdata/snapshots/darwin/dashboard_filter_bar-dark.png create mode 100644 testdata/snapshots/darwin/dashboard_filter_bar-light.png diff --git a/dashboard_filter_bar.go b/dashboard_filter_bar.go new file mode 100644 index 0000000..efd0686 --- /dev/null +++ b/dashboard_filter_bar.go @@ -0,0 +1,95 @@ +package mujica + +import ( + "fmt" + "log/slog" + "slices" + "time" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" +) + +// DashboardFilters is what a dashboard is filtered by; the zero value filters nothing. +type DashboardFilters struct { + // Range is how far back to look; zero means the caller's default. + Range time.Duration + Env string + Tags []string +} + +// DashboardFilterBarOptions configure a DashboardFilterBar. +type DashboardFilterBarOptions struct { + // Ranges are the offered look-backs; nil uses 15 min, 1 h, 24 h and 7 d. + Ranges []time.Duration + // Envs and Tags are the offered environments and tags. + Envs, Tags []string + Disabled bool +} + +var dashboardRanges = []time.Duration{15 * time.Minute, time.Hour, 24 * time.Hour, 7 * 24 * time.Hour} + +// DashboardFilterBar filters by time range, environment and tags, with a clear button. +func DashboardFilterBar(c *ui.Context, f *DashboardFilters, opts DashboardFilterBarOptions) ChoiceResult { + if f == nil { + panic("mujica: DashboardFilterBar needs filters") + } + ranges := opts.Ranges + if ranges == nil { + ranges = dashboardRanges + } + if f.Env != "" && !slices.Contains(opts.Envs, f.Env) { + panic("mujica: DashboardFilterBar Env is not offered") + } + u := Density(c).Unit() + var r ChoiceResult + r.Element = ui.Row(c).Shrink(0).Wrap().Gap(u * 3).AlignItems(ui.Center).Disabled(opts.Disabled). + Label(msg(c, "dashboard_filter_bar.label")) + locked := r.Element.IsDisabled() + r.Element.Children(func() { + rs := []SelectOption[time.Duration]{{Value: 0, Label: msg(c, "dashboard_filter_bar.default_range")}} + for _, d := range ranges { + rs = append(rs, SelectOption[time.Duration]{Value: d, Label: fmt.Sprintf(msg(c, "dashboard_filter_bar.last"), dashboardRangeLabel(c, d))}) + } + if f.Range != 0 && !slices.Contains(ranges, f.Range) { + panic("mujica: DashboardFilterBar Range is not offered") + } + r.changed = Select(c, &f.Range, rs, SelectOptions{Label: msg(c, "dashboard_filter_bar.range"), Disabled: locked}).Changed() + envs := []SelectOption[string]{{Value: "", Label: msg(c, "dashboard_filter_bar.all_envs")}} + for _, e := range opts.Envs { + envs = append(envs, SelectOption[string]{Value: e, Label: e}) + } + r.changed = Select(c, &f.Env, envs, SelectOptions{Label: msg(c, "dashboard_filter_bar.env"), Disabled: locked}).Changed() || r.changed + if len(opts.Tags) > 0 { + tags := make([]SelectOption[string], len(opts.Tags)) + for i, t := range opts.Tags { + tags[i] = SelectOption[string]{Value: t, Label: t} + } + r.changed = MultiSelect(c, &f.Tags, tags, MultiSelectOptions{Placeholder: msg(c, "dashboard_filter_bar.tags"), + Label: msg(c, "dashboard_filter_bar.tags"), Disabled: locked}).Changed() || r.changed + } + empty := f.Range == 0 && f.Env == "" && len(f.Tags) == 0 + if Button(c, msg(c, "dashboard_filter_bar.clear"), ButtonOptions{Variant: Ghost, Icon: icons.Must("x"), Disabled: locked || empty}).Clicked() && !locked { + *f, r.changed = DashboardFilters{}, true + } + }) + if r.changed { + slog.Debug("mujica: dashboard_filter_bar change", "range", f.Range, "env", f.Env, "tags", len(f.Tags)) + } + return r +} + +// dashboardRangeLabel names a look-back, as "15 min" or "7 d". +func dashboardRangeLabel(c *ui.Context, d time.Duration) string { + switch { + case d <= 0: + case d%(24*time.Hour) == 0: + return fmt.Sprintf(msg(c, "dashboard_filter_bar.days"), int(d/(24*time.Hour))) + case d%time.Hour == 0: + return fmt.Sprintf(msg(c, "dashboard_filter_bar.hours"), int(d/time.Hour)) + case d%time.Minute == 0: + return fmt.Sprintf(msg(c, "dashboard_filter_bar.minutes"), int(d/time.Minute)) + } + panic("mujica: DashboardFilterBar ranges must be positive whole minutes") +} diff --git a/dashboard_filter_bar_test.go b/dashboard_filter_bar_test.go new file mode 100644 index 0000000..db17674 --- /dev/null +++ b/dashboard_filter_bar_test.go @@ -0,0 +1,73 @@ +package mujica + +import ( + "testing" + "time" + + "github.com/egoist/mygo/ui" +) + +func filterTester(f *DashboardFilters, opts DashboardFilterBarOptions, changes *int) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Column(c).Padding(8).Children(func() { + if DashboardFilterBar(c, f, opts).Changed() { + *changes++ + } + }) + }, 900, 420) +} + +var filterOpts = DashboardFilterBarOptions{Envs: []string{"production", "staging"}, Tags: []string{"api", "web", "db"}} + +func TestDashboardFilterBarChooseAndClear(t *testing.T) { + var f DashboardFilters + n := 0 + tt := filterTester(&f, filterOpts, &n) + _ = tt.Click("Time range") + _ = tt.Click("Last 1 d") + if f.Range != 24*time.Hour || n != 1 { + t.Fatalf("range %v changes %d", f.Range, n) + } + _ = tt.Click("Environment") + _ = tt.Click("staging") + if f.Env != "staging" { + t.Fatalf("env %q", f.Env) + } + _ = tt.Click("Tags") + _ = tt.Click("db") + if len(f.Tags) != 1 || f.Tags[0] != "db" { + t.Fatalf("tags %v", f.Tags) + } + tt.Key(0, ui.KeyEscape) + _ = tt.Click("Clear filters") + if f.Range != 0 || f.Env != "" || len(f.Tags) != 0 || !tt.HasText("All environments") { + t.Fatalf("clear: %+v", f) + } +} + +func TestDashboardFilterBarDisabledAndRules(t *testing.T) { + f := DashboardFilters{Env: "production"} + n := 0 + tt := filterTester(&f, DashboardFilterBarOptions{Envs: filterOpts.Envs, Disabled: true}, &n) + _ = tt.Click("Clear filters") + _ = tt.Click("Time range") + _ = tt.Click("Last 1 h") + if n != 0 || f.Env != "production" || f.Range != 0 { + t.Fatalf("disabled changed: %+v", f) + } + defer func() { + if recover() == nil { + t.Fatal("unoffered env did not panic") + } + }() + bad := DashboardFilters{Env: "moon"} + filterTester(&bad, filterOpts, &n) +} + +func TestDashboardFilterBarSnapshot(t *testing.T) { + snapshot(t, "dashboard_filter_bar", 860, 90, func(c *ui.Context) { + f := DashboardFilters{Range: time.Hour, Env: "production", Tags: []string{"api"}} + ui.Column(c).Padding(16).Children(func() { DashboardFilterBar(c, &f, filterOpts) }) + }) +} diff --git a/messages_devtools.go b/messages_devtools.go index 9e2a822..8829dd6 100644 --- a/messages_devtools.go +++ b/messages_devtools.go @@ -54,6 +54,18 @@ func init() { "refresh_interval_selector.minutes": {"Every %d min", "每 %d 分钟"}, "refresh_interval_selector.label": {"Refresh interval", "刷新间隔"}, + "dashboard_filter_bar.label": {"Dashboard filters", "仪表盘过滤"}, + "dashboard_filter_bar.range": {"Time range", "时间范围"}, + "dashboard_filter_bar.default_range": {"Default range", "默认范围"}, + "dashboard_filter_bar.last": {"Last %s", "最近 %s"}, + "dashboard_filter_bar.minutes": {"%d min", "%d 分钟"}, + "dashboard_filter_bar.hours": {"%d h", "%d 小时"}, + "dashboard_filter_bar.days": {"%d d", "%d 天"}, + "dashboard_filter_bar.env": {"Environment", "环境"}, + "dashboard_filter_bar.all_envs": {"All environments", "全部环境"}, + "dashboard_filter_bar.tags": {"Tags", "标签"}, + "dashboard_filter_bar.clear": {"Clear filters", "清除过滤"}, + "alert_list.empty": {"No alerts", "没有告警"}, }) } diff --git a/refresh_interval_selector.go b/refresh_interval_selector.go index f9a39e3..5839c59 100644 --- a/refresh_interval_selector.go +++ b/refresh_interval_selector.go @@ -44,8 +44,8 @@ func RefreshIntervalSelector(c *ui.Context, interval *time.Duration, opts Refres options := []SelectOption[time.Duration]{{Value: 0, Label: msg(c, "refresh_interval_selector.off")}} known := *interval == 0 for _, d := range choices { - if d < time.Second { - panic("mujica: RefreshIntervalSelector intervals must be at least a second") + if d < time.Second || d%time.Second != 0 { + panic("mujica: RefreshIntervalSelector intervals must be whole seconds") } known = known || d == *interval label := fmt.Sprintf(msg(c, "refresh_interval_selector.seconds"), int(d/time.Second)) diff --git a/testdata/snapshots/darwin/dashboard_filter_bar-dark.png b/testdata/snapshots/darwin/dashboard_filter_bar-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..8e96c8813a748e7fbe5ac16c2006f6cfc0ea0418 GIT binary patch literal 6339 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 literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/dashboard_filter_bar-light.png b/testdata/snapshots/darwin/dashboard_filter_bar-light.png new file mode 100644 index 0000000000000000000000000000000000000000..be559045800d15662b63a63a11f25fe0ed6875dd GIT binary patch literal 6241 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 Ku~NZ0=sy6+%WpgY literal 0 HcmV?d00001 From e755f7937190d2efd108956bb105b0462760d7a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 5 Oct 2026 14:55:00 -0400 Subject: [PATCH 09/14] wip-devtools --- log_stream.go | 122 ++++++++++++++++++ log_stream_test.go | 97 ++++++++++++++ messages_devtools.go | 6 + testdata/snapshots/darwin/log_stream-dark.png | Bin 0 -> 25790 bytes .../snapshots/darwin/log_stream-light.png | Bin 0 -> 24674 bytes 5 files changed, 225 insertions(+) create mode 100644 log_stream.go create mode 100644 log_stream_test.go create mode 100644 testdata/snapshots/darwin/log_stream-dark.png create mode 100644 testdata/snapshots/darwin/log_stream-light.png diff --git a/log_stream.go b/log_stream.go new file mode 100644 index 0000000..0e188a5 --- /dev/null +++ b/log_stream.go @@ -0,0 +1,122 @@ +package mujica + +import ( + "fmt" + "log/slog" + + "github.com/egoist/mygo/ui" + + "mujicaui/theme" +) + +// LogStreamState is a LogStream's pause, level filter and place. +type LogStreamState struct { + Paused bool + // MinLevel hides lines below it. + MinLevel LogLevel + // frozen is the line count at pause; lines after it wait. + frozen int + shown []int + scanned int + min LogLevel + rows ui.ListState +} + +// LogStreamOptions configure a LogStream. +type LogStreamOptions struct { + // Height of the line well; zero means 320. + Height float32 + Disabled bool +} + +// LogStream shows live ANSI log lines that follow the end, pause and filter by level, virtualized. +func LogStream(c *ui.Context, lines []LogLine, s *LogStreamState, opts LogStreamOptions) ChoiceResult { + if s == nil { + panic("mujica: LogStream needs a state") + } + if opts.Height < 0 || s.MinLevel < LogDebug || s.MinLevel > LogError { + panic("mujica: LogStream needs Height >= 0 and a known MinLevel") + } + h := opts.Height + if h == 0 { + h = 320 + } + var r ChoiceResult + r.Element = codePanel(c).Disabled(opts.Disabled).Height(h) + locked := r.Element.IsDisabled() + r.Element.Children(func() { + codeHeader(c, func() { + was := s.Paused + label := msg(c, "log_stream.pause") + if s.Paused { + label = msg(c, "log_stream.resume") + } + if Toggle(c, &s.Paused, label, ToggleOptions{Disabled: locked}).Changed() && !locked { + r.changed = true + } else { + s.Paused = was + } + levels := make([]Segment, 4) + for lv := LogDebug; lv <= LogError; lv++ { + levels[lv] = Segment{Label: logViewerName(c, lv)} + } + pick := int(s.MinLevel) + SegmentedControl(c, &pick, levels, SegmentedControlOptions{Label: msg(c, "log_stream.level"), Disabled: locked}) + if LogLevel(pick) != s.MinLevel && !locked { + s.MinLevel, r.changed = LogLevel(pick), true + } + ui.Box(c).Grow(1) + if s.Paused && len(lines) > s.frozen { + codeCaption(c, fmt.Sprintf(msg(c, "log_stream.waiting"), len(lines)-s.frozen)).Role(ui.RoleStatus) + } + }) + if !s.Paused { + s.frozen = len(lines) + } + logStreamFilter(lines[:min(s.frozen, len(lines))], s) + if len(s.shown) == 0 { + codeCaption(c, msg(c, "log_stream.empty")).Padding(Density(c).Unit() * 4) + return + } + s.rows.FollowEnd = !s.Paused + s.rows.Key = func(i int) any { return s.shown[i] } + d := Density(c) + ui.List(c, &s.rows, len(s.shown), func(i int) { logStreamRow(c, lines[s.shown[i]]) }).Grow(1).Padding(d.Unit(), 0) + }) + if r.changed { + slog.Debug("mujica: log_stream change", "paused", s.Paused, "min_level", s.MinLevel) + } + return r +} + +// logStreamFilter extends the shown lines, or redoes them after a filter change. +func logStreamFilter(lines []LogLine, s *LogStreamState) { + if s.min != s.MinLevel || s.scanned > len(lines) { + s.min, s.scanned, s.shown = s.MinLevel, 0, s.shown[:0] + } + for i := s.scanned; i < len(lines); i++ { + lv := lines[i].Level + if lv < LogDebug || lv > LogError { + panic("mujica: LogLine level out of range") + } + if lv >= s.MinLevel { + s.shown = append(s.shown, i) + } + } + s.scanned = len(lines) +} + +// logStreamRow shows one line: time, level word and ANSI text. +func logStreamRow(c *ui.Context, l LogLine) { + k := Tokens(c) + d := Density(c) + col := [...]ui.Color{k.TextMuted, k.Info, k.Warning, k.Danger}[l.Level] + ui.Row(c).MinHeight(d.ControlHeight() - 8).Padding(0, d.Unit()*3).Gap(d.Unit() * 3).AlignItems(ui.Center).Children(func() { + if !l.Time.IsZero() { + codeMono(c, l.Time.Format("15:04:05.000")).TextColor(k.TextMuted).Shrink(0) + } + ui.Text(c, fmt.Sprintf("%-5s", logViewerName(c, l.Level))).Font(theme.MonoFont).FontSize(FontSize(c, codeSize)). + FontWeight(700).TextColor(col).SingleLine().Shrink(0) + ui.RichText(c, codeAnsiSpans(c, l.Text)...).Font(theme.MonoFont).FontSize(FontSize(c, codeSize)).Grow(1).MinWidth(0) + }) +} diff --git a/log_stream_test.go b/log_stream_test.go new file mode 100644 index 0000000..782c686 --- /dev/null +++ b/log_stream_test.go @@ -0,0 +1,97 @@ +package mujica + +import ( + "fmt" + "testing" + + "github.com/egoist/mygo/ui" +) + +func logStreamLines(n int) []LogLine { + out := make([]LogLine, n) + for i := range out { + out[i] = LogLine{Level: LogLevel(i % 4), Text: fmt.Sprintf("\x1b[1mline\x1b[0m %d", i)} + } + return out +} + +func logStreamTester(lines *[]LogLine, s *LogStreamState, opts LogStreamOptions, changes *int) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if LogStream(c, *lines, s, opts).Changed() { + *changes++ + } + }, 700, 400) +} + +func TestLogStreamFollowPauseResume(t *testing.T) { + lines := logStreamLines(10) + var s LogStreamState + n := 0 + tt := logStreamTester(&lines, &s, LogStreamOptions{}, &n) + if !tt.HasText("line 9") || tt.HasText("\x1b[1mline") { + t.Fatalf("texts %q", tt.Texts()) + } + _ = tt.Click("Pause") + lines = append(lines, logStreamLines(13)[10:]...) + tt.Frame() + if !s.Paused || tt.HasText("line 12") || !tt.HasText("3 new lines") { + t.Fatalf("paused: %q", tt.Texts()) + } + _ = tt.Click("Resume") + if s.Paused || !tt.HasText("line 12") || n != 2 { + t.Fatalf("resume: %q changes %d", tt.Texts(), n) + } +} + +func TestLogStreamLevelFilter(t *testing.T) { + lines := logStreamLines(8) + var s LogStreamState + n := 0 + tt := logStreamTester(&lines, &s, LogStreamOptions{}, &n) + _ = tt.Click("WARN") + if s.MinLevel != LogWarn || tt.HasText("line 5") || !tt.HasText("line 6") || !tt.HasText("line 7") { + t.Fatalf("filter: %v %q", s.MinLevel, tt.Texts()) + } + tt.Key(0, ui.KeyRight) + if s.MinLevel != LogError || tt.HasText("line 6") { + t.Fatalf("keyboard filter: %v", s.MinLevel) + } +} + +func TestLogStreamDisabledEmptyMany(t *testing.T) { + lines := logStreamLines(4) + var s LogStreamState + n := 0 + tt := logStreamTester(&lines, &s, LogStreamOptions{Disabled: true}, &n) + _ = tt.Click("Pause") + _ = tt.Click("WARN") + if n != 0 || s.Paused || s.MinLevel != LogDebug { + t.Fatalf("disabled changed: %+v", s) + } + none := []LogLine{} + tt = logStreamTester(&none, &LogStreamState{}, LogStreamOptions{}, &n) + if !tt.HasText("No log lines") { + t.Fatal("empty text missing") + } + many := logStreamLines(100000) + var ms LogStreamState + tt = logStreamTester(&many, &ms, LogStreamOptions{}, &n) + if got := len(tt.Texts()); got > 300 || !tt.HasText("line 99999") { + t.Fatalf("%d texts; last line shown %v", got, tt.HasText("line 99999")) + } +} + +func TestLogStreamSnapshot(t *testing.T) { + lines := []LogLine{ + {Level: LogInfo, Text: "server listening on :8080"}, + {Level: LogDebug, Text: "cache warm: 312 keys"}, + {Level: LogWarn, Text: "slow query \x1b[33m412 ms\x1b[0m on guests"}, + {Level: LogError, Text: "\x1b[31mpayment provider unreachable\x1b[0m"}, + {Level: LogInfo, Text: "retry 1 of 3 succeeded"}, + } + snapshot(t, "log_stream", 640, 260, func(c *ui.Context) { + var s LogStreamState + ui.Column(c).Padding(16).Children(func() { LogStream(c, lines, &s, LogStreamOptions{Height: 220}) }) + }) +} diff --git a/messages_devtools.go b/messages_devtools.go index 8829dd6..c5c325c 100644 --- a/messages_devtools.go +++ b/messages_devtools.go @@ -66,6 +66,12 @@ func init() { "dashboard_filter_bar.tags": {"Tags", "标签"}, "dashboard_filter_bar.clear": {"Clear filters", "清除过滤"}, + "log_stream.pause": {"Pause", "暂停"}, + "log_stream.resume": {"Resume", "继续"}, + "log_stream.level": {"Minimum level", "最低级别"}, + "log_stream.waiting": {"%d new lines", "%d 条新日志"}, + "log_stream.empty": {"No log lines", "没有日志"}, + "alert_list.empty": {"No alerts", "没有告警"}, }) } diff --git a/testdata/snapshots/darwin/log_stream-dark.png b/testdata/snapshots/darwin/log_stream-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..64003dff77c7648a86950345da432d28b7fec39c GIT binary patch literal 25790 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 Vc&^X_Joj{Tw{93*FVV1%_%BH@*TVn+ literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/log_stream-light.png b/testdata/snapshots/darwin/log_stream-light.png new file mode 100644 index 0000000000000000000000000000000000000000..68839db031f0f8f4b729de69bcfc10015184dd46 GIT binary patch literal 24674 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 P7656e>ZnvH*@piI()E%H literal 0 HcmV?d00001 From ea7db1c98cc803af358680a7bc41d5d1691da42a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 5 Oct 2026 14:56:35 -0400 Subject: [PATCH 10/14] wip-devtools --- messages_devtools.go | 6 + .../snapshots/darwin/trace_waterfall-dark.png | Bin 0 -> 15132 bytes .../darwin/trace_waterfall-light.png | Bin 0 -> 14635 bytes trace_waterfall.go | 178 ++++++++++++++++++ trace_waterfall_test.go | 78 ++++++++ 5 files changed, 262 insertions(+) create mode 100644 testdata/snapshots/darwin/trace_waterfall-dark.png create mode 100644 testdata/snapshots/darwin/trace_waterfall-light.png create mode 100644 trace_waterfall.go create mode 100644 trace_waterfall_test.go diff --git a/messages_devtools.go b/messages_devtools.go index c5c325c..ebfd5fb 100644 --- a/messages_devtools.go +++ b/messages_devtools.go @@ -72,6 +72,12 @@ func init() { "log_stream.waiting": {"%d new lines", "%d 条新日志"}, "log_stream.empty": {"No log lines", "没有日志"}, + "trace_waterfall.label": {"Trace", "调用链"}, + "trace_waterfall.empty": {"No spans", "没有跨度"}, + "trace_waterfall.summary": {"%d spans · %s", "%d 个跨度 · %s"}, + "trace_waterfall.detail": {"%s · starts at %s · takes %s", "%s · 起于 %s · 耗时 %s"}, + "trace_waterfall.error": {"Error", "错误"}, + "alert_list.empty": {"No alerts", "没有告警"}, }) } diff --git a/testdata/snapshots/darwin/trace_waterfall-dark.png b/testdata/snapshots/darwin/trace_waterfall-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..637f862fc3b86946d646836e2ac6316bf5250cce GIT binary patch literal 15132 zcmeHuWl)^Ywf|aKB}}7FG(;SzQo5o>d%RzsIAam zNgUk#LWKbs+|=51VCQz*mz~VpQ#Y7SVTsIY3iUJu@VJ30aodRv{4Xkci$A*FpQ=H{ z^-Jh>9W!F{nA#KR6!hwK%jz4dxmr2l+biRTt8*P@3PU^>wZm6Qmr@CIPST;q;MSF9 zgIj_2zvF!df3>pjXNttHN+9ij!*T4dk{q@i?;EU6m;Y07nGb_Q^4sigDf(T>`xo0$ z|3ym!hX=!#0@%O9A46U}M1@-9{s2Fj638l)&;};b+369%hanHkmC(|*3HRJd$LqFR zH^0X1mcxlRHq=0r#_=VrFuiO%}NoM zD)H-6u*F%58Sxq!$-C@d$`Y1a0%z&BDvu06eE3?{El=tQkD3Iy0lYRhm%Mh|l*c3t zx-Ka>OjSy$YP;TBmVXOqxyj0>{>G>(zGPp|ZUR16gh6+a2Scz|h^0O%QrMIhg~>zw z4rkY3Hb9?i55D=eEZ|^|%fC7oW7~K|AR85? zSXUz%svSdBo`YsTsy|hi$}m0rJF)QCV`?e2u4Fnbl|NB6P5F3+Ay!Je&5=!t*5@lh z=mb;P7j)HXp6S1d5jzyaMZFsH9pmlc1Yi4XW2wyi|@u0{Ag zA1|GZ@#*mKUa9?kN#<6lKThS!^?EW8DKtPqyyhdljp_)@%QA%+_o9^Tx?@B{`F zIjED8lKLDz910k*Tqem}cBDP1Fep70Foc=&I}K!%={|_lpOS{7+`a$?0X9%?Up$kd zKg*tB)|E}03f=leC==1(hoNCKKVA<+%tI+!)iPgOnn+u^O0IpcXlWR$l$KY$G%A9uu}4P7-gRSwNJ(AsnF=p%)#iTlDN8OXB$9%KQ2# zq522_Y7~(B*0^!y`<(4U4!YMHe>$!|*H`gy&4o)Nxz9ZJ@67yVitFG0(xQlQk*PXg z`(SpvIevYeIIDX@?R1Z9DuwN_PBr__>H)OC{Xp zC?O$*oq53j06`I4>F>7!4QuVK;@NLt;om*pRW2XB#fFg@6~|qK}_{H}D}a;MMXMsb*v8REKE;UBkP%W~L;!-9YAJ07(RsLj}LU zLnp!u^Rp%qMiz9gd(d@Ja#+3&9_9@_uwm?LX{qsJ7Xe1{ z^OK42bFqXEGA$X7CaV0nsqg2^#BbV0PXs+KcgCDrMq)gmLB{~A{s7vbAfGkp?$dTm zhX(RI41wx;X(=2O$%(q?$myP_`NS#Yn+kg_j{wIrcLjz-1rqC?W1Ib2$gP6Y>heC6 zgLx-6M7n#+1-;fQ)L5K1z{V>5}({M8j#;G|J?Njie3eJ`Et)-H*C`J2@2Ags#0a`j}k#I!47rzlYiyQ z02RQ0gzV0W=aSiHVl%MV)>}WaP^nyXrkTsx6l`G60(IY8F0Y7c=@<%&BXBv1i0GKN zUfc!Z3j`RUE&Z7PAoG6#^k3gRA0orG6CO12QzBFTw+CRKj(eT1?>7Fgmr6@c3nuK3 z0q_7vZn?JqkAls13}7(u>b^lw5{%)3TR37#H)s<}i%+^e)KqJj} zY2|q9B{`h<;8ura_ukW??y!swPr=B4e^5*3ZFqD9zjc+Gq9OrEshCHn-nBX{iB&HG z6PS8`&#I~&RrNTu+_qT)ka(UNM$Nu8Jxs1BAQxa+Loa=7i&c9*XfW3Sjsx#8Oock5qYNmDVsdoWqw2reeYxsNp;QB3*pVttUQt%S#QMCy=Yh!< zBl6kJ)ffj8(fCK2_&;nsIqTz`F~;oE&^Hz@5L=>86elfDbA@|zdTUGD2M0LB_+mE6 zW=r#nhOxrjj-GBqMT>W#jo(lzTHn{@6!#3ceszE5=m7OCM=CfSQ;=EaIb*(A^gb>m%Rk1+;g%Ox;!u{x1TFZUoHF$k)EmwO%R`#1p z=9U%brPb%%fb+YQExqu?E%2Zz{%_60$V7=$`SqHs=7l%dCglEr0^>H@%3YhuE+Ngs zPc9;lk8P1mkenX3Ldoa`aUcL!w!B?P)N%K#G=N_8p++u$34E+^ zR)(-bN4*7is-~JXN$d(1%uWN>oFysHG}jlWrL@Is{2~0k)&oyqpm1wTy3ta^K<{En zY2h4qy_t>)ZOS08ebNGng41uqSiibfC^$quR?gW{S^GFNtX)AqBa=LZ?U%fa646LH zCvc^*2bmE(WK3A+QQmKRR@PnK58K>Sm66b%PhCz@#9t4LG$*CuMlU>Q6~LXe^`5uE zrz9?$9-4(3-h(9dVj$US(w6Q{$Dz+Wetr+@wKeA>tjKj~8#%pf!*moMh3-4K5LoM! zObl$H@)*THXK>|;j8<5Q7mUR0@ceu(GcGb8wzRmkN1Wv0=^fp>2QbS{CmO^2DEdVP z2PMp0`#Qg+moG3GIo5D?ZIW46V1EKNTYG-aZl;v4a~PZQY2h>X-HPDsprn$FVrV86 z8hJm3+VDq_B)(r)NAvQJ#DB>m2S#U`fA6dZcN9^o+0Z*8@O&sfJp#ZF^slt&Acs?J z??X+fylx_k+a8VK>mJufcYh{6+1M1FZTE>j?Sxc5xnm^vCS!H$6B*H<1ye=nBwuY2V&54)PWLV3kE+>jcF~ zZ~kbbM~q7vo1t1Ikg6>6`2dxcQkf&nT~6< z#_m2?B;$O$HRN5o5km~X!Pk4D=gjizb%^Xm$nTP7`pw??h9jR%AH%+>R~L^qKG?;a z#(s}t?JcYRZ+rSHKv%opc4*;06$>`aS&LM!{GMH0^!*;~lb%X{z=_MWoSikKx8u}q zp76k6gd^=L-DfY{cR*c^JY8MEd;vQ5dW%on)NN9^r>9>o&cM_b$D3dxp51iA_Px(?Bp5aff^>mB&yENIX9wAB4OdVb# zHr*$k(g0lC@e5~X_T?-;{c@!^M_}RRFxHt<^vfGeI44LVdv5M}Xvp53Hkb;nkjG}o z+i`v5A^-?HYdGe>n=GqpZ{^vn-p^iMS}tRKF9wB4tbXP;U`e0@uv8? zf5s$1x!a%rNDI>dnh#W7kdH&!1(=1o1NxK0Ur92F_>1pOYY%tSTJ5ePlUY8nPC;95 z2vd@6_IA{=c&Qm!G`ni{Sy?z#FpLdZeD1Q7r-$RzK7Xz>Bd)%Pl=saVf6vCw{2p?- z>VUm{vc*7&9knQMm!xgHsUVbWwX(XZpIlN=0XTh^UpA2YTc8YB?ECqG$KtCHMkq!9 zSzutGMQ1N=XqM?}5HSn&!1zT`Cd}4E&In?HB3bFM^zQFk0NXXA?3`T5A09Vjvuq8v zTRsA>2E0g*4(^W5{yE(-(q^;2ghpFpu8P;GFVBtyvbP}wlAN=1rp1+&tzOVFooiFm ztQ57==zR8;7jukx-^bYX0dtVNt4JostM2PUOzC+cB&5eaG|}}r^%QqeSvC?DKR?fq zIha!kQ6hGe7<<=959i0m-D=N|>hS*`Q^@TK^>EAeszG9TB5x!ft$L? z4*b5&U3>mI`JdyyjnYryf1D5k5*7~?Faod@eoOt|qk((V0ZV+4kVx3Xhpq3zN9w(` zYw=cux91k`8^>O9c}4ON_exFb{)m@L z5X=dfA=8)l&2~zBuGB{g;%gq$tMs>kWt{M+i1O@*kulg<2ar<|(GVGvB3K6$VfwIz zspbT{T-zNcit63C83Qed;1@HMHGY+-MdIP)!Rlss!Cp`h97V%KMUt*WV@Q+3W@nqG z6q=mYf2yT4qR7=agGZ;0^XDTbA;CAbFX&pFbX=ba&4bK|k`2~wp2j%aqQ043OYCKD z+=-F}|e@a%f^?D!|nH3K}=gq_hYiKd-C=?^U zmVNi0tjriEKc6>`c5p~I-J@VTd{A*}g@ltcK-VV8=PZ?|yT9s+f7LuFf^9pE0q##A zrulxWx%8CO`q`k}=q_0Xsi?flCqthd9}Gzxq>ZBkoDB(+j(q`s1w7RN(8HL?YQ+bi zQ}Uwfd;>OJJA=Dt7l?y{OOgCN$261(5gZ+NMGUO0uzI}uBKR8XbqM>5lwmI39WUeH zk95Wq%`!Cc;5}kt36JI?wT|YPXwxib@wF3=3r57tO{BzaxB*uCxl)y-r7OW;V56xC zV+a*m@Fl(!vKdI7!?e~FI$tfH+4r5&QsO0&3`5tCphXDL^Yz&r2Wy!F(Fpd$F=|t* zpxPIY+r8EvC$0y!QI-9%tg}YADI6D4GO%NO@2Zz_hQt|gJ8`>U0{7>7&&snKGOKiT zxxgjDFBI!dB4n}9e7S8Y7e@{g4D05)wdkL3p?Fzuc(FT%>P?+|HSZqTUPGQJ!g(-%xHQw&dJ{%DTQ* z8+g%jlah*7ALkQ;)vQDu)+6KccmxtZPyefO1I^!>pPuL8!9R4nf1f^gdfPS&a8^_m zd~Jti6W27U!6m@WCn>UX|RC1 zIkY*o5}el}xBMn2uXO2ync2sOj}KwOVhxcKx35@-Z+o=s4QttG$_pFVPxPajQ%h39 z$&h?}o8FM}o=(JX6T6*17Wyq4LOysurATbUhjC7Q-&Z)%DfINxQ$(uivLxBp7gj5D z?O)W?aJ&eLtxwWlnOLhtK|(kTYcj{t0jgfrYQ&;v1mzxA;VqaY{v$TIG8I}LV!H@} zHALc5C@Ct|w*r){EkkzN2jbo|8yKX!`W(MK%EUowoL) zzrrD4X=Y_WJy(5#BiMM-f(?h01nWh1k$RoS%-0hsTQ=L|p@XockHWq!rF-BBeNKn_ zpFJ}G=BRg00xQa&^C;$iC?))s%O4yV8wLNY(sejc4dmK}4KYr(2IYAjuOtLgUroP< zqP!G;Qe>>J_be=A@bg=ujkDN&*UEA_EnIA>}xTx{U zYVgk?J3r~V39?Vl_A*lgz57ysPy7%90e`?#5<5E(&%ttTQBflsjdF=*FQtl~50yy= z=r^+Ei>hV=MGfs*?dw0Lj8zPgKb>bImD%UTemf&rf(ZlZCrgs5_`du34uKTT0m;eXq_RD8;OV|Tica6zBOrP=q49=|Na+DH=Rg-deKbqNh5-rTkG*~L zR#y9@q;A8*^{ree3U5h1BZ}i>QAOYB_Ep}J{db0?c+Y(hsm)j zVSfL5UDq@q!?bY$C9AU;`%CsN9INJ7SrB!xfi+2ZRDymhtgk}k+WGb}I_AjO1-sE% z9_9IJ3-COo4j~n90gUUd9@rlrK9!IBM1E*oUeX`@>=yO1S-CZHYGQ)d@!&@)5G)tA zGIAl*=#o+y74Wc1h|7*ofsAZTd4GS=&~RO+W%FWVt$hf6%rfNV)06e>2xe9V1;K{J z*zVVcIV1im*X*6g&e8&deXW4D)xfq@j@{oC#$a`|xYent($&g%=AVj5#+9d6nWCM( zM~MvolTROu9%9HWLH8rRS@D}2U-=6u{Lf{3=?T=`AB)n{Q(w2AFxn+2Cnq6y%m$Nf z_btRxI0^XV#8wscBl?$|YQ1@XHVC7MaZofK{h1XKT5jH4$J;#8Dfs!ToL{HSXau(i zpQU52&oe+#7U$+DpB7JU)=dIF!M$ST5|aX%neXJ0ZlA+F{2w+o$Y^@E`iAu?IgFac z?WI5a=ywDQAiv1TtMOaCub7>!NBU#qa#Wd_J`Zd%u9zcOZ4sO6%*rd~v8gSuKt(|v z^>a!~Q7rhb_EE^6J$-cULFh?p;89==3tOAX(5r{Mytw>z5bfC@^~L%^gJ*lu_zERs z$ferH;GP@(n+C*3>|<~MKeD-K{%OX}&?nBlS0jI!I+KFQs^!&qm8|+Tr(W8OfAMy8 zPT&P7DC@B3ApP*J|A(Y#x`GXq_f~7p6N|=#(9(Jw%gJFW~%ajYz@(kWS}FJDwuRJ1QozUEJ_m=rDD{DM_p&bI3& zD=m$0GLN1;uEmnnLYrd3MRZE|zQV*Mf5EXKo?4H^-%%i}^Al(4NcgB<29_Ks`jyB! zpn*A&Sr|m?5%Ojdg#1LhR|#@0m!j${I-q9~uruGgH zMx)e-`{papIaDN~M7#IgZ#=GYSULawco;B#o2ncJA1QfjdE_ISzYcqh_d72tEVM?_ zZ)5t_YcW|7L>D2wPD#c)V7;V0{w==*Ll*N1L$riTRs-piz`2!xUJ)*Bn2z?`&YmfA zlPMZfF15RlC~b=`r;Y^*2_TRk?cy{GT6R&O(Tqy)AwlH=tdqufN^`maLfni zGVuIUTv3rYw1bu#6*UBse0=@-o_LJG@!8iU??A8gjqPfOp;_ZxxXt-ro0oM&w!BNf zUGrZH3Qt^J+C%^5W7Au&j!xF2s*)^2TwKJ&+EMS~yiX+btJKvMsGbFgAi31b=`i3) zz|eWlmjt?3Yn0Hg5K%D`8TkYpDb#Je8+ppSK5$qHs?o3Xv2>n-wmi439bdf2Iunt# zwNr@}7J$;#@h`NFDyv+%yj?RzKbp31hifKs^2=Eg*L zXO7E4lNL=p&9~CB0y0{!$le6-wOBEARoB447ZV@#pkPd^I*V`+H(;Qo>*#9QZBCp{w%k6P0w1+-=pOd7*vHSJQmoK-FqvIS!c6Y?? zX)lts8Sp6|k`Od^c*iVNoE=!@AmF?{1lH3xcDt8~3JK(3k;W&oF5K)KW{4%LXJ!-h z=-pZq6F1-n%{5$=L5u{eGP%@hC#-7slr!6e0(M(;hQqPIS%ZTxq3`V9EcfM?u{g=9(Q;w*2jk@ zmf2GlAlB2^@buM(I%^|SDiT{~>!C?7(I@A*tp06&mA50K%3N6P-tNYvkE7dNTR&!T zQB)lt?X2X{Uexg}2b5V%;^Jg+3LXDTl3AVc#g>!!XrT%!v)#Wlby5z7M^}?yVqIibC zVK#32HYPRNW*p-b7wggDQk}eCYr3)=Yf<0GC>-bA5zj>WI#buO!HxB(f#Pm`$8bnR zIa_Hdonk@F?l-+KFc>_H6n-n+j|d^9(Fh5}R_iU_&n~_c50;#rom%y`a3$xc@@a5? zFto7=f4BkIdVS(lHa2MgG>PpV7x@VVUc5W6Q1-S7+$|L3! zepB=cq5T?RBW$Nh_zLrsEb)djTS9%?k$*TyF5gv1PNgd45Tj4DQidh}oHzc=0JZjc9h=Xr8cA5- z{a`=-qg8^LHh1rl^z1W;Xj4wk!0`0!#E*(}xi-RMm6)r`%cyh)Z&|hC ztmNw2V_OT0048VDDXD}Vc=>yLSw>oHa!LW~+aH}-+y=3nXFK0bCbi{g9l9FX?)~-7TA!WmMHtaB?hz zWJ|+5Hzkvk_fRBPn$yYJ{uV8@__4AoA>i-I!5Al(A4?{ZVgG?0`JTQ9_IJGsd-gQt zXR^+~F5gSo*A~{$?r>i%F*HUrB_($!+|<-GTcBidano-^&7M|A?WcbGUAnEU-({6j zA>aJry5AqY?*Y}G|FK9deIZ}Iv2T&o^Ny2;$01{VWLzG_aoM$Fv>p@$7yRpvP&w!( zD74v5(%kjSZC~iHv<#zaqkx^B2J_s?V=2M1SB;(Vk!Ott6C&1!P>-|pn&U1*;I7?w zJG|gLJ2hb7lbSR?bm_`yNp+@uSze`&{tM0Fh^!N? zuU|`4uTC1C6gEJ7Y}9{dYvVEoD&TBA4tFc%igQ1{KSn^(dh}F}CCSCjCXQ|}F#ar> zXhhhHPLsW?x)v&Pl@lCH!`Kf7f@vKbpbV-c6;9I3C3>$PurN%Xju&fFGRP&;dC#JA z0CRXKqz!wmze2ipeqyawh8XMSYxCO{5(Iy3_KmBlac4LAI5Grov~ej{W?)pHOW|Al z_$>(W_;5_tN3VY4qV%bo2K7DT-ZMWxn0-_C@*2Bgokxrp zJC2?|Rb@SQ>Q8R>_aYp{0h=PP(Gi2bKK$)+gS^vXo!{aVhYpM9b!GT> zY!wUUN*g%CBS~@4HmmY!g!v)C-?^G`)biV@%_6-~D`j~CnIfc|EAs*CYJ+l{ty1ei z$KCIXhyW5Jw^c(1X2&`roPxA&kscB}C{MG}ohjBMDX=V+fP;(@)p$+C-tQjK{2c#!X6A zuK#*#RB_AouEK?JnVpCTEmwYbR{#?eE#bE?B1#V;I`wqp-~Ljv6dp$zmg?@iO_3@2 zSYi6u?j&4E!j+aQ4>8aW!Z~**&*_>qXN5^5Jh!r^Lc|Pla7)-)cV6j*;CGNsSn7)~(jOwelFGwGyEIYW>K`QA zHcj*nXp+;D4(%Nb4fbg5(@N6QWD8wA@#!x}-%kAxq;$Ikub07tOq6JD0!p-rb}lRlPL69G6n0vE-})Z| z?r%4wQN0It`B{<-nefKag!lpsPfKU3qGf30<^5AfqSkS!zfqq14U#@PKO1491RG3C z1;6Z5qEFzD-J40Rb78k~+g4-G5*V6SqQiSq&lA3NHBOi7hRyVP?lLkFpz)8z9Q+o? zWyU;5%)cVYBgKSsYDrP?1jT4xu^wG&++)|LP4xO_wY#nnw8hbwbE-+TNva8;~_2GhARyUlG@b=Qu@k3OJp3;E&a ztGme;lHe1{=2ASMdxByCSiE}*tP1(%ESg1=cXQqM{gmS5_^K#oq)dj_cdCh4SA2Wt zKwV$IZL{8~-t$UVH`&1y$nIgEqs-9i2`#Evz9uq*)PH#%DIV|@aDL`x{&EDqkq}Sy zyN;dLRm2-*{`Q0RK3aR#}b z1i-}OsjnD!t)jwP{J~g=&vvWh`H7{}{w;-qt)qRifsyR{G?B~dPD7`Mf{~+Xv#f3J z<4vAtc@yI%=)FvxmapG@!T`c$gq|ars>*-1u8yz#=tK^c+U&h#eMGXWUkX)vu5s#L zSE;fcv4u1>ZC8imhrc0fvA%Ne6M0QEmL?z|dbqI9wS2R<$lTD@o)xi!kq!=AU0KO! zgw#rii8(-%^+FipB-}0+vH}4rsdxuRJ4}u7xRZTv%4n>^=@~LtZl3#}^8>tLD?RXf zguQ=MZ^2yn`PPAli$zi|8OweAN?K}KevkOXFD(vMm**w4el#hpMC7@8H9*!P?Wh|L zENvbl(ua!%nT088xrIw>TCP8CxiZR!=LDU}5D;*#bq99H%O{Q%*^HpQIa$BK1rcb4pv10SCs$Qi>Mm3N+_e*W}y_ArW|qnp&z%^mx_n?W;-U4!E`5O&51; z!{9b+Eotd|Z59fFISwx7tL_C98WxVK`;88-?HBRtg7ktLWp$Sq`w;p(0=?bD_=|_j zYA45@-+)A*@;%pKKSFr^t^iT(|EZ^a=jZ$O{0p&>{%@Pl)oiRdXasKQy3bkS&b?#0 zlQRM7>*G!2BCdue239s@zr>vE-G-Qxt=dJyg_mxqX;SNxIzN3PVL0w8z)a!s6b@T2 z3Gaq$IUUo$s|$6uNuNCx&i1sPFfq=Eh%k=$*qOntv zt5dBfU#kLPG5nKbX_;v3qhXz-ol-@?tM2wd55K+u>YxW^X69+zI)rgXyq5>dE54Jj zT})vPk1Tr_>bBR#nE#TH2Xnez)-~P!9CN|fnV+ezt5Zcs-x)(0`9*2*u5f87dwJP2 zn_O&fAAqR*ek(CKKVHeou3tP*f5Z8^OA1||SJ5eCf6p_f6pcvjY6hDJ9o7Yy1crqB zgQgjH2ihTWWxKyX9l%PLh{bmJRYmI*yD`RF64*XZ2B;{!+vU^ZFAhz;@G$r5bj+J; z1B!Q)7qa$6RcBlzSg7KHfB7CGZ`M{t>FKEGe6~?=yO_;qQiP5YUs4y;K!*736Y;*G?+kQKSx&3TY_q%d=+^bGj-Yg}08e;D4U; zF~*6*+;Wu^WnVC&B$mO}v`h6H_=Jtfoe=kGNMwldboFB4qCok)zZZ8(xd+`9`tkw7 zoQPcGegUh!fVlY1Y!PP%8dlAlLGD62KTSZd9!10+wIy%~ONZy_dtEX}aVc%gMyXjzR?-N5=+hbp{NQ`9QpdYJ*MB&(Gb<7W^kG zXML-!PQASXPmgWQqreC1zTTo@?Z2*;^0$A<-FHf@v`12tW_qc1^ z!$tq%Qq=3y5j^YX3A^4q776XUz(3y^RQf(W7Q|S^M>jYjbcDK}V?;-}oVGq=<24Dj zorzA98)IAhKrYLc{6o9^>FaA1llEC= zXES-BwONs-TI#j|uR|i}@Tz5KxYqN(zQQD8Tm7t2X#y+%vuk#hY@eQ91Y7Hb{AEWE zl{!g(82RQdF{hhM6W`muesB|HV5mq+(S2}r16r=b0$eU6!79(H{JLTwmce5+E3bgS z4T+ne9e0V*Mi6XhNPw7|AJTkM&W)gfP^nHmJK|{Y!_`7*yUF}o<+d61H+ApB+N1JU z&z9tlev_ant@SB4QO0P%fgCAT*n?>oWck`G&?s9je^>9&JpIi3ZtL$~(UA++Ty=d@ zQ{U~qxJOPYH0qfOXbI0VdsSg!Wntk7HY^NB3hk=yTl?jW9D=C*to|qXv2R~U{09^K z_v$=TzJg0ER)zeNn2BQXJSW!GWFB)Hnf?Fk|C<%q-h$lrXHn#{V9A)1SW&?wr4-Ts zKI;+xoJnM^oPB>Jhe_)O{yXNlLGXX$MjzzmbHspF0v*L7850BxvV^@8QULi_)8r4)|){2FQFm1tOSk$KTS P3!)&aDpM)-DfoW?(tZK% literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/trace_waterfall-light.png b/testdata/snapshots/darwin/trace_waterfall-light.png new file mode 100644 index 0000000000000000000000000000000000000000..314480ba16e02ab0c1d6f97704580122bd7a98fc GIT binary patch literal 14635 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 literal 0 HcmV?d00001 diff --git a/trace_waterfall.go b/trace_waterfall.go new file mode 100644 index 0000000..9ac6b6b --- /dev/null +++ b/trace_waterfall.go @@ -0,0 +1,178 @@ +package mujica + +import ( + "cmp" + "fmt" + "log/slog" + "slices" + "time" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/internal" + "mujicaui/theme" +) + +// TraceSpan is one timed operation of a request; Parent is "" for the root. +type TraceSpan struct { + ID, Parent string + Name string + Service string + // Start is the offset from the trace start. + Start, Duration time.Duration + Error bool +} + +// TraceWaterfallOptions configure a TraceWaterfall. +type TraceWaterfallOptions struct { + // Height of the span list; zero means 320. + Height float32 +} + +// traceRow is a span in tree order with its depth. +type traceRow struct { + span int + depth int +} + +type traceState struct { + key string + rows []traceRow + total time.Duration + selected string + services map[string]int // palette slot of each service +} + +// TraceWaterfall lays spans out by parent and time; choosing a span shows its detail. +func TraceWaterfall(c *ui.Context, spans []TraceSpan, opts TraceWaterfallOptions) ChoiceResult { + if opts.Height < 0 { + panic("mujica: TraceWaterfall Height is negative") + } + h := opts.Height + if h == 0 { + h = 320 + } + k := Tokens(c) + u := Density(c).Unit() + var r ChoiceResult + r.Element = codePanel(c).Label(msg(c, "trace_waterfall.label")) + st := ui.Local(r.Element, "mujica.trace", func() traceState { return traceState{} }) + traceLayout(st, spans) + r.Element.Children(func() { + if len(st.rows) == 0 { + codeCaption(c, msg(c, "trace_waterfall.empty")).Padding(u * 4) + return + } + codeHeader(c, func() { + codeCaption(c, fmt.Sprintf(msg(c, "trace_waterfall.summary"), len(spans), agentDuration(st.total))).Grow(1) + }) + agentRows(c, len(st.rows), func(i int) { + row := st.rows[i] + s := spans[row.span] + if traceSpanRow(c, s, row.depth, st.total, st.services[s.Service], s.ID == st.selected) { + st.selected, r.changed = s.ID, true + slog.Debug("mujica: trace_waterfall select", "span", s.ID) + } + }).Height(h).FocusGroup(ui.Vertical) + for _, s := range spans { + if s.ID == st.selected { + ui.Column(c).Shrink(0).Gap(u).Padding(u*2, u*3).BorderWidth(theme.BorderWidth, 0, 0, 0).BorderColor(k.Border). + Role(ui.RoleStatus).Children(func() { + ui.Text(c, s.Name).FontWeight(600) + codeCaption(c, fmt.Sprintf(msg(c, "trace_waterfall.detail"), s.Service, agentDuration(s.Start), agentDuration(s.Duration))) + }) + } + } + }) + return r +} + +// traceLayout orders spans depth-first by start, only when the spans change. +func traceLayout(st *traceState, spans []TraceSpan) { + key := fmt.Sprintf("%d", len(spans)) + if len(spans) > 0 { + key += fmt.Sprintf("%p", &spans[0]) + } + if key == st.key { + return + } + st.key, st.rows, st.total, st.services = key, st.rows[:0], 0, map[string]int{} + index := make(map[string]int, len(spans)) + kids := map[string][]int{} + for i, s := range spans { + if s.ID == "" || s.Name == "" || s.Start < 0 || s.Duration < 0 { + panic(fmt.Sprintf("mujica: TraceWaterfall span %d needs an ID, a Name and non-negative times", i)) + } + if _, dup := index[s.ID]; dup { + panic("mujica: TraceWaterfall span IDs must be unique: " + s.ID) + } + index[s.ID] = i + if _, ok := st.services[s.Service]; !ok { + st.services[s.Service] = len(st.services) + } + st.total = max(st.total, s.Start+s.Duration) + } + for i, s := range spans { + if _, ok := index[s.Parent]; s.Parent != "" && !ok { + panic("mujica: TraceWaterfall span " + s.ID + " has an unknown Parent") + } + kids[s.Parent] = append(kids[s.Parent], i) + } + var walk func(parent string, depth int) + walk = func(parent string, depth int) { + list := kids[parent] + slices.SortStableFunc(list, func(a, b int) int { return cmp.Compare(spans[a].Start, spans[b].Start) }) + for _, i := range list { + st.rows = append(st.rows, traceRow{i, depth}) + walk(spans[i].ID, depth+1) + } + } + walk("", 0) + if len(st.rows) != len(spans) { + panic("mujica: TraceWaterfall spans form a cycle") + } +} + +// traceSpanRow shows a span's name indented by depth beside its bar; it reports a choice. +func traceSpanRow(c *ui.Context, s TraceSpan, depth int, total time.Duration, slot int, selected bool) bool { + k := Tokens(c) + d := Density(c) + u := d.Unit() + row := ui.ButtonBase(c).Key(s.ID).Justify(ui.Start).Gap(u*2).MinHeight(d.ControlHeight()-4).Padding(0, u*2). + Label(fmt.Sprintf("%s, %s, %s", s.Name, s.Service, agentDuration(s.Duration))) + internal.Ring(row, k.Focus, theme.ControlRadius) + if selected { + row.Background(k.Selection) + } else if row.Hovered() { + row.Background(k.SurfaceHover) + } + pal := internal.Palette(k) + col := pal[slot%len(pal)] + if s.Error { + col = k.Danger + } + row.Children(func() { + ui.Row(c).Width(220).Shrink(0).Gap(u).Padding(0, 0, 0, float32(depth)*u*4).AlignItems(ui.Center).Children(func() { + if s.Error { + ui.Icon(c, icons.Must("circle-x")).Size(12, 12).TextColor(k.Danger).Label(msg(c, "trace_waterfall.error")) + } + ui.Text(c, s.Name).FontSize(FontSize(c, theme.CaptionSize+1)).SingleLine().Shrink(1) + }) + ui.Box(c).Grow(1).Height(14).Draw(func(p *ui.Painter, r ui.Rect) { + span := max(float64(total), 1) + x0 := r.X + r.W*float32(float64(s.Start)/span) + w := max(r.W*float32(float64(s.Duration)/span), 2) + bar := ui.Rect{X: internal.Snap(p, x0), Y: r.Y, W: w, H: r.H} + if s.Error { // hollow, so errors differ by shape too + p.Fill(bar, col.Alpha(0.25), 2) + p.Stroke(bar, col, 2, 1.5) + return + } + p.Fill(bar, col, 2) + }) + ui.Text(c, agentDuration(s.Duration)).Width(64).TextAlign(ui.End).FontSize(FontSize(c, theme.CaptionSize)). + TextColor(k.TextMuted).FontFeatures("tnum").Shrink(0) + }) + return row.Clicked() +} diff --git a/trace_waterfall_test.go b/trace_waterfall_test.go new file mode 100644 index 0000000..a15ad5a --- /dev/null +++ b/trace_waterfall_test.go @@ -0,0 +1,78 @@ +package mujica + +import ( + "fmt" + "testing" + "time" + + "github.com/egoist/mygo/ui" +) + +var traceSample = []TraceSpan{ + {ID: "db", Parent: "api", Name: "SELECT guests", Service: "postgres", Start: 40 * time.Millisecond, Duration: 90 * time.Millisecond}, + {ID: "root", Name: "GET /ball", Service: "gateway", Duration: 320 * time.Millisecond}, + {ID: "api", Parent: "root", Name: "ListGuests", Service: "api", Start: 20 * time.Millisecond, Duration: 200 * time.Millisecond}, + {ID: "pay", Parent: "root", Name: "Charge", Service: "payments", Start: 230 * time.Millisecond, Duration: 80 * time.Millisecond, Error: true}, +} + +func traceTester(spans []TraceSpan, changes *int) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if TraceWaterfall(c, spans, TraceWaterfallOptions{}).Changed() { + *changes++ + } + }, 700, 460) +} + +func TestTraceWaterfallOrderAndDetail(t *testing.T) { + n := 0 + tt := traceTester(traceSample, &n) + if !tt.HasText("4 spans · 320 ms") { + t.Fatalf("summary: %q", tt.Texts()) + } + root, _ := tt.Find("GET /ball") + api, _ := tt.Find("ListGuests") + db, _ := tt.Find("SELECT guests") + pay, _ := tt.Find("Charge") + if !(root.Y < api.Y && api.Y < db.Y && db.Y < pay.Y && db.X > api.X && api.X > root.X) { + t.Fatalf("tree order: root %v api %v db %v pay %v", root, api, db, pay) + } + _ = tt.Click("Charge") + if n != 1 || !tt.HasText("payments · starts at 230 ms · takes 80 ms") { + t.Fatalf("detail: %q", tt.Texts()) + } + tt.Key(0, ui.KeyUp) + tt.Key(0, ui.KeyEnter) + if !tt.HasText("postgres · starts at 40 ms · takes 90 ms") { + t.Fatalf("keyboard detail: %q", tt.Texts()) + } +} + +func TestTraceWaterfallRulesAndMany(t *testing.T) { + n := 0 + tt := traceTester(nil, &n) + if !tt.HasText("No spans") { + t.Fatal("empty") + } + many := make([]TraceSpan, 100000) + many[0] = TraceSpan{ID: "0", Name: "root", Duration: time.Second} + for i := 1; i < len(many); i++ { + many[i] = TraceSpan{ID: fmt.Sprint(i), Parent: "0", Name: fmt.Sprint("span ", i), Start: time.Duration(i) * time.Microsecond, Duration: time.Microsecond} + } + tt = traceTester(many, &n) + if got := len(tt.Texts()); got > 400 { + t.Fatalf("%d texts for 100000 spans", got) + } + defer func() { + if recover() == nil { + t.Fatal("cycle did not panic") + } + }() + traceTester([]TraceSpan{{ID: "a", Parent: "b", Name: "a"}, {ID: "b", Parent: "a", Name: "b"}}, &n) +} + +func TestTraceWaterfallSnapshot(t *testing.T) { + snapshot(t, "trace_waterfall", 680, 280, func(c *ui.Context) { + ui.Column(c).Padding(16).Children(func() { TraceWaterfall(c, traceSample, TraceWaterfallOptions{Height: 180}) }) + }) +} From 1822101db795b63b4abe64265d03b6197c1a0da2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 5 Oct 2026 14:58:49 -0400 Subject: [PATCH 11/14] wip-devtools --- json_tree.go | 93 ++++++++ json_viewer.go | 212 ++++++++++++++++++ json_viewer_test.go | 119 ++++++++++ messages_devtools.go | 4 + .../snapshots/darwin/json_viewer-dark.png | Bin 0 -> 16370 bytes .../snapshots/darwin/json_viewer-light.png | Bin 0 -> 16084 bytes 6 files changed, 428 insertions(+) create mode 100644 json_tree.go create mode 100644 json_viewer.go create mode 100644 json_viewer_test.go create mode 100644 testdata/snapshots/darwin/json_viewer-dark.png create mode 100644 testdata/snapshots/darwin/json_viewer-light.png diff --git a/json_tree.go b/json_tree.go new file mode 100644 index 0000000..fd570f5 --- /dev/null +++ b/json_tree.go @@ -0,0 +1,93 @@ +package mujica + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "regexp" + "strconv" +) + +// jsonNode is one value of a parsed document, in document order. +type jsonNode struct { + key, path string + // value is a scalar's JSON text, or "{}" / "[]" for containers. + value string + depth int + parent int + // end is the index after this node's subtree; count is its child count. + end, count int +} + +func (n jsonNode) container() bool { return n.value == "{}" || n.value == "[]" } + +func (n jsonNode) label() string { + if n.key == "" { + return n.value + } + return n.key + ": " + n.value +} + +// jsonTree is a document flattened depth-first. +type jsonTree struct{ nodes []jsonNode } + +var jsonIdent = regexp.MustCompile(`^[A-Za-z_$][A-Za-z0-9_$]*$`) + +// parseJSONTree flattens data keeping key order; it fails on invalid or trailing JSON. +func parseJSONTree(data string) (jsonTree, error) { + dec := json.NewDecoder(bytes.NewReader([]byte(data))) + dec.UseNumber() + var t jsonTree + if err := jsonParseValue(dec, &t, "", "$", 0, -1); err != nil { + return jsonTree{}, err + } + if _, err := dec.Token(); err != io.EOF { + return jsonTree{}, fmt.Errorf("unexpected data after the value") + } + return t, nil +} + +func jsonParseValue(dec *json.Decoder, t *jsonTree, key, path string, depth, parent int) error { + tok, err := dec.Token() + if err != nil { + return err + } + at := len(t.nodes) + t.nodes = append(t.nodes, jsonNode{key: key, path: path, depth: depth, parent: parent}) + switch v := tok.(type) { + case json.Delim: + t.nodes[at].value = map[json.Delim]string{'{': "{}", '[': "[]"}[v] + for i := 0; dec.More(); i++ { + childKey, childPath := "", fmt.Sprintf("%s[%d]", path, i) + if v == '{' { + kt, err := dec.Token() + if err != nil { + return err + } + childKey = kt.(string) + childPath = path + "." + childKey + if !jsonIdent.MatchString(childKey) { + childPath = path + "[" + strconv.Quote(childKey) + "]" + } + } + if err := jsonParseValue(dec, t, childKey, childPath, depth+1, at); err != nil { + return err + } + t.nodes[at].count++ + } + if _, err := dec.Token(); err != nil { // the closing delimiter + return err + } + case string: + t.nodes[at].value = strconv.Quote(v) + case json.Number: + t.nodes[at].value = v.String() + case bool: + t.nodes[at].value = strconv.FormatBool(v) + case nil: + t.nodes[at].value = "null" + } + t.nodes[at].end = len(t.nodes) + return nil +} diff --git a/json_viewer.go b/json_viewer.go new file mode 100644 index 0000000..0a847c6 --- /dev/null +++ b/json_viewer.go @@ -0,0 +1,212 @@ +package mujica + +import ( + "fmt" + "log/slog" + "strings" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/internal" + "mujicaui/theme" +) + +// JsonViewerState is a JsonViewer's open nodes, search and choice. +type JsonViewerState struct { + // Query finds keys and values; matches open their ancestors. + Query string + // Current is the chosen match. + Current int + open map[string]bool + tree jsonTree + src string + err error + rows []int // visible node indexes + stale bool // rows need rebuilding + searched string + matches []int + sel int + list ui.ListState +} + +// JsonViewerOptions configure a JsonViewer. +type JsonViewerOptions struct { + // Height of the tree; zero means 360. + Height float32 + // Expanded opens every node at first. + Expanded bool +} + +// JsonViewerResult reports this frame's events. +type JsonViewerResult struct { + Element *ui.Element + // Copied is the path written to the clipboard, else "". + Copied string +} + +// JsonViewer shows JSON as a tree with folding, path copy and search, virtualized. +func JsonViewer(c *ui.Context, data string, s *JsonViewerState, opts JsonViewerOptions) JsonViewerResult { + if s == nil || opts.Height < 0 { + panic("mujica: JsonViewer needs a state and Height >= 0") + } + h := opts.Height + if h == 0 { + h = 360 + } + if s.open == nil || data != s.src { + s.src, s.open, s.stale, s.searched = data, map[string]bool{"$": true}, true, "\x00" + s.tree, s.err = parseJSONTree(data) + if opts.Expanded { + for _, n := range s.tree.nodes { + s.open[n.path] = true + } + } + } + u := Density(c).Unit() + var r JsonViewerResult + r.Element = codePanel(c).Height(h) + r.Element.Children(func() { + if s.err != nil { + ui.Row(c).Padding(u * 3).Gap(u * 2).TextColor(Tokens(c).Danger).Role(ui.RoleStatus).Children(func() { + ui.Icon(c, icons.Must("circle-alert")).Size(16, 16) + ui.Text(c, fmt.Sprintf(msg(c, "json_viewer.invalid"), s.err)) + }) + return + } + jsonViewerSearch(s) + codeHeader(c, func() { + moved := SearchInput(c, &s.Query, SearchInputOptions{Find: true, Matches: len(s.matches), Current: &s.Current}).Moved() + if moved && len(s.matches) > 0 { + s.sel = s.matches[s.Current] + s.stale = true + } + path := "" + if s.sel >= 0 && s.sel < len(s.tree.nodes) { + path = s.tree.nodes[s.sel].path + } + codeMono(c, path).Grow(1).MinWidth(0).SingleLine() + if Button(c, msg(c, "json_viewer.copy"), ButtonOptions{Variant: Ghost, Icon: icons.Must("copy"), Disabled: path == ""}).Clicked() && path != "" { + c.WriteClipboard(path) + r.Copied = path + slog.Debug("mujica: json_viewer copy path", "path", path) + } + }) + jsonViewerRows(s) + at := -1 + for i, n := range s.rows { + if n == s.sel { + at = i + } + } + was := at + s.list.Selected = &at + s.list.Label = func(i int) string { return s.tree.nodes[s.rows[i]].label() } + list := codeRows(c, &s.list, len(s.rows), func(i int) { jsonViewerRow(c, s, s.rows[i]) }) + if at != was && at >= 0 { + s.sel = s.rows[at] + } + if at >= 0 && at < len(s.rows) { + n := s.tree.nodes[s.rows[at]] + if n.container() && (list.Shortcut(0, ui.KeyRight) && !s.open[n.path] || list.Shortcut(0, ui.KeyLeft) && s.open[n.path] || list.Submitted()) { + s.open[n.path] = !s.open[n.path] + s.stale = true + } + } + }) + return r +} + +// jsonViewerSearch finds matches and opens their ancestors when the query changes. +func jsonViewerSearch(s *JsonViewerState) { + if s.searched == s.Query { + return + } + s.searched = s.Query + s.matches = s.matches[:0] + q := strings.ToLower(s.Query) + if q != "" { + for i, n := range s.tree.nodes { + if strings.Contains(strings.ToLower(n.key), q) || strings.Contains(strings.ToLower(n.value), q) { + s.matches = append(s.matches, i) + for p := n.parent; p >= 0; p = s.tree.nodes[p].parent { + s.open[s.tree.nodes[p].path] = true + } + } + } + } + s.Current = max(0, min(s.Current, len(s.matches)-1)) + if len(s.matches) > 0 { + s.sel = s.matches[s.Current] + } + s.stale = true +} + +// jsonViewerRows lists the visible nodes, skipping closed subtrees, when anything changed. +func jsonViewerRows(s *JsonViewerState) { + if !s.stale { + return + } + s.stale = false + s.rows = s.rows[:0] + for i := 0; i < len(s.tree.nodes); { + n := s.tree.nodes[i] + s.rows = append(s.rows, i) + if n.container() && !s.open[n.path] { + i = n.end + continue + } + i++ + } + if i := s.list.Selected; i != nil && *i >= 0 { + for at, n := range s.rows { + if n == s.sel { + s.list.ScrollIntoView(at) + } + } + } +} + +// jsonViewerRow shows one node: fold mark, key, and value or size. +func jsonViewerRow(c *ui.Context, s *JsonViewerState, i int) { + k := Tokens(c) + n := s.tree.nodes[i] + ui.Box(c).Width(float32(n.depth) * 16).Shrink(0) + mark := ui.Box(c).Size(theme.MinHitSize, theme.MinHitSize).Center().Shrink(0) + if n.container() { + name := "chevron-right" + if s.open[n.path] { + name = "chevron-down" + } + mark.Role(ui.RoleDisclosure).Label(n.path).Cursor(ui.CursorPointer) + mark.Children(func() { ui.Icon(c, icons.Must(name)).Size(14, 14).TextColor(k.TextMuted) }) + if mark.Clicked() { + s.open[n.path], s.stale = !s.open[n.path], true + } + } + var spans []ui.Span + if n.key != "" { + for _, sp := range codeMatchSpans(c, n.key, s.Query) { + sp.Color, sp.Weight = k.AccentText, max(sp.Weight, 600) + spans = append(spans, sp) + } + spans = append(spans, ui.Span{Text: ": ", Color: k.TextMuted}) + } + switch { + case n.container() && s.open[n.path]: + spans = append(spans, ui.Span{Text: n.value[:1], Color: k.TextMuted}) + case n.container(): + spans = append(spans, ui.Span{Text: fmt.Sprintf(msg(c, "json_viewer.items"), n.value[:1], n.count, n.value[1:]), Color: k.TextMuted}) + default: + tok, _ := internal.Highlight("json", n.value, false) + col := k.Text + if len(tok) > 0 { + col = codeTokenColor(k, tok[0].Kind) + } + for _, sp := range codeMatchSpans(c, n.value, s.Query) { + sp.Color = col + spans = append(spans, sp) + } + } + ui.RichText(c, spans...).Font(theme.MonoFont).FontSize(FontSize(c, codeSize)).SingleLine().Grow(1).MinWidth(0) +} diff --git a/json_viewer_test.go b/json_viewer_test.go new file mode 100644 index 0000000..8ba2c81 --- /dev/null +++ b/json_viewer_test.go @@ -0,0 +1,119 @@ +package mujica + +import ( + "fmt" + "strings" + "testing" + + "github.com/egoist/mygo/ui" +) + +const jsonSample = `{"ball":{"hall":"east wing","guests":[{"name":"Sakiko","seat":1},{"name":"Uika","seat":2}],"wine":null},"open":true,"two words":3.5}` + +func jsonViewerTester(data *string, s *JsonViewerState, opts JsonViewerOptions, copied *[]string) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if r := JsonViewer(c, *data, s, opts); r.Copied != "" { + *copied = append(*copied, r.Copied) + } + }, 640, 460) +} + +func TestJsonTreePaths(t *testing.T) { + tr, err := parseJSONTree(jsonSample) + if err != nil { + t.Fatal(err) + } + var paths []string + for _, n := range tr.nodes { + paths = append(paths, n.path) + } + want := `$ $.ball $.ball.hall $.ball.guests $.ball.guests[0] $.ball.guests[0].name $.ball.guests[0].seat $.ball.guests[1] $.ball.guests[1].name $.ball.guests[1].seat $.ball.wine $.open $["two words"]` + if strings.Join(paths, " ") != want { + t.Fatalf("paths %v", paths) + } + if tr.nodes[3].count != 2 || tr.nodes[3].end != 10 { + t.Fatalf("guests node %+v", tr.nodes[3]) + } + if _, err := parseJSONTree(`{"a":1} x`); err == nil { + t.Fatal("trailing data accepted") + } +} + +func TestJsonViewerFoldKeyboardCopy(t *testing.T) { + data := jsonSample + var s JsonViewerState + var copied []string + tt := jsonViewerTester(&data, &s, JsonViewerOptions{}, &copied) + if !tt.HasText("ball: { 3 items }") || tt.HasText(`hall: "east wing"`) { + t.Fatalf("closed: %q", tt.Texts()) + } + _ = tt.Click("$.ball") // its fold mark + if !tt.HasText(`hall: "east wing"`) || !tt.HasText("guests: [ 2 items ]") { + t.Fatalf("open: %q", tt.Texts()) + } + _ = tt.Click(`hall: "east wing"`) + _ = tt.Click("Copy path") + if len(copied) != 1 || copied[0] != "$.ball.hall" || tt.Clipboard() != "$.ball.hall" { + t.Fatalf("copy: %v", copied) + } + _ = tt.Click(`hall: "east wing"`) // back on the list + tt.Key(0, ui.KeyDown) // guests + tt.Key(0, ui.KeyRight) + if !tt.HasText("[0]: { 2 items }") && !tt.HasText("{ 2 items }") { + t.Fatalf("Right did not open guests: %q", tt.Texts()) + } + tt.Key(0, ui.KeyLeft) + if tt.HasText("{ 2 items }") { + t.Fatal("Left did not close guests") + } +} + +func TestJsonViewerSearchOpensMatches(t *testing.T) { + data := jsonSample + var s JsonViewerState + var copied []string + tt := jsonViewerTester(&data, &s, JsonViewerOptions{}, &copied) + _ = tt.Click("Find") + tt.Type("uika") + if !tt.HasText("1 of 1") || !tt.HasText("$.ball.guests[1].name") { + t.Fatalf("search: %q", tt.Texts()) + } + data = `{"broken": }` + tt.Frame() + found := false + for _, x := range tt.Texts() { + found = found || strings.HasPrefix(x, "Invalid JSON") + } + if !found { + t.Fatalf("parse error: %q", tt.Texts()) + } +} + +func TestJsonViewerLargeDocumentVirtualized(t *testing.T) { + var b strings.Builder + b.WriteString("[") + for i := range 100000 { + if i > 0 { + b.WriteString(",") + } + fmt.Fprintf(&b, "%d", i) + } + b.WriteString("]") + data := b.String() + var s JsonViewerState + var copied []string + tt := jsonViewerTester(&data, &s, JsonViewerOptions{Expanded: true}, &copied) + if n := len(tt.Texts()); n > 300 { + t.Fatalf("%d texts for 100000 items", n) + } +} + +func TestJsonViewerSnapshot(t *testing.T) { + snapshot(t, "json_viewer", 600, 360, func(c *ui.Context) { + var s JsonViewerState + ui.Column(c).Padding(16).Children(func() { + JsonViewer(c, jsonSample, &s, JsonViewerOptions{Expanded: true, Height: 320}) + }) + }) +} diff --git a/messages_devtools.go b/messages_devtools.go index ebfd5fb..abe4d0e 100644 --- a/messages_devtools.go +++ b/messages_devtools.go @@ -78,6 +78,10 @@ func init() { "trace_waterfall.detail": {"%s · starts at %s · takes %s", "%s · 起于 %s · 耗时 %s"}, "trace_waterfall.error": {"Error", "错误"}, + "json_viewer.invalid": {"Invalid JSON: %v", "JSON 无效:%v"}, + "json_viewer.copy": {"Copy path", "复制路径"}, + "json_viewer.items": {"%s %d items %s", "%s %d 项 %s"}, + "alert_list.empty": {"No alerts", "没有告警"}, }) } diff --git a/testdata/snapshots/darwin/json_viewer-dark.png b/testdata/snapshots/darwin/json_viewer-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..29937571e0324476caeabe9c3500942b8ca785fa GIT binary patch literal 16370 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 h58)+B8c5pbV9! z{%!M%7a}RL5@PCKQjeAmY}Cx~ew|I(jz{L@9aEKvYxc&DxE+49A!nztVxzHI^D`r8 z2l{coWFKW~Q6{k4>SZ(W^W>C`1>;3$otZExPRMizG$7QxHyvuy=?XHLJ|VK8Kc?q4 zYYDNau|CrG8im1Dj)bUNt$j`}Q5n_p{oZ0?Mj0|9U)aNsDHTgQGBg_-j-zCzl!H>S;dF#5JlQr9$mW#yt5JjaK$al~ zoxmXu{rL4Nlms&ukj{?LXr#D*m%nt}et$S2{WAx)n!385-8D-Ly;h56y?Rgp-;{4K z(9lUhU&Os!M+`o_mq=z#Q-leyyX8VKFkTr~iYJcE{Nx}O6T_Ae6Q)aSjQ_~iptZy- zP4Vdk`2A03fIYDPFHT16CGMs#3g=b|+Nfam1M3caM2crbWbnZMQCfbl9S^JDa=3Ky zA%R|}$G_>uQn8~h?-3XOCUH~_Q@R9(4J;q%edGywiDC5)sG$Kzs~AW_ldV-Qz{t=d zlHny8{v|3{X6U)eO>H|L4oXOeEpwTx`(>Md0L(w&0&)F!{~k1*L{d{ad;b`sH5Rwe2pP zc|y4X_2>6L`2l@WQmZRd29PV4JKlNXh!oC!`L*dFh;PMBxUk@3IljU?j3o9$Uhbmr zrj)GZK4ks$3=$!Hxko9Lqw>Al<=FZg0Dt2_DdGYJx(o_3Kvg@vmvyFRYilUc7+#T zQr*>q@hz)Fe+xny@<+K}`0^u?BLcu=RSZ+`fJ_ek@uIJ~k|jBH5t?4aGevW_hedPN z<)s>O*0#PDGXmD73mot`r5xR%bQ}^A_(%W%9XRi+EP($e0gTQDA1adwxj66Rhw$G; zP^RvBUtFezZ#f22iXu^N9A{$jbdGOMr6Ob2sa*A<9iw_V(| z=eroXKq@~L^{}`e#95cF;q$yLIoVqHrnclri9>Pp%_S0W;p7~ypg^c65!X4|L4G^E zzmeT}tv+#id0EmZewxlyMJjarbUJZmWM|BVzsPIvNGi&5?Dj#-M?^0}*!qNskOZBa z>TL*65f+smFO`L{5<|Nl`Rcva1>d%hn+*l5u~K5{{~nX5(vgE-58ZGL%{f$Z9sviS}oSHQgqdpKloP?VDXBCU^1J^-*yx zy?*cJv?kevJ7wn=G?z?`?dSAq;x*+1`HFQy|B%$XcC??k>cdb=@3_+yrEJ}|}My*Y0a|YeiZM%&LbtWHJ(NxcTs4PoAf>q@=5zrc=k{&?tO|9y&F?|0;_5!XT(qYL=wz zndawWND7wvqy?}j^{lPk6Lz+F_2-uJ%(yF^&(d}_VaMp?JGV-rRVizZn-*&`!hL4pSZGUvSqZX*Hn))j|4d>T1fyd7F|U@Sw;3dc*HQEnGRHYe1$s=q8q)g-JElAIml>I`*+4^TmKM z8y;ynlviZ(>*1b?2xmSeSX+JvA{{AQT$DXwQIGX@C5`t+VeAY*?kqP%S_k=6;E&MZ zXJ9Snp3obrdwoR(R=)7mGtZ(^Q)$}(j+7URJU(ik-MM+)kTeiDGecCMp>d><1l-xB z=+>lx0b8#`FezsX?_EI6eI_c<9^%s$Y4lX1rUAr7Qses3>Eq?hc zA|8|IZ&x3|7#iZ6j|t7xz^JFKO08Eu+uF%zw7eq?X$?L}&o!Yw2#`DSt}KOP5u%cRce|er8R+S8CB}O-Gvyb`r0`;=ExmN}{WgihW7hJ5?RUh0YU6ADF?M~W?Zcvw!NI}h zX1H|Ggyl?B6cip?T`eP{39#!@FBWfl*hmm{9R#5cB74#{BrG zTnTmQsoYJbY|BX;N;$K|%X}e?HfGE@axf97m4m`WmOcp|XP0jVE93m_h^So76!kfh z)bD(V-<*wCa#Gi)!#d&5aiNf3@NuEe%dfLmHuNIV4)%Vop|OJ=vE zF0G;wHxl`Lqms>1zD3XQMU|IVzywA*IcLO-AGlnsgCb@9QN+5u|)G>|CyaW;X8SAJ+7kp_85E-wkR%LOY1Dcv-YOSBlDG zxQ;K`ECxf$LSMqYE?I8bw{eiM_^5nZIE4xddY#ITEP{;`kmdWD0^F-`+FRgG(_|xz zY+d|g%6FWb8(&^Z`=j!Z4$+GjEi~|AM~gQx8LNS7Ys&LvdFh4r%-FY=^&E`{$uOXCUR^OeaBy1G*Crb=c zur;wn%0;T}3i{4Ha1bQtt}|-V_Qc3ddBqJ(x;jSDCYsR~TlRaWHCjjKTEp0B(DaKZ zM%_CpLLtIm4r9=XclV*(kJbBE!m zIp*8wfP98dKy7lhe4Y_)E4+~iA58wk}IX3TO4`VtY10Ei4?1ydTNf%Qti7HQXxv0#_i zA)`;FL5NqxDaxzC>J;=NqjTImrh68f`dGC%8Y2bT;F3jhPeV?37>|l8vlw+OQhU?x zZ7Eik4|{<+n(JM!<@t1}kGQ#f34QU$$}8-v{?{^9`7Nsa^pT#9#BpWUFY)(7*m3I= zzm)gQ0Tv^Ci;&%39qTl{e(@p`dHf?s{Oc3Yh!zJX*8*8O&Wc)oBSK0};nQTrUC~DL zPf|2ljf|V>;%tvM&0oR&w^y@EEA98R*}doGMof}0v@w19zag!X^Da__}0D+Y=fGN zI&_+DB?Y=74o>* z&1YW(VKKzz7mVSHbQ04(Gt%k90&D~XHHPEw@JP`kg23a_fPaeLM)!c1&n#!oy?h?s ze~r^lC_`t&C1yTt@0!BD8`47tp!5r6fJW`)y(fIkYvV~Wd4eO>nAa~e&Bevw;|9mg zwoQ9CV+ADXsx#qfWPZUTCX!9`(aJmzEuSn(U6r+@sOQ1zgfeyp;m_v>O|T2QT2!Fa z1Ovn9{wXY!EHIBzgk5F87=I=Z_5X}y{~KzUA?t)yyR()mNEqYwDRE#;(!>~&6%tNu z`GhA1Pvy9P4)QK-J7xK+@X5wposR{M}OUY2K-t@p--W7sZJ;aNuJBFVPL-2+{g?W&`sr z-67w-z|to#|Am%lqOA{_GKp~OJ(@-a2o4-u)U>8DisKDzDSFk2+i4F#p^?fz5&uRX z4D#02VN%*;W3rPB1V>rIOTJ_eT{NV6!HujtV zVHoj@7BK-u*=XnyWwf*M$O;H^Sk_(sIYpl;mv=7i!bg-yHCMIf>G8uk2+X(!En+9= zawaw`rcWd~Z7Ib~lrj_&8ZMTB%x$Lqn`amzSxBkY>6Y06DmUdN3zw z^Zer5IrD=gvc%SuJ^#WYGgzkFCzdc9()*QI6zery$LOe<^F`(5?sRENeX)u%V1nd$ zkAIQS_d+0HC{1XBz8;Yr%*X1r%E`P=dA@JN1&k5CsQ9jc6z{qI;%4Q?QoP zn2~8HZ9W{n9*tiz`I}u8shsapr4dqcxAO&=jYYH-LPbRcfhs+s&*;cs`nW~zB*3uf zo2*f$dEkyytgWki4JXz(QmCRB?qn6KmXk`bJ-zo$DO|wQHSI)X*3sJzdbjBA*eLyP z#*mdKwb#9U^H7eepP=OT@65!V8;Bd$^QqDKAI1K!;$iC<80Wa|D;uMFOCDi@_Kxmz zdP~G_l}gDj8A2aro+Uml*HWZP2_Q^{>w$eYlwr+%H;KF-aM>xaA3(BxMUSfR^3LYM zAP1_PBRWl&@!SC8O`r8s@ju4-fBFI~@RU~YhG5YYr_b>bdy(Bj&Xsvm;gbU#5j>*t z;44Hma-{W7zmVI0tugoR1+r?s#iL;&1OUO{_hg&|%5Uet`$Yi&V&vpu6>mQVTkoBJCQ z_U<0DVXyPTo-k8Fz&DIP$w7vETD`Vy+ANLDkSCkga$$?zO|K{ELScp*Zoei>7)U}ZbBCP7^Y7$Rjgh;M44E813sha*|1L0|3#k@Z z4#pp2u_!8H9v;4WXbQmSF(CMf7}Kpww!jrvP(or}@psmH#5uM(U2Hnuf* z|LDjCroQvIu792@$hBF;Jvd9JmtzxN#eHwIKu}9d{khcf2>Q|M3th`r%a7V#W2FzV zFjBeRJQ~q@5Xd5P_ApG8K8@2e|ZX2ab=iGwhc0c`%dyn&~F8wN=ey=tY zKj8(Jf?S1;8S5v-3_g#8+j`6Wef*2vA(DTA(^=q()lOzYrvME8I}ez0N1HPX`VHw9 zObV(DjO?AxNONS7;`WW%K77B>xAq)^+m+o`)dP_#Ri{aLkt6A??Q2~z(vd8BXMQ$t z|BBxq0A08{cFPoCnF`%E_ljFqE?Q5HmRT*j08D$HqEIKBO%}Pyr+ITE1rsbc)-O6` z;p{jkP8u4tO0sd`!R&Y_*SX#JmYj@3zVX8U!e=`P$%z-+jU0IU!E8d0@|((+hxuG+Ts=EAvx+{AVT z4PidJ&@rvuyy2v~UzgLFqT}_N-nDiq^^M$@vccIUzP#)XC!r>EXAxc&4#*(+Szf9jmr?7m|8|}R{9`?iV|O31aB3S1Yuz>l&;_$o zt$_4Ry0}YrjNioOY#4c5px$*015YrWB7b;+mngSLj8}z$q1kkyO|Jjj!tuZK{l6O4 z|9AJZf(r08UscR3D!SnN3WNB+5n3_9{}mt^@B%>$MIwlu$d-$7BHl4&dLUXzoTeu? zC!Yj~KYAk?&59*pAMbVU`j5?MzDxijpvoTr2+gdz+G1{L9`Qhj3gG-?WBIc4(3;wG z;|>9p3#DK1x5Hp>1b{B!J-`HJHl=rwU}rp!8Svn2kpPF84B#iUg*Sz@6_wkn03!R+ z2)Rsn{ERTM0CKXBRX?ot1Oh0dnD`pv;+sovD0IvM6rWzp;0?qPy1q=Ov}|PEF;(o{ z=`Lp)-e!%&m$nvGbbaE^liKF)X3pV*=xCwKHTtcHNe4$@5R?DGLSzFaHeXp;ru_W4 z4DwB+|IK&@l+uMk!9Z-^f7&am)(9e?mzbjCbQS|zv^*v^hu+8S$|3J*K(HUi*8i}BEi>&nd8jqudNVL~71#iTQ zKa62-RS*fz4!O@CeJremmaBC`i34U5bQfYL&Ww7gKnj5QCu4AU0M1i-#d{RfCPnjo%JAVN6J zePzJzSBvZv%{>AJkS~4p2S$n;yPh3Q%1vhth&Iwjr>4^*lk$lHW$2^!>n;ww_>PL< z7{d602tkU|mNCW{D;Dgug^Feq?VT^v5gR#$COd6A!dK`zuv?bhj~t+uzIBfX6P<|7 zFA)W`B<7IHuD8Y&26_h})-S<>U4ft6uc1@yEAhEj9TE4)^q+xF2iuQ#pQJOe5RQG>YL!Xkkcb)dxOGcKt6NmO|eeu3az= zm95zP{8`-S-5O8+9r&4quaoujS&=K`v9@g=cgXn?^*pBOxcO3zrgt*jSLgnF>r^0E zTMQeK+$(}6z3E4z!@+P~^*f@cGkMSyGjZQ!!uYd)Nc?H7>sZGC;+CM1&^w7$18$hQ zw~)eAQLK)hA|g8K{Vk{y!!@jmjX&?vrMJO-caql=SB|kbl7dn~rKYkvuhxs2{gKmz zlCleJ>5DF967^loQ{QWpxv|klc8AFotRPggw;Z-^{WbFf5w=r5GhAg2u`*B{ms^p(llb>R$uwt$x*C zDye5S1!Fel%G7lSw?D*Ly-L&buypx&JaRu%si%{kB4l;cnVAGDD>?291_k7h#Q zK+Dfq6vyVfD_dE|Q8*%=0HzoM!t#jL2W!I5H2IKr5lE}nDqTrYa75%Xh>QJk3^JK= zFOJ>Q{BF5#vFAx%ETUCG)Tn;lE5b((a8a;ZFrI=Q8v&{0(u$qYR6rfSX)vqRxTNVI zA-@JxN(T!Ak1$2&9A(T_@ ze1FsQ<_0l~-P*o*rYyq01P7O6DuY|^S-#LX*Zi`x9gy$4mBk)mZc*!Q&XZXrR z8`7*dLu_KazYJ4UIe-^_A#D zm}*SWS)IPMTq5Dbn&QXucVhgohXwE>yFton(U2V@rPCTj5_+N?XWa4)r3)_6wEo1B z6F{5crdcpkt2f|_Xq6&W`XgzoxOt+oM5gba6L3r_8l zCW|lSm&bl~^mXyST>1~sC_n%LgHtv?^7eIHBB|vh67)>d=9k?h6GE)t?%U{FC+;2HGXVTCvN{qu%A%Vbj67qvVqNE(}-KW zr9l-(Ct0`+@l>O$_?yf$D_uPzC5vZhKgZs{j6JP&xmuU%^R?~On9;=fK3 zbWSmDP)8}Ju+_QqK@YikJZa=x&rli_-W`UW0{acf`g7T@uTfFwgJqba=s=)Gj=y6T z@I9X>nJKYiuiZP@?(R*;+D7>a$XH!{o#&S?KjB`zZO<5v`!_Y-y)ZR9I0Nu~+-8Kc|9{EZwT5y)@p6w0>Ron>ai;5|jdth&=rs!-#``UI2TKHIy} z$sdM-O6xKc`(5z`w^j@#xGtxixOW?%vj%Uv;LClSkK3@5I;#sRw;v;&ZSo5w)KjlH zqtrLnn{TJE=3~O$L1YxgJ;}B18B^=O-N}aJvercCBb3%|;r}j}Za1-jS!~p<=B{QM zjy^91qcUv}~ zot&bvlk@UP+x6#RxP`L3`rbojazlMS9t7&hhu3Roj@R44*BkmAK9D&(=SA;Bc|>ci z^mGsGeH_Q1{A%()G&3{LD3xxUz|7f~?5h?7SC*T|uip@c&_PtR%njh&oj17RpyIWU zS9@Ulw<-yW<;~|e=J8@7K})84=ro*u^ogCBL=DksfL{>`hK$cU=hynF>Pj|YYmYyBilkB@07Tz_-8KO0gK z0Wc^HpDWo0ZfB`BB*)q9+pqInr44eUw3?OQD<1TRa|yxfS@zupwn(8NTj?oeYLPc8 z42^RC#0du>upRQ0vp%D37|76Sbjxt%uw=?|<5RGOyjYv^d!h8k!!wyk5>X51Cvhqa zBnb}7wS-Qr2;w!LK8x6nmm%h8_=|#iCl=+z!_D4g?n6e34!j28t^(#FhO57~Tm-m> z#2a4y<1)8IU{SRf_FfuMGMkmN&u02 z=4G|*8yd?lXOy4aJ)&2;)B77aE2xSa`l{y?^Ia7@Q0GB088_UiSqCC4_Y|1KFpKKO0^Tb( z%t+CD9Jacb7rzSwcNb4HN9?@n`geP(QLnSiyQ?UYVi~_9A3B`FKPE+H7(a9ksG$=O zgve~e?zU{{_3_0pOjjheTq)V8QV-W>_J&#s(qj5sTNfCu?RV0zBlX$G=S?^+yKR+- z)P4lhsE=QjraL8Ri*zi>QN=kVU~M5F6cCGc#s6qH3qYC$R7wSN14}SJg`dtCQKlAq z?>czuN!kY0i8yFeeqUU(S zn?)6z4GoO97#}#qV5Y`=tFA`I(#U1upQ9a3z=OR~Q8;NQC1b`cHivz*7V<+{zgcS& zoUvOkwCfImu0MeEpA)oEj)+CEZgeg#PLTKYxvOg$tx~Fh7fp#60Epp-0VZ1_#b9aE zxouZ&aB3Az7z#+UQs*ur&KhLZFus zLsE56!hts~#h{Vp5aQ$F;b^ioPR=)>%<1wMmyHJdYB>AY*=$iDss8Z8&Rtt!0Ceh- zf8T6q{w*k~wNT|C*KyaV>R!nkQCyP77&q8iJeOxahE8gU*xXN;#S63tuK-#xJt>Fx z2#rmg)s&3RPD&T`vQEwRC6$aSD;wz_p?&5%6I+`hLC4SA3JeU4X)!;5-)2eBJ^v)h zg9VvW(~{HElFL*&=t31UMBN)<3w_($17~LyF#u{{h%!YifV#7UrqdR=e1#}Ddrw(h z{I`b3`JK0k^Hk;aCIojikQnf{l;#5+v;a%|jw0>r z@&1!l-di^^c=A7+oe%;d>z>zqbt~<>k1jj1zYCuwTBP!4f_7CsiFhJ#Enp!y6b66d%^9x>-YjD z9Q^|fkI})P&Dv6-DpaXkYiJS_>atwqisO1b8xwzEb*wuNq&GX1E&UXw$uQ&jE%bxR zw<{?LO$jjoTd2Aii`e%qA6Ylxt)63Tu&a5IPka0D?F#H^)yuIrDH2Bc#}X9y{+5e; zn5n;2M@>V;i&5M$q*mv^Q{(R2rAf0A5q*Q<@B&HFu;0>{?^6FLiTvQ*W$js|W#c<- zIQ%anYbh0zdT}LLclwO1t?K&ygy98Fql_9?CHVhL$sa)&f%cIiX$8Fvh8vY7c;jo5-&D{b)$ zSs|~DML0nX-K)tx?w;+u5`3{A?-+q@U&-2WYCo9qq?kGJTU$8RyqkAe z+7b8qga&yS`sqlqjs-=Il{;P`;)_iqf5s6#UaVK6W*L@-q_dTefq5CRwL+&@Yi! zJF*_N4>Jmucy>}o{DqSBEz*qcNZ^y;`%)prFZSvWwej*2!@bZAIU?hjXkAk>mppXxOW0ZeL8r3fR-z?A#tIKNsbXVs=C zk{K&`PSh^y>-6Udc~osJpUFLXgu+U@&#F=YZ<%aQ^u&{4Oel4H)Te_EdCAdt5QNvz zc~Pk8dD77QQABS^c%*PVh{g2t*H(2Yi7vzA_VunzoNAb%p_(n*dC6Qke?h;_wL$lh zBUUL=`sjde?ZX>Vi18tue=0GN=n_T*Gtci&{(ltCFkHg#fQ)0ztlA|{_l%koP6}1c zCDq*g{ga~Z>ng{NZ*AnHn9>J}gG=2x0ezD}tf#aTl}n?vd;WsFjJ1RJm2$g=(7VKZ zMOg{SzOkn310IJWQp`BUGP-D`{eziLjl`Jzd;&fMxX<4r^qTpq-JzNr{_VI(@TiO) zl-h&{EyA^O&DV>nSoNe&jat$-jhnRXE6t>sBR44=tM;Mkywldxpk}XJgEqD$>-Urw zrV$p(r_DWC#|DN~T$lq?s!0;}bi*9y&TXRDq+6n6eO`$;)io{1Pfb_J?ANvw2TQXf z=JPG7#ggK}%;jjeQ|dAEri|EWkN47*{qe$Ze|z3K=bNmAplx)D0yH~Qo(s>`MANxy zmI6r#-4HYOLg;rP)+oI|H3=#U?f7a;06az1)h~`6&eMYh!DNB$-ulnnqc{Ngs~K)h zKuG_5Pe5zw-ESVDq+lCJ$`&>fypOol9V)(tzG2hdc=~qA9I+uLGmhN3Z(|d^qr1<5 z#+xMhLe=mVdzjpOX;H3o!FL)gYYUN8+8$>jt#Xy>*F{YRo7*cn>pzH0&qt4!iVmz! zK-3h-zu)SyA4U|a$cKNj!v15?56aJ%BGsTt7bU9ekfPJ}cA7g6lT|!ktYk!wd{2iV zOT8TE@n)N-?Z)wB@|!^cV}PV&=qtC`jh{8i{ZE3=-G=lpUu{#zm|I>pr3NZwMLoE; z98F%IhVFr~4&StIm@Q*_j+Hj70mep`fuAT9BKwR_hY^rzN447_1<`{k;V}LRcP0kr zN+L{4mj%KZ9L4Jhg%dH1tsXQoZxQmE$JDR9MfQwx&e+nFpRCGPZJdsMyByQrTs3;q zFjBfHIeKai$NriTry;0m>IJdXTT#rFEI06ES7rMxk5~0mO}0ccGP>Kb6w(!!6^ARV z*mpUS5I1u@LdP<>_gr6tG*wpSA8FbJU49Iv66*#GqtF#HEjm$Uok7ev_E9|3Rz9|B znUAm9n-^CMaAI)4PnF6eXX>I5r54{F|5Jb zl-?|}0Ek9cy!=p~6dh6Z*3;%{cP~Pq8>rRSixu6(pNOarv#)98aQXD?$=kS-mQhtx z3M(q-P!3W8Rj0B{Ws4m!8)adki5XZ~Nq4n(#8pVecXimkEQcDedC)d?X*uHx4l*gF zYzlf@osjl=Kh~Go5x01G@;s@=TTK;RRTWnI*Gr&X9qmp`fFS$R+>Uxo?}GCKe1Q^j zG-ICbx#)*>g=3$GGp@HM6UDuD?^+*bGuP)B1~pHhxp47vir>H%;1%y{qJMed^hd@Eu_E19Kg&XWoHXb-b#z zM{!|NPZwJ-WLok3$1cnWrv@d89}Nj8Mt8f!_~(qDM~6}(>$#6j?Eg5&h5r-t{r_P$ z{~t!`?TGxP-sgE2 z=aWc*JM78O`=oyJ=w*gwQ9<5a(Kr`>11vRSsvz{HpOFXnPwtGUz{Is3m5(6N18u|- zEh#1M49F_UJ<$0~ZR|U}{%1E2>HD9S=(ngaw+6!JM6jiyS1PO64|VmJ{^98KUShiJ zB8qle#ky^>!g*rw+~1=pTLOic?=#dTY}p8qWoSMBe1yTw%ekDJC!h}{*obY2;3F20 zOMulJRQc$l7&URU$jGx3U}>*&1m(_)(-YO6R`-k~?F$v-sh42vy7NN^XoG z`ms@6ohwtF@3lfl;IYaXWk!ln8%Mpx<}kuaAx08%LfY>(dxy?q6%*;5()noECJ#Ay z=7bdg-6C^UJqCpkjQ|Hr-Ayq0ITpBx;e%XQHeFHn;JksC$?V|hO9S0^(?JbcRha*l zSAGKyi><+m1(v#B#OQs5C4Cks;xeg3+T-Eer2!qOZck@Usnh8K^3MF8|vvWpZ! zs#{rhpj{X_@RK2QzC6aepbiNMExzENy$Z*Ooom4)SaUuG?sY{R%^$S}i-vRa)avu_ zy-}pX_>bm&ur#R#eD{QfLZhP^e$O6<+}xDkAS3FZpD&)rrC5?g3bpEZSSIyt?l0Fz z5@BEpQ=EOT=n|#W7d_sLH7++Q;WLMjSe>%NyFd-vK2E8R{o?Hlowdm#N3Bg5-NJ#jaSy4e`aIob-;7bWw(Zq>SNCviPDTD#_&$>BV;6$^sZef8gD z7y5916L7UpTmD7r_KOf@ix1vN+xca@yylQ-$JD;7E#2PJC!D{ISHdxT@qKe6c;dbU zX;>9zxe@%=rkMZX(~JM_HGG`IUbHG#3F%w#wmqu_~uaMo#(!POKpgKaqOP^A7jjlk~@IlwQdYfA)zHFk5A3 zr;Iv9Vui)e4p{_bWXFlb_lOp2H02#Tu52{ZJmKLY?bVm*i=#@T{0M)c0O<(pv&|zg zqyRnlf+?tIr!E^mmHj?%Pdlx$SN%z07J&kC3^-~IwGDS6F2hV zDjU1cZjL_dK>_68p1Xj?qSK)m1t7 z=^1T#t`KrE^Ew1Qx9Cqo47zGvX@lPWwwb&yIKFKxB$~as5%m?~hu%*aQOBu%C=}JU zT$oy%6XkR9`B;JBmG(_pLSoDQgHl*K++Py^&jXkIJJ9C{1HAhBMX}#(vD Date: Mon, 5 Oct 2026 15:00:02 -0400 Subject: [PATCH 12/14] wip-devtools --- json_editor.go | 146 ++++++++++++++++++ json_editor_test.go | 75 +++++++++ messages_devtools.go | 5 + .../snapshots/darwin/json_editor-dark.png | Bin 0 -> 17115 bytes .../snapshots/darwin/json_editor-light.png | Bin 0 -> 16593 bytes 5 files changed, 226 insertions(+) create mode 100644 json_editor.go create mode 100644 json_editor_test.go create mode 100644 testdata/snapshots/darwin/json_editor-dark.png create mode 100644 testdata/snapshots/darwin/json_editor-light.png diff --git a/json_editor.go b/json_editor.go new file mode 100644 index 0000000..eb66d78 --- /dev/null +++ b/json_editor.go @@ -0,0 +1,146 @@ +package mujica + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "log/slog" + "strings" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/internal" + "mujicaui/theme" +) + +// JsonEditorOptions configure a JsonEditor. +type JsonEditorOptions struct { + // Label names the editor; empty uses "JSON". + Label string + // MinHeight and MaxHeight bound the text; zero means 160 and 400. + MinHeight, MaxHeight float32 + Disabled bool +} + +// JsonEditorResult reports this frame's events and the parse state. +type JsonEditorResult struct { + Element *ui.Element + Changed bool + // Valid reports *text parses as one JSON value. + Valid bool + // Line and Column locate a parse error, 1-based; 0 when valid. + Line, Column int +} + +type jsonEditorCheck struct { + src string + err string + line, column int +} + +// JsonEditor edits JSON text, formats it and points at the line and column of a parse error. +func JsonEditor(c *ui.Context, text *string, opts JsonEditorOptions) JsonEditorResult { + if text == nil || opts.MinHeight < 0 || opts.MaxHeight < 0 { + panic("mujica: JsonEditor needs text and non-negative heights") + } + label := opts.Label + if label == "" { + label = msg(c, "json_editor.label") + } + minH, maxH := opts.MinHeight, opts.MaxHeight + if minH == 0 { + minH = 160 + } + if maxH == 0 { + maxH = 400 + } + k := Tokens(c) + u := Density(c).Unit() + var r JsonEditorResult + r.Element = ui.Column(c).Shrink(0).Gap(u * 2).Disabled(opts.Disabled) + chk := ui.Local(r.Element, "mujica.jsoneditor", func() jsonEditorCheck { return jsonEditorCheck{src: "\x00"} }) + locked := r.Element.IsDisabled() + r.Element.Children(func() { + jsonEditorValidate(chk, *text) + ui.Row(c).Gap(u * 2).AlignItems(ui.Center).Children(func() { + ui.Text(c, label).FontWeight(600).Grow(1) + if Button(c, msg(c, "json_editor.format"), ButtonOptions{Variant: Secondary, Disabled: locked || chk.err != ""}).Clicked() && !locked { + var b bytes.Buffer + if json.Indent(&b, []byte(*text), "", " ") == nil && b.String() != *text { + *text, r.Changed = b.String(), true + slog.Debug("mujica: json_editor format") + } + } + }) + in := TextArea(c, text, TextAreaOptions{Label: label, MinHeight: minH, MaxHeight: maxH, Disabled: locked, Error: chk.err}) + in.Input.Font(theme.MonoFont).FontSize(FontSize(c, codeSize)) + r.Changed = r.Changed || in.Changed() + jsonEditorValidate(chk, *text) + if chk.err != "" { + lines := strings.Split(*text, "\n") + if chk.line >= 1 && chk.line <= len(lines) { + jsonEditorExcerpt(c, lines[chk.line-1], chk.line, chk.column) + } + return + } + ui.Row(c).Gap(u).AlignItems(ui.Center).Role(ui.RoleStatus).Children(func() { + ui.Icon(c, icons.Must("circle-check")).Size(14, 14).TextColor(k.Success) + ui.Text(c, msg(c, "json_editor.valid")).TextColor(k.Success).FontSize(FontSize(c, theme.CaptionSize+1)) + }) + }) + r.Valid, r.Line, r.Column = chk.err == "", chk.line, chk.column + return r +} + +// jsonEditorValidate parses text once per change and locates any error. +func jsonEditorValidate(chk *jsonEditorCheck, text string) { + if chk.src == text { + return + } + chk.src, chk.err, chk.line, chk.column = text, "", 0, 0 + dec := json.NewDecoder(strings.NewReader(text)) + var v any + err := dec.Decode(&v) + offset := dec.InputOffset() + if err == nil && dec.More() { + err = errors.New("unexpected data after the value") + } + if err == nil { + return + } + var syn *json.SyntaxError + var typ *json.UnmarshalTypeError + switch { + case errors.As(err, &syn): + offset = syn.Offset + case errors.As(err, &typ): + offset = typ.Offset + case strings.TrimSpace(text) == "": + err, offset = errors.New("empty document"), 0 + default: + offset = int64(len(text)) + } + before := text[:min(int(offset), len(text))] + chk.line = strings.Count(before, "\n") + 1 + chk.column = len([]rune(before[strings.LastIndex(before, "\n")+1:])) + chk.column = max(chk.column, 1) + chk.err = fmt.Sprintf("%d:%d %v", chk.line, chk.column, err) +} + +// jsonEditorExcerpt shows the error line with a caret under the column. +func jsonEditorExcerpt(c *ui.Context, line string, n, col int) { + k := Tokens(c) + u := Density(c).Unit() + ui.Column(c).Padding(u*2, u*3).Radius(theme.ControlRadius).Background(k.Background).Border(theme.BorderWidth, k.Border). + Label(fmt.Sprintf(msg(c, "json_editor.at"), n, col)).Children(func() { + spans := []ui.Span{{Text: fmt.Sprintf("%4d | ", n), Color: k.TextMuted}} + toks, _ := internal.Highlight("json", line, false) + for _, t := range toks { + spans = append(spans, ui.Span{Text: t.Text, Color: codeTokenColor(k, t.Kind)}) + } + ui.RichText(c, spans...).Font(theme.MonoFont).FontSize(FontSize(c, codeSize)).SingleLine() + codeMono(c, strings.Repeat(" ", 7+col-1)+"^").TextColor(k.Danger) + }) +} diff --git a/json_editor_test.go b/json_editor_test.go new file mode 100644 index 0000000..ebed49e --- /dev/null +++ b/json_editor_test.go @@ -0,0 +1,75 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +func jsonEditorTester(text *string, opts JsonEditorOptions, last *JsonEditorResult) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + *last = JsonEditor(c, text, opts) + }, 600, 460) +} + +func TestJsonEditorFormatAndValid(t *testing.T) { + text := `{"hall":"east","guests":[1,2]}` + var r JsonEditorResult + tt := jsonEditorTester(&text, JsonEditorOptions{}, &r) + if !r.Valid || !tt.HasText("Valid JSON") { + t.Fatalf("valid: %+v", r) + } + _ = tt.Click("Format") + if text != "{\n \"hall\": \"east\",\n \"guests\": [\n 1,\n 2\n ]\n}" { + t.Fatalf("formatted %q", text) + } +} + +func TestJsonEditorErrorLocation(t *testing.T) { + text := "{\n \"hall\": \"east\",\n \"guests\": [1, 2,]\n}" + var r JsonEditorResult + tt := jsonEditorTester(&text, JsonEditorOptions{}, &r) + if r.Valid || r.Line != 3 || r.Column != 19 { + t.Fatalf("location %+v", r) + } + if !tt.HasText(" 3 | ") || !tt.HasText(" ^") { + t.Fatalf("excerpt: %q", tt.Texts()) + } + _ = tt.Click("Format") // disabled while invalid + if r.Changed { + t.Fatal("formatted invalid JSON") + } + text = "{} {}" + tt.Frame() + if r.Valid || r.Line != 1 { + t.Fatalf("trailing value: %+v", r) + } +} + +func TestJsonEditorTypingAndDisabled(t *testing.T) { + text := "" + var r JsonEditorResult + tt := jsonEditorTester(&text, JsonEditorOptions{}, &r) + if r.Valid { + t.Fatal("empty text is valid") + } + tt.Key(0, ui.KeyTab) // Format is disabled while invalid + tt.Type(`[true]`) + if !r.Valid || text != "[true]" { + t.Fatalf("typed %q valid %v", text, r.Valid) + } + tt = jsonEditorTester(&text, JsonEditorOptions{Disabled: true}, &r) + tt.Key(0, ui.KeyTab) + tt.Type("x") + if text != "[true]" { + t.Fatalf("disabled edited: %q", text) + } +} + +func TestJsonEditorSnapshot(t *testing.T) { + snapshot(t, "json_editor", 560, 360, func(c *ui.Context) { + text := "{\n \"hall\": \"east wing\",\n \"guests\": [1, 2,]\n}" + ui.Column(c).Padding(16).Children(func() { JsonEditor(c, &text, JsonEditorOptions{MinHeight: 120}) }) + }) +} diff --git a/messages_devtools.go b/messages_devtools.go index abe4d0e..5a02788 100644 --- a/messages_devtools.go +++ b/messages_devtools.go @@ -82,6 +82,11 @@ func init() { "json_viewer.copy": {"Copy path", "复制路径"}, "json_viewer.items": {"%s %d items %s", "%s %d 项 %s"}, + "json_editor.label": {"JSON", "JSON"}, + "json_editor.format": {"Format", "格式化"}, + "json_editor.valid": {"Valid JSON", "JSON 有效"}, + "json_editor.at": {"Error at line %d, column %d", "错误位于第 %d 行第 %d 列"}, + "alert_list.empty": {"No alerts", "没有告警"}, }) } diff --git a/testdata/snapshots/darwin/json_editor-dark.png b/testdata/snapshots/darwin/json_editor-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..8c843a6ffecd8f88e77321e049871e29d54c7db2 GIT binary patch literal 17115 zcmeIZbyQqU(=SQ_!Gl|H*Py{YgS)%CLvUx3;O=h09fCUqcXto&5OmPtOy2K#@;>MM zaqc~S*Scq~UOjv7>8`G>?ymk-Rqs$md5O1(_=r$YP;aFqMU|nTUb8?!y|RRV4SCaF zs4fHr1;~{W{iy1ZakA#An6OU-_iJN4{5zb6VKPOKiI%3R5~)}oJ2g3_F{TMe*H2bF z8asfUH4SUO^@kFri27uXFYrc)O zt!)IOro06zZ-+sTPqpW*s3R!AlVFf1Nyjg25GBS}QuwbvE1NZ_P#D6mC7}>VUr~OB ziTVH9!*nJ*Mz-%~2(PQllvMpi^cbS#WXU~76KjDNtbWNWYZiECTzE^)fAe36DOnN* zqv!B)kg_(B)y&XC7AKQ8t(>6p20_M6v!*AGDCy+8se5|Ti75G|UKKj}eQ0@uW3}dB z7SvxdU>_6Sa^iMx7dMIr2b)R#Et5tB%7_+$-kTw$l%MPw4-)tZZ8jn0^tOlz;S z;1e+O=~e#F$}KJ)e6Q9rXMH>m`tB@MAe}5K*3jSe|`jWp`~lr+T$Q+>q_kQ}=C9_*fUk!_*Z4@b1KmI2xCm z^wNX>5%@fx{o)i7a>hLR($mGI%LKwKEA63vb~+z_uhzf(mg5E&e<@iM4JOs`UcXN{ zR^pYuC_Kgf-<}yo+nVAm#;%#&9rW$ z*TnIP%!mf2(p*|1$AlWDQcvGr;U0rn;Qex2Mg{YH%4kvx&n zra?4l$uj~h+`YtdqO@2{x{CR#)QEv!KlXg;=`+upqQpW}_XXbn{Gq7pV_T=|bc=ZnbGs%b>m4`uU1; zMblb3PL`S~SHK&{r37;f3Z9jcS0CK1iQ#RicI@2fG+K*CeNSs_fc+5-=04~XcZS3; zZrNNab?vV8#LW(+SYKX2T(KlmGEKbn+>xwMPhEp+vz~n@B^w779kuxSdJblI->wX< z%~g%-nt=LTALK97`%L2I}3@( zC>TL=1X7N7r zpzF1+8>l}RYA?4FWqOh0vm^U@>}*f!e;_x;+v}kq;mfSNaD4oFFSh^UdNIAr`Q`JA z>`mHYiY58c$+Y`n<yW{zBkTS`N~6=VFg#=NW)2LL2mw{A$=#SKYm_#0CGNu5TYbYa$f*5Zl90Fw$8DN zRS&B=8zM79Na5LLoVSB$d^QG2WPNmz(OumYPF}Zq-Qf4ITwFMk#XLN~_TbB{CvtJx z(~##)VXbQIxwfq8>KkU-8vclhub^k}RU{;flcvmxw!AHKFepUE$ezH3zax%bIKH{0 zX*bDH{ITnF97X*2b5O!Z5O`lRy}TwA9v(h6F;iDphZ!qNohiUqP_*y$iFen!2Ilpb ziGjL$cP99CAma06W83x7<9WyPFOhH`!608dn2#~dbT z-Y8`Ut3W)SDp^Xz{h4O|;WZbShblZwM|(Lq@}<%D?Th*Tnf`Ly7F3iF&5&#gGvyLm z9;RwxI=bsOu6IbW)m?`EYf)tS4&TG8)7w2+@y@L8G$Ohd`xea+E0neC7#XD*)yn3R z8^q8{xGiAEZ5Z__2{f@nKox&3ym*V9*(Kj^kKagw%fVaa~q9GA!8LW4(de? zeTVUZ(p>UCUU*Si{6+Xb$p5$IaL{!7FL*2WU#dnR0F&=MH4F5If2CU6(Q8$DH2wRl z%bz47xO4h`!bg{G*H55{Ur!m!q~zqwLzJT4c5A5FzF7>CJlRgVZgBWH=>JMnS@>3I z=8~Rv_6^gUssx_Uq3OK#M@=Q$g;Bq6vf4}d_;C)tGW|z-JWx(~^FP#y|FY9{B$7$c zV5a2N$hRCEb21}2Q&X(BS!c!K^@c>q>frq=t^=sI4Za~3kA zO#UfrL_v|7rSjNc^ndz@#7>VUsVsdX3RxEg11FiDx+)6|1MV1cui2<%zeaEf0x%i< zCA)&Z{)84ohY3ZOJ{Atq#um-4kUl&SE34QNQxxvf#-?q|tq)sxl~J$#3LER4SRf1R zJLH%hzi!8Etp0G#lZT2Y$MN%%?;_Xn%-$7=wJ!iVK3`uw8-}Rx+U+?Zr|)pWuSs9U zKmai%)N~BA)!|mOg-45;D!=rqGn98sjei1B{t2lW937wJ(p%5rv)~FojR3LYV)`rG zH^!)+Vg9b2PX?##+kSA;%%HWDEGs8&^q>ESae*d;&Oq1R-%ARG@TZpVYh^jR%$yuQ z!_UmYJ>A6Y9KtHlf3hAlP7bu_h=@_pK>Pmwu@#H*`QF%9CXzqZq4LSzDBJ1z9t=zR z!2V|N<1mV{+i#xN2&9)j3je;Yss7b9jDNHW>FIy>BQsQJkC88EtVp(yhw~5RYr+_u z@%_j~A`82*O=iR&#Jr9tpZ8rfeCAs*wi`{euM4vNg`0geyre_s# z17K29-0gc_xY&zQ#JiN3R|S!#i2_<2sP z=H~X85%I3Pa7c>4nMVR6MA>h3z2j8K$+-&i%?(XR@t-T{fJ&b`vY4WePdCrL{-eDk zEiA-0B)9nlT=wSK?W*ePUn2FNvP@=ap)t3+mIVE!PdA2x#Pha#EXZ9EA!J=$-*yrS zEG;euL|Wu3(jpd-#s|;5;Gn^?pX9S!3LUx*Pdkk&3{Aa9+S_FSo`uM@cpjqwLb@gq zQ8BrF3}=?<1v6a*%_Wp&WElMVe-6&}((Vcqrpj);VQP1;f?0V^iU!r8LO;t!NUXK% zU_DRth9_6L`gO#A70lQc7!lD)ciilrTa$uALOgRtk#KIVee$tLNyXpaTVF;`mOXjZ zc@tbUCL4(ccs67# zG;rIo-NHk@uLn*JKkZ1h6eAFjV`F7lwc+G~M^epHm!E%f>;ODCI9R;&&CjYt0a)hd ztz9bM=(l^BTqSAB%%>#gkY)D7#>RG)CVsV9a7qrYOdguV&8{M^NrpoK<71`=pz(U2 zImy~8%i6{%YWmZ7Y5|Nm;;kz4dcM~aSr~pBFsjvjPn}t*rbWf?eRdgCeA)^koLyK@ zQ}RqgD)>-W*d%MpQr93(!_FR}9?YzyLhuF6$*Bfar>m;GbR9qQTS<<+P*>XcWVgas zY>e_4!$25@{6?3+9U-p#kFz1BHQT8c`O5Dac1gN6>=%R3rp+gt!TP3bQuK*-;qoC- z^7)FikiHWWGhq^)!s?q|UPl*Su-@)Co_gsod>$wMfB|5G z5R<_Hni}($78E3k=c%cwZEyN>G6N2V=Y%ed&PyvQBr@sgnR>GL>x=V6dA>VH?7y>i zIR7@ofevtKG2hwWoL4`&y+tWem8QHv1GpoFyZ4`OJ6eogI#prJpR7B7g8Eibg}c`I zGPxXPVs4(1kTGneqgW!%!_#PhcO8a^B$Jkwh7Q=xbbwdtRo2jesBi-V=D3&E5d7?y zxn5&d$7L~tKZ@6TM8%CQ^<;NJj=BPQH#u3z#d)8X=Mf7FYm333jxDwK29!2%b?{4{@ue0VQjp&_g+K^z({T{v>ddEBU|Vxf5zxjU3LBeP+U^7G&(D(qLNiN zJE`csu!?1HD1{s}4|mJS%ZqFa^_SFjUgc|uP>T>@Ao%C>nWiykOqkSWpGJc#n-PNT za>|NCaum63kx*8?-pO?Gb;2zzN9fdg9hN4(mI%IBJZ_947APs?BsbP8Ub_P&GwOZ1 z(Cvrevk6d?2o4a>OHEI2;#Qzc!}@f7mw&Ys?4f%e6&1C&mo!fU0csJQ-h|5>G)08%CG-dOA zOJ^(gNmq8(KREIJO`x6$rk^pCcstQoyNV@GADqk@qIbO4+RN+i+9!+^Vuj!w4j%LfIW^pCS)Q5IR% z*@gl@@aiOXS3v=Y%a`QJhum>ykH{$J$)Q7ABu}aN3MMdc?G}t~mQTRbn6G>|rt?Nz zJcxtC_3~@##;D=b^M-3SqXc{(%@ZMJI_06qrZ4rnXTW61%swUt=<(ibc`>5hPoSzs zImF@svhq~OW6?V4;9*|hh*3hf$KEw53~$ug`Rr)>+y;q}$EvESrKRh9zgWNy1WcJv z+E^dMe{-`6D3w0OtX8F`ij|zO7#%c?D=np0n(|Z=-B`PN#;sCl;M^|sZ0e;st17c$ z!7EA5!XasNJ|DmHy=yTpDC%FBpMwi7?}vqldGJ1rX6~%jZGWP2UaX4of=`v@d~YdQ z&KWR=*ix6Sqm4c9kgu%e&W_K9g&rL@?WCbW!?hq)URIbu$ZD_bX{P230=;-np4@PI8}swWU2k^q z$#FIWH9;n|Z0ga*B&pZo^s!$i`PBB3Gfun9W9X01OWE(WgbV1^G!{_sBu_wK3yoFK zrQQ1g4wW@(IoC`qv}=TpV$FBKA3mU1ut}sB5Qg*+&>Ug5jVnCj0pIun%9E1jKxf zh!L9qi_VnoUM+jET4!_oC*lAIjY3t8n-Is*Ci@E=o3YsA%S`I>eZA&}7=241ZB8^|_q)>MZ*U#hFlt4sOleOK7XE z0x0Ejj!USnzFwjnX_p!C$xp`C*PoV})9QUNNA!Lz4P8M=N%Jv^6XL-3!dyiN(PwbF z@8={YofU}f1yC!nIpqAlc)2a_6Vhfbj~(78u(3?|#}b(Me0`+8^&Y9PrDeI{bj45V zr2-T1Fh6f{@u?5r;SpSa?Ab}o3(=w+te0u`z~1?v5Dja}1_`Dx>2(PD?gjViH9}Mw z;RHWLtmNK>&v-_&q7KMqL?a-k`(mwqoGB1~KH{B;-^CWm;z1)XFYm9-pO)Z()x#Ox z&J98xjfKwwe{6~y_33$Dit3#ZNyJ^2Vg_LpB*VBoXQ zPlH8&e2hO+^QJRMlyb6s-C{%m&5MM@<*aGhh()>c3axzrOk$e{hsqFbmPK}p^kTRb zF{EOyp|LQ$N#@nYv_+y)_)u|wqSK>9p%HX>@gYn4u&-Uwo2ZKAPd&wNF6*MaXSERJTS-n;?1+|;cV$@fnPOGIa7ys(1gzd88%kqO=Pu$qjn zfuk8>B|9GXRLVe0=mxgdmS|E`(?LNN8@%MVDw^P-?j8v?2$T{na)n4HB=SYGswAhf zs_L;%BfWY-sCwg(1XkoY(|tX5JJ@RPx)YO^mlBs*kK)(W{X?Qjig|p|oj_+*GeFKU z_0DXkG%QRanaLn~iw&JjRq@k#SI;mQO!#L2hrZ0t&o?;DC1+ROQ}Vyx*~}}oS#TbR z^o@p6o?f3-+x*H*-gA6}FQ3`*6AV;Hy_R{Hy4bD&B*Vo^=F=OEwcNarHf(_%S1Or> z4C-A+k~0asH^3I7_WTGf5OB#t)zh=)S(-ir*^31`ki3aE{3{J6G7l^zY)RH}r`B_t zU$Q>{4!ZkPPEN_q6b&TYNM)jcm>=mtNDde~c?Zo~~}w|LePWvK}W z2*m9)7wVty!O)$zznmfa#ryXukYT8P_4iwXS>pIt87 z70dI+9+o>gqbYa`x-{t}0x7 z-&HC4<}2+RpK;}Se~*43DdYhhoZOEr;(cp*<5N;TS0Y zESay^(=k;s8PxKc?}ioDYa^RDQ2ybdaDbI$t(H2Q*`J%8Z5KLg$7T8pvi{z4*%O2T z@KTgCnN z)7HoM9rs5s8t)AP1S}te8XW#)=VZ2z)Wk^p69qO_<+?Xw_WFLw2>+JDv?BD-Z12HS zpU}9eUN7t)AJM;DFb?EqMfsxWLg=W!hW-IkoSh*F!Q@Bi{D{o831 zQ*^Sbo2gs$be>oSEG#1FBmO79DEW3Pm<@IcKP>)?{J-kA=Ohrp{JumtAg;K7bNDwv z`OCTWzw+_$^wl(QvH7aFuo4FOCl^_`lq`JA_Ykre_%Ar7YFHl~NzmVj!N6koPC1!d z6E(JMR@TwF@*ORv@_ryh!Y1cHJY#FZQdP~jZz8VvIgOBm&)-c(JsSGSYK{dui8u4& zab2i6d1rUG-FtkWnMV*F0q`}3l=A=QKVInoHoQn#SpO(UoCR%Xj^!P)|CFRerzV-& z!qL}NESCVtK>||dz#+qHW!f?q4wu|^qbaykIr+n(`&gwlTU)JBl;n4uwu|459y>RX z#Z3|`_sUeLei!3j;+r0TREEDaK*GfS5+*G6ja(7E7=8r%3jjct6Qd|eONoVnh!7a? z-~D(9Y>z3w_dzLxeyLI)#gz;o=k=l!Ak2X6*q4)Vz+eg&m>ozS*!n2f>)C>p-XNo+Er`HF?W5_obdU)6(5}DaqOqUx$0aHqFY<$Kp z>PIesLCMY^fl92$)Rdhm)WLg<@+S)$TP5ExiMYKpJ(Sj+ldR0g(pP~o{`D#2^$aA@ zVZ@uq3KB%}I!ND~4W_e9UpAv<`gdBSQFr{V5a%?j6HZxzEw`{8^r^kFeHYnp6fAvItyi2{z3L{PXW zw4`Lnlx<4MV$VV!%K*2zqeIYqdpYd>J6fIFpq(moshcdJ|z5( zdibmTg!{dl+u@MF{z#qIvpCwc6;F~KcqfJO^&1RY>577>PcGG0?w8n&2{{3%)B?G!^RvMG`&UZAcor)%?15BwY zt;Unh&Q?tPFvyu|@0IXTq{giwX`s(nv)|~sAJW?~))-P)VVhZm02JCcY;Fk=PvN~uw zFYd4Afy-R%__lBK#-IVVFs}h2wG~8b*`Ame_sX&N`&4i?jsXroZnwpFsA%FJP)`aD z95-9}IA4EkO1{UXK0jk)-aoT&*hs^|Coa6yE?BB>zq9n&n9N!4a8U^8G3L<@Us8V` zs#s?ar~zv|cds(@*xnuswJajG*k|&OEt+blr0c1btz_`m-!JZ99L7Ibj9gwd*}7?8 za|1&98lAu6px)jNG4WJa9DJYda_l;&hU;KdP}!Fh%UwcO3W&LG>g^yd<#?0UHbd2k zM1nzt^)z;41J~KE_;_ultv{|;;g%A;F%UcTW_O9w#t&vtm|E_l_7HAqEX<$%Vu6oeSVG~AfPU6lFY}UIb;~jEylY_PV!uGy}7Bt z73G_VY79}wNjCKCKuJT$M|BkX*Y0fWD?{011R-j7YF@$gvmrZ3rD=xXr? z&A<8Kd+|f#tYypQM(9QXM{GcoH5G?O$H4d^o;S>Ho{D;5c5ZB=xo0@+-PR`E*41Hb zS|fVLD~OUtH7M6?);Zl=wA=2*TAOK6KeNcz`Y80$3<$=)90te$Qewt z0DJkE*#el&qUX|{94a-K!b6dI*FcV-=kbn5aOWZ)W)pQ~{Y&N{Hk( zpV25y$q;mIV?2elsQb_;zNvfp`bCqZ^6&5LjGCsL^Y0U8-H(V$CV96{eqV?uaWCR~ z(N&^8JF0tlB#7GHFRgS)NiLo`uIJUWR%QbfztqL6N%%30GSP9$Cy6Sg5o2iU8hD?H z?>-F{ua+h6i^&(G*ExyVJsD7KU}aFnOX@RKG8B!&?|q`zP78&7ABKSJmcwH=epjed zz-yqQ)V|)ID~DqDjW+7(c>ugo!x?0v5^k zrFu)Nm%Cf=116s?l=^P!C1fHH9HGOt5VmPL9=9(##%}Ac=D0$}bgqX|ReB36a^?7% zb``xEwOuQZ9`4s33!~mzogbc^w`uA`qIum{=fBI@wCQ{rB^4&VouzKYOIG&?Y&{ktWP!gAop)Kv!Ny7BBgshvJrSgkQ;PiBNbVlI81@ zn|Bymi=xWS)M5PImtj!0SCV|evVeNjqhUiG=767r{Z`nr-)e_V2JrK};81_bq$S~r zmF+J)#MF$m*ve^Zq*8m`)hkY=h1_QJZRT0kXV8Qve2#p<<%laY&x~an+K9Gri$#x6j$4#&Uy?n#%Z^-P&7}o zf*0-jAo+sv$cW))RgT$_fRcMe7x9 zMj2FkKN`VX)JUUfco1sS_Z6ovAAUA9o7vR5TXnsUx_~c^ln^dk;F6*2vW*I^l>rvL zla#_Vh7i|pO|krmT0d+$NZC=hHdGhgp&ya^pq+;4 z-Gf@zjQruWYW?OlMo{GSZMAnf>~yet^MyB;P;#JnPTGV5frSC-&BhFRR>bWYQ{p@~ zQ|Z+41BfSOjCI3^G~g(pe+}8#(cV1>_0;r?0a)0G*@xGf-s2vCra@U(@!QxEpcKYG z?a}5i*#kZ&l+7QO>NtR%@{F}5QyiO~QaLas;Krp^L{y(dVksm}Up7=)qkMwVq$9#Q zwy8$Nac#;tc%Mt(eel;h$@c3ELtCz<6`P-lPm5h&g(z}68l?-Ide=cMVqz>dt&J5K z>R9n>onu!OYO`kVNm{H5eTDAt#Y$hEDWGfDF1C~U`{j@jRy=-|ISD$Pj`Bo{iG}m> zdc6&TDg;HQGm3M5=Ru6rp0z@zVl{2vg6G}Qjr~<|LK31l?U6~9)&_^+GFUDF)$<)) zKSE`9AzxtaIkWKS;1qL72Lq?0b}Mx(xPxrDsiKK|Gl8{V`1^uk3NgNX)2ZaD6+c!7 zvbb8=h2otTD~!_;Bgex~b{G4zs2}onoTHIM@R|w7FTvGF{N8Q|ZDE{r8gw|8_Q$Gz zoI$hsH(`E-Z{hg37vtY?i5(>g)qQfSCZ3p$xRxngfe(lQgUhYI@KC0RJRpsd4Pl2R zUd*^N=^qUk_QSwVyu;P%+9wNuEVdR+6<--O0P~pKo$={|Fb*H5$4SGRa3FoFb;kzb zx9j$HA&msPv}~995~W2l33#Q*oD})InTn8f9G7&V1zLx_Sk`GBx{)zV{+)u4w5#&ly{XNZoiyueq%jHiGW zA487jPI~Z~hS|=&LMfPz?n=SJlBJGi;+mxqmCF!diaR~LS&uJ6EeEDrp06Lc>thCe zUyEiY>DjZDlQ#GC(6!oGcEz`$P}MbPrTAc?Z#tXysvLTho(_MXTu;QqcdsV!fM+>IqfU@<=#{`=_BwT2n~GB|V$QAt#jfY}0N`{%PCSqaz-+eILJWQdM_c`-!+ z98C7TPUqvBnGCyFXFL_X1#>WG`8~@)M;X{zvqVpUu%ot{UNycqrp|@&cbuuo)KG6> z^YhZzDmhGath{Td(a2u9SI^Vzbx79SnA>mXHdyZ1UyMCP!ex$5SD#z%jt{A*K;IU- z)aVpyvbmT}7;+S_c3loVQEx7HfQxAFc5EfFn3;oYYq+uzL(7+?%6ZLx@#m-Jx?R=J zzZyxZ^po$0IRg~^%sdMZH$&g3C31Y7_h+`#zuE;y$n5)Tp7@SrySX}T$*j1Wn=i8S zvDdm)sWEwDLJMq_j_7Ol z;{wBeEj?Ojb9ZZ*6vLi1(8et~aZPa1hi@;L+y0)Di0|~Q1dyi%y4k0T-~Ot3qA;G} zbvvcS*)FK-!kRL+^w_Jy#I9|ZZaDL$>LKH$X-$c5fW~=4;pP3yezbbU&{9jD8q>DS z%GkJFzlx3BZPJ1yp1Pu*=T75Cp=3;CTaV?*Y`o*$tKui^Uak40J5ZXresV1m&9inae(Ldebj-O3qvuU2B zQ@j$V0OuohzN4H-IaGv|W^S{o;B$;9zQN41)lT2m@h>{VJ54(WQr^xrJBWSr`8xwN z1AU$KDtQa&yKbUI%HD5b^&HOaY_3^bgZuh<0tAt}eA@9y_@8af9gV;@m+x|nvMW7V zIVjp+^1pf3uYUu)yvowU3rg*LpRST1UqBx4_6Zja;uU*PfWt;XT{8h;I;r(vQ+nf$R?x>b5zOWqzLC) zy-DvMz7y!&>xJ2O|8|j+PBLD)IHO(?aA=ag^%@)xm4?QD75Q0QnBp7y3W*o*|N_^1l ztW#O(aR|m=agde?VSMbLLR9ADiP9D2mF@a!a!o{Zj%V-jBX5^FJE;gk%B%<~fstHq zVMT;34G&8I-Om>A6tSNOCAj6e3F%m@%gd6zpA$<%uL@_ARu8a7LgKnY$8J5jPTzh^ zm#k1H;YnM;l~kqo+$^7TOWm5DGwrlma6-ULrVYNo-bFSI@^X;-V=aU{&w zN7o;+A2LU&WiU_?Mq%AI;&sL-eSJu}KTdK9F%^N0yWfb?dt@@8tL+el ze-mkXw#Mc#N^3qcne7~iFXzvC%&?4dwg%FYa8tTfWX?*(CO)${TpFxsI zD2eq|_*<}Kj{tx?UP|aiSy5Zvo+WgH9{$%A6=HC5vHa{Cf@C2KKKZiggcm2zr<0+9 zjR~~bD~l@apF`#ucgYv}Sx2IJoiFum3elJJP4GDvwKNKHsk^5WIGpSJ?!SumhGDPT zeXdO|;&Eh6B6sRkuMknPJG90`;2Bpg2)*l=1EsUty}IhmoY4WP-kp#mVU1E9xn^oK-ONRvCrtl7U4}K1-Ko}R-B1!F5f+yrou<=Jm1vA=cK`gYL@;o@kR@T zCfHjvEb^nTxA%7g2^hUSLTUe{G1H6^GUQVlNRguo22kg4fX#uK0GBBMfp5VpPrhS2NC#={r|&`Em2;`I@F+p+xrr>?9uUD7h6RoYy z(#r*MV?JdsAD1+$e7>odqDRqB7*P1oetOb8n>%ub0|MYnu!a*(%R(?7@i8$=)ohqe zvse$_4m0f@)x8Dh< z5dR7pS=~XE#13rS9WjwmJI{p`tZ-Fq+${^S2ZrtTXrV6-ab@CC9!Z9SDh(oWvwqd z@IXf|bZ^(ARS~yw3)+*M%j1N!xB(Ls?*8V1`_<4udTI>|>-p#sXKa%mH3RMYEOu6U zkL!b@mItG$PC;dc1c*H3(g$0N!mPXm+5{Mr*GPfgkvo8r8kviWW#6VQ;k)J}iu6T4D&W6ek zCGyy1tQ*{!nfv&)a4)Ro**gA2aiJZzA3uJ;nz014H}T|;SDw+aPBHU8x`=8@ih5W& zb@R9D3lWN+K7<4FSSj6B^ArR*{cd+s6=*Q*d&_>Z`S@-njru_TSmi)M;40-e*gM)4 zJiiyq5bkWE`ruCF4K(#Y1JJTFU6SySh>m6DD_JLGB35Y6l$}$p2ed5nQ48YWvTvfh zePa%lZ83RXIgF29U%#8aVs_A$;D%*nc7F)?j*i(a?$JCWu=;+bay+T%K&XR~?)ms7 zi{I+C(sP58yX%7oh#Y6;rggPul2rZV?z6;e-;nN)b9r4w&xj8ZHS`FqO zZ-dH;!hHs!*i!(xD6!XDu}@Yq77AL|#qxO!qu_#qW$sB09c3-Iy~bu69G=$Jy2cPN z=vcSnDEe6-9lZU7o5X)Vk3`9K59I8i!K~w}1oG*1DBm_p6eT_TWqw-KQrV`d)?B|? zEo|Z4CY-2CnR2jClf%k=S$gCK(B7Q3jbQhm;BEH@Uf%5co zojzW7{^b|&&`9`%pzjMC$KqVCrwT2*wU%G##|?sb7LJR8f^6_J(eXwuZ}a30uW!{^ z(gp1{t}uDCXYxB+3f+61C4nTuJwYVxd=3XK8p`*{yx3!yPbWg;=f`a>`Cs>fn zp3H@v4>VHsAtY|)$YmXLdRjAQqu+3Ws_wROLDU64`&J+%QRCG40LpxXe*Fb(`t}tL zMA_F!Q=gM^2A8M`4AZz(hGuY-XcQ5=BkpCaI7@%`F;5jUy%C-Zuf@!*2ID99*=MHb zk6TS(D1zop1hK7V1O>FJ-#a%xx7SYfwkJfPwdnELb$8vs1)UfUqWsRzh>@3`1Iuq4 zhp$gx1bD6AuLb(u>~E#zBIfSo#H*L3b2wN@NbEDN!!7(Bs}_(X^?ek<*(-kP(A4P} zYk(bOwjX6?!jK9XGn)ESh=ugfh1VDpa`;&o_$n5RzXk(wra(U`aQ!)O@!tRnAEKe+ zT5GRrqsP;@dH)&fhC|4QSV65g%)VC_H2;i>#|Ue`6MD8*5fS|{IQUs9;Gf?m2;oXr zcbWJvWwi5XK`bDMZH>V7kB~EvDMr0tp~}*#(gxGifJq-uU?@_aM*1JrA27d&k{cOh ze-J!;+Nq!3dRR8(45s~}>lQPD;7D`*RRqIE<+6UTVnk$}hsV95BBju60HK%vh$!o8OQ* zrhfg=c9V7)x-?aQ$+vEwC5jNc6p^pL zJsE#x`JLm0|Ib1B>jjwUmudqzXvo*}@V!qJ-T{t1cx31p|2O@gkw9Od3t414FCJt# zgh}@t)+izf`aZ)@lD2n^M7yuC(FSx24#A&%;XhB-cUy1d86wHXyZ`_I literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/json_editor-light.png b/testdata/snapshots/darwin/json_editor-light.png new file mode 100644 index 0000000000000000000000000000000000000000..59b92e9f740e4d9d1b6be559b383bf97694df598 GIT binary patch literal 16593 zcmeIZbyQqS(=SW{L4p$`xVr`k4uRnAlOTcM?rsy@-QC>+1RVwq?yduY;O;iaH|IR( zoaerOd~02KudH?Nu39~NPtUIIuBz_(?XKBjDoWDmuZdp6!NH-+%1Efe!M)^wgL`3) z{1Wy}U$K@b9NdRgSqU)>_sm0xkB0gUY5&=Y;!D{Nu_3342uXq8!~#6M;b;Xato-;P zi)qfnIyWj!t|iVTdRF6y*uwp;S&%u!hzv>4^jzb#8iYT)al_kO*`ne*mZW^#2MWr3 z2Fk8E0~C*DUcr>pg5K$Rr>ibC2g+>YJ3|tzYDAV~>!7sF589EOBJYOGWyx>~J_A zUdq6sP`se|h7kLI+Qn2Aa(pEC12Uzv(M2;9;*;XY{llWB*u6K-SiYwKU)xP%@Ze~!xD-?OxPjx-aVstfgnnv2_)zx9M@y0Kp zYr>4T^9@!xS&yYI<^$t&Qm8@V{uJL?9ZrRSkhpgo92|7(SO|VQ{XVsBr$0{4l%Z_z zYT}0qqv)R{CA1FG=--PRU15YOb8hg}x0oMHYlelPOwtoRE<1YmM-x~dW%*Oc6it18 zb$2ii7|Y_y2(^9>6&CgUP?DhvvTM?1ixC;zU6+2J&1c~O9R(_66q|^mQwGxML2t-O z-ZpnP{#4bvwgHQT*g!h(R~~b8?|zz(n7w&JTv&^Q?;*6RFMQjqR_6ZeDW|xkB0W7l zhP0+;sqLy}b{tF9sNgDYP2Bw-5mA)gRr_acRgSh)8<7qeDw8>BhqT^$jv5 z_u!j|m>57s{lc8NKLs~8_myt*TQr23{mPAvph>(kFcjmh*)~5&4;~&c2XU+Ebrg7u zCP#x4Qm<2{Qu(R2vSV#}Wra!t0nBE$SVv3JA0)osClxU~`{s3!rbU?>i+*})>S-4e zz2Kobp8dV&^%NHTj zNvWwE#n{-7o2%iesj1h|W3}?qPCFw?GO2YY!{owb?8~T>ffFeJec;9lzfQJThe4N6 zjM*<0$mFhzTh6#yZKhh8+9+*Ak=kDD+S=qKKM8WGd^mr!p!2S|{BTQt{wu_Q_J)Sh z^-zr`R5$h1=RA zI7+jlX6c8^O?Ah2QfzVax*dDIf|Q*w2DZaAvhCrP`JKpa`|8dt7}iPhz@ zrcvVo#Cne#CZR`}Usza%qP`(aW`|oC8OeQJwIL&`xXl+Giu?Mkx)xTs!4jud_RAwX zR83vI6*RtVP5SZUQw6{WH|$i1J>hZfTeB>Mr1^?82VVN?*V4_lcg;sBH=;{RmIr$+ zsR>+)c`AIQ)dn26#Kc^6)OX$iDeNH4GDZsvrka}zVwv|g-j^e3G0%+`y$4TXe=ZS6 z6Q%XZ=>+wx4uL&XH2T~hnUc=dPZtQgUFgvcw^DA7K__mm{MNhl15Y0lKhsgM=kZvHZV_B%D}!NwJ>@GTes%{d~{4q{i{0- zCOofaW?YOAK|#_8{4xFd))i~Ev5^sjlTiJ(&ia%$CuoBP6D^SJopOD9fpK1L?E*kd zXlb+gQ#HgZ+xvVmIk|CU~grsGvIs4?MvU*Kdz zYGF~1PE2Ns^wdTxkH31+b28uV5UWTLh(!d@%~UH;1>SxwP@q$1*u)=ZY!TARcLTCFzTT8*;h80lOwQ6EmwMxmOuat&bsK5UK@JL(ZtoWir!AuO;!tWW)dvO#oQeIYFYyH@&CN#UjyJ!{%j6!NVv!t+GkEVysnxoC=yTH4eg^>3OGe+WG0`dEaaL|;{ zyh&HA>wmTOBs)v=wg>jCca3N~2y)3T!4 zwFeA1enzn|tE*2-71W)=m)P^Mj7p+CsIRs=BqeLoG{^p=1uZDMUiU#jIHDk{hV&|} zdOj$ZRDs&3X1z>Ul=!TA>G`=PCB?(ZyRI&BgM+OuGjpxOcL401#I)pE#`#vbo+bZlBj?RbmDqe^4g6%J|)!xzjAL(Eeg@rH2-2C;@^n?gM zH_np?b02KV#pcAJ2cx$%`cG@#l|+Zu&V1XnFc-BiSgPxgLHh&qb-Y25mTL6^+09wJ zlD&wBY)&8(hDa>jltGb)Ns642WJuyGK@T;U-0$+r!eeY zRyMDyuD+p5DElp0K_hDzi4pb>F&>dlNpP6grcNS9P5jCDlMJL3WtK8XQU(Zr@BwB4 z`C%4{iu%rdUk5@I2jYtttW>}Jv$QD@uq`1&h6_ad-=4K4B`P0EdYSz-KO`3AUxz!s znAEbwt7_1wl2oZEMUK<^p^b$sAE)73DkDx(m%W0rE+2=DmWWz(7CU6CeR(JkKnKZppc`C?^ zc_c4kP5gBax4?(={2G1ri_+%XuU=5~!#|q;yKduyA>UF*(*zx#Il%Fp|4^(1ad!qT zizrte-r4e-5<2y2nV(!yUq5*#^(V@%*C<*t-^Km$a4KV^EP@bT{c#k@{TK579^F6P z{nuds8!ofq)x4n7Pp2s4)JYI~TlnuQUMj23~wy>+J%(H{`17wJo5U(5MJkM?;1zO5Bi( z9lDNh(HOSg4{`N7YzD;INMZ*R=A;N!0e!{?xpC|QS;1JARyH6VXJtH~fkTU~f9&3_ zxsg%g?9>#~F3zvXiJ~%D6HdKTWGcbXLyEe90Sqe84mIdW0{}Q_xSqaCj(7?0Z)wF} zsxzJ~91*!-1@(KA(84Kl9C`_~kBf+o#z67{2*@lL5RP15Uk?oUM57f` z2i(o;E1wGAGYLO#mk^6cBb%7*@N=db);8DH4vx*W)F%M%&NT&f%zEc0CPGjA+7*S` z&BmWjPZBWiigHMKPE zpms(aBxBivHrCdgph;y4EULhT!|>2h26FP6pFacp`ikw^pYUr2-PgaWR?K^QbNvpF ztDOWuF_`9zGkAf%43wr*g|fuFp5$_&{E1jxovk)=BZO|pD@k%i9gnU#Js!GU)s>Z% zutw9^va}y-ZYJQd8#b;(3|~V=VMQoTtVWp3JR%40)mz|00c@i@6UvS+}zzu zOZ(=A{=vcby%8w_%|hPvyVvND_Pk|YXQ;1Bs}H8!-u!f56mblequVpG*SRBf_oMQ- z<~b&-B|p8AcZ{;@>00ZoJ`K*9d)C~X*5}Vnq$Jj7!ofSu_xJbnQ|c)G9Nd|~V80(e zUT|VqxUY+v3uW|tUhZqz+P3?3>4*@wVTtR!d|y=jg^k^8JV!Z4@P^d2_XI)g)Ln>x zpg%R$eTFqmf(rMwPPz_b;#piA5Lj$tZB6qW5Jk)j8L&TIar^#gm^PLML#Z&A!;~Mw zZ?{uF{~o!vScc462Ju|Kb5Sqe4G9X0l_}hl3T9-qfSJtH^aLN$Qc+cfhuxyC7-elC z+u`sVf0Z;XW1N|dacV+E)c1jy^KSNb2l21vQRC%QAd0ZvhNY!2ujlIR$o3$R!kA<9 z1I6wg!q!(=3*D8t`1lZQZEan)`ALTs$LDncHgWTN=a1eOTSUCoJ6AIkD*a@ZHa1pP zP^*@n5D0Z)g<#90te|*lh5UEuun&t?V{2*v4-Ltlw!R!(5=z zc#!JBpgdfGZklDJoT;Lsyu626yq?o8qSvsMwrbqg=kmC%g$4(OlG|k;mots!@XKIB zlPC{YPDwG{PE{*5y1MZ=iF;X0!#+5ya}Y8 ztd$G-gpHk-cb=G+ZEdJcz-l{^z3~_;Ou}923j*awaQ|bdQi+MNF(HvseeGuzZEda3 znl`dcoe4~`!J9}^Q*(wX0HzJWGzG0MV|^F!vNSle^SsXJ{dWX^2L&m=LY6q08Coo4 zgzoO{{LDjLYwFrhELh02I4EKR2ru(C;4@x4yeQAhbI6+SyE=G&o-EKlMAv3xi{{~J z7#!Tm5%%gcG+Flj^|S0z{p^7jM-~78I5`cMshgc1mlhVzjLqG31sK8yG?|X2=jG*9 ze`;W`T;dM+F*EGqa>f3r#GIuB^59Cawa6GbdL87b5h_9D z_kr*9jsi9dY;0_hF#GBk@t(u@XQrmY%@EbJv>L%#s5(5J|7bp{W#Bky7J8IA!#czI z%<2nr1DfyvCztIq+Ml1qi#ob?>Z!a9c}G{YpTP4EIVcz0W?Phn%jN?ow0R;B@P zB_%z|&F$QMe5WsUoWclZ{rZHoSudfTvd3X)!Th2g>jtR9trJlS-<-a}(n_hdq z^*fphLbW+%gDzih3HMVw<6qxN_& zM|W^=us*q#ujZ8vBv*-(DSR^LHe^t<_VjFB^Y&q(rHzu}8XspbR4a469!{)(!~?Za z8TY>z@`r`GKVzO3E0tjBBr7_46lw>}&JID{F!NmXV_=uP3T>xCi~DuXX6Ao^Fs@$3 zZzwqP@&aIN-KMKvqSVDJMu8R~^#Hzc$u+&}@yxNy9eqmwn!X!K|L+hRQ@+n1zDlUY$ zI60}QubJ+fvhUu>Dk~F=Q;6i~e{xSzD!F=(#4M7Nlc6)QFk!C5Ro~dy?b^Y_)JovQ zkxfxZGWMHAt2JDBjPlQz4^zaKo¬Hd>KSNy*8}ivqtVLq*2Z>P-vvmE}9K=ZJ+Q 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 literal 0 HcmV?d00001 From 8f6203e6ed243b87e6628dde6457ea809591ad11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 5 Oct 2026 15:01:29 -0400 Subject: [PATCH 13/14] wip-devtools --- messages_devtools.go | 8 + schema_tree.go | 155 ++++++++++++++++++ schema_tree_test.go | 90 ++++++++++ .../snapshots/darwin/schema_tree-dark.png | Bin 0 -> 16250 bytes .../snapshots/darwin/schema_tree-light.png | Bin 0 -> 15825 bytes 5 files changed, 253 insertions(+) create mode 100644 schema_tree.go create mode 100644 schema_tree_test.go create mode 100644 testdata/snapshots/darwin/schema_tree-dark.png create mode 100644 testdata/snapshots/darwin/schema_tree-light.png diff --git a/messages_devtools.go b/messages_devtools.go index 5a02788..57d55fd 100644 --- a/messages_devtools.go +++ b/messages_devtools.go @@ -87,6 +87,14 @@ func init() { "json_editor.valid": {"Valid JSON", "JSON 有效"}, "json_editor.at": {"Error at line %d, column %d", "错误位于第 %d 行第 %d 列"}, + "schema_tree.label": {"Schema", "数据库结构"}, + "schema_tree.empty": {"No databases", "没有数据库"}, + "schema_tree.database": {"Database", "数据库"}, + "schema_tree.table": {"Table", "表"}, + "schema_tree.view": {"View", "视图"}, + "schema_tree.column": {"Column", "列"}, + "schema_tree.index": {"Index", "索引"}, + "alert_list.empty": {"No alerts", "没有告警"}, }) } diff --git a/schema_tree.go b/schema_tree.go new file mode 100644 index 0000000..0c8fed3 --- /dev/null +++ b/schema_tree.go @@ -0,0 +1,155 @@ +package mujica + +import ( + "fmt" + "log/slog" + + "github.com/egoist/mygo/ui" + + "mujicaui/internal" + "mujicaui/theme" +) + +// SchemaKind is what a schema node is. The zero value is SchemaDatabase. +type SchemaKind int + +const ( + SchemaDatabase SchemaKind = iota + SchemaTable + SchemaView + SchemaColumn + SchemaIndex +) + +// SchemaNode is one database object; Detail is a column type or index columns. +type SchemaNode struct { + Name string + Kind SchemaKind + Detail string + Children []SchemaNode +} + +// SchemaTreeState is a SchemaTree's open nodes and choice. +type SchemaTreeState struct { + outline ui.OutlineState[string] + selected int + nodes map[string]*SchemaNode + kids map[string][]string + roots []string + src []SchemaNode +} + +// Selected returns the chosen node's path, as "shop/orders/id", and whether one is chosen. +func (s *SchemaTreeState) Selected() (string, bool) { + if s.selected < 0 || s.selected >= s.outline.Rows() { + return "", false + } + return s.outline.Item(s.selected), true +} + +// SchemaTreeOptions configure a SchemaTree. +type SchemaTreeOptions struct { + // Height of the tree; zero means 360. + Height float32 +} + +// SchemaTree shows databases, tables, views, columns and indexes as a keyboard tree, virtualized. +func SchemaTree(c *ui.Context, s *SchemaTreeState, roots []SchemaNode, opts SchemaTreeOptions) ChoiceResult { + if s == nil || opts.Height < 0 { + panic("mujica: SchemaTree needs a state and Height >= 0") + } + h := opts.Height + if h == 0 { + h = 360 + } + schemaIndex(s, roots) + k := Tokens(c) + d := Density(c) + var r ChoiceResult + r.Element = codePanel(c).Height(h).Label(msg(c, "schema_tree.label")) + r.Element.Children(func() { + if len(s.roots) == 0 { + codeCaption(c, msg(c, "schema_tree.empty")).Padding(d.Unit() * 4) + return + } + was := s.selected + s.outline.List.Selected = &s.selected + s.outline.List.Label = func(i int) string { return s.nodes[s.outline.Item(i)].Name } + restore := dataSelectTheme(c, 0) + e := ui.Outline(c, &s.outline, s.roots, func(p string) []string { return s.kids[p] }, func(p string) { + n := s.nodes[p] + ui.Row(c).Grow(1).MinWidth(0).MinHeight(d.ControlHeight()-2*d.Unit()).Gap(d.Unit()*2).AlignItems(ui.Center). + Label(fmt.Sprintf("%s %s %s", schemaWord(c, n.Kind), n.Name, n.Detail)).Children(func() { + sel, _ := s.Selected() + codeSelectMark(c, sel == p) + letter, col := schemaBadge(k, n.Kind) + codeBadge(c, letter, col) + ui.Text(c, n.Name).SingleLine().Shrink(0) + if n.Detail != "" { + codeMono(c, n.Detail).TextColor(k.TextMuted).MinWidth(0) + } + }) + }).Grow(1) + restore() + internal.Ring(e, k.Focus, theme.ControlRadius) + r.changed = s.selected != was + }) + if r.changed { + p, _ := s.Selected() + slog.Debug("mujica: schema_tree select", "path", p) + } + return r +} + +// schemaIndex maps paths to nodes once per new schema. +func schemaIndex(s *SchemaTreeState, roots []SchemaNode) { + if s.nodes != nil && len(roots) == len(s.src) && (len(roots) == 0 || &roots[0] == &s.src[0]) { + return + } + if s.nodes == nil { + s.selected = -1 // nothing chosen at first + } + s.src, s.nodes, s.kids, s.roots = roots, map[string]*SchemaNode{}, map[string][]string{}, nil + var walk func(list []SchemaNode, parent string) []string + walk = func(list []SchemaNode, parent string) []string { + var paths []string + for i := range list { + n := &list[i] + if n.Name == "" || n.Kind < SchemaDatabase || n.Kind > SchemaIndex { + panic(fmt.Sprintf("mujica: SchemaTree node %q needs a Name and a known Kind", n.Name)) + } + p := n.Name + if parent != "" { + p = parent + "/" + n.Name + } + if _, dup := s.nodes[p]; dup { + panic("mujica: SchemaTree has two nodes at " + p) + } + s.nodes[p] = n + paths = append(paths, p) + if len(n.Children) > 0 { + s.kids[p] = walk(n.Children, p) + } + } + return paths + } + s.roots = walk(roots, "") +} + +func schemaBadge(k theme.Tokens, kind SchemaKind) (string, ui.Color) { + switch kind { + case SchemaDatabase: + return "D", k.AccentText + case SchemaTable: + return "T", k.Info + case SchemaView: + return "V", k.Success + case SchemaIndex: + return "I", k.Warning + } + return "C", k.TextMuted +} + +func schemaWord(c *ui.Context, kind SchemaKind) string { + return msg(c, [...]string{"schema_tree.database", "schema_tree.table", "schema_tree.view", "schema_tree.column", "schema_tree.index"}[kind]) +} diff --git a/schema_tree_test.go b/schema_tree_test.go new file mode 100644 index 0000000..2a487b1 --- /dev/null +++ b/schema_tree_test.go @@ -0,0 +1,90 @@ +package mujica + +import ( + "fmt" + "testing" + + "github.com/egoist/mygo/ui" +) + +var schemaSample = []SchemaNode{{Name: "court", Kind: SchemaDatabase, Children: []SchemaNode{ + {Name: "guests", Kind: SchemaTable, Children: []SchemaNode{ + {Name: "id", Kind: SchemaColumn, Detail: "bigint primary key"}, + {Name: "name", Kind: SchemaColumn, Detail: "text not null"}, + {Name: "guests_name_idx", Kind: SchemaIndex, Detail: "(name)"}, + }}, + {Name: "seating", Kind: SchemaView}, +}}} + +func schemaTester(s *SchemaTreeState, roots []SchemaNode, changes *int) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + if SchemaTree(c, s, roots, SchemaTreeOptions{}).Changed() { + *changes++ + } + }, 520, 420) +} + +func TestSchemaTreeKeyboardOpenAndChoose(t *testing.T) { + var s SchemaTreeState + n := 0 + tt := schemaTester(&s, schemaSample, &n) + if _, ok := s.Selected(); ok || tt.HasText("guests") { + t.Fatalf("start: %q", tt.Texts()) + } + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyDown) // court + tt.Key(0, ui.KeyRight) + tt.Key(0, ui.KeyDown) // guests + tt.Key(0, ui.KeyRight) + for _, s := range []string{"guests", "seating", "bigint primary key", "(name)"} { + if !tt.HasText(s) { + t.Fatalf("missing %q in %q", s, tt.Texts()) + } + } + tt.Key(0, ui.KeyDown) + if p, _ := s.Selected(); p != "court/guests/id" || n == 0 { + t.Fatalf("selected %q changes %d", p, n) + } +} + +func TestSchemaTreeEmptyRulesMany(t *testing.T) { + var s SchemaTreeState + n := 0 + tt := schemaTester(&s, nil, &n) + if !tt.HasText("No databases") { + t.Fatal("empty text") + } + cols := make([]SchemaNode, 100000) + for i := range cols { + cols[i] = SchemaNode{Name: fmt.Sprint("c", i), Kind: SchemaColumn} + } + big := []SchemaNode{{Name: "wide", Kind: SchemaTable, Children: cols}} + var bs SchemaTreeState + tt = schemaTester(&bs, big, &n) + tt.Key(0, ui.KeyTab) + tt.Key(0, ui.KeyDown) + tt.Key(0, ui.KeyRight) + if got := len(tt.Texts()); got > 400 || !tt.HasText("c0") { + t.Fatalf("%d texts for 100000 columns", got) + } + defer func() { + if recover() == nil { + t.Fatal("duplicate path did not panic") + } + }() + var ds SchemaTreeState + schemaTester(&ds, []SchemaNode{{Name: "a"}, {Name: "a"}}, &n) +} + +func TestSchemaTreeSnapshot(t *testing.T) { + var s SchemaTreeState + snapshotAfter(t, "schema_tree", 500, 300, func(c *ui.Context) { + ui.Column(c).Padding(16).Children(func() { SchemaTree(c, &s, schemaSample, SchemaTreeOptions{Height: 260}) }) + }, func(tt *ui.Tester) { + s = SchemaTreeState{} + for _, k := range []ui.Key{ui.KeyTab, ui.KeyDown, ui.KeyRight, ui.KeyDown, ui.KeyRight, ui.KeyDown} { + tt.Key(0, k) + } + }) +} diff --git a/testdata/snapshots/darwin/schema_tree-dark.png b/testdata/snapshots/darwin/schema_tree-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..b11c6b27e8803791ed7d9910d8554d0e716d912c GIT binary patch literal 16250 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 Date: Mon, 5 Oct 2026 15:02:59 -0400 Subject: [PATCH 14/14] wip-devtools --- api_request_builder.go | 124 ++++++++++++++++++ api_request_builder_test.go | 93 +++++++++++++ messages_devtools.go | 11 ++ .../darwin/api_request_builder-dark.png | Bin 0 -> 23383 bytes .../darwin/api_request_builder-light.png | Bin 0 -> 22442 bytes 5 files changed, 228 insertions(+) create mode 100644 api_request_builder.go create mode 100644 api_request_builder_test.go create mode 100644 testdata/snapshots/darwin/api_request_builder-dark.png create mode 100644 testdata/snapshots/darwin/api_request_builder-light.png diff --git a/api_request_builder.go b/api_request_builder.go new file mode 100644 index 0000000..8abceab --- /dev/null +++ b/api_request_builder.go @@ -0,0 +1,124 @@ +package mujica + +import ( + "fmt" + "log/slog" + "net/url" + "slices" + + "github.com/egoist/mygo/ui" + + "mujicaui/icons" + "mujicaui/theme" +) + +// ApiRequest is an HTTP request being built; the caller sends it. +type ApiRequest struct { + // Method is GET when empty. + Method string + URL string + Params []KeyValue + Headers []KeyValue + Body string +} + +// ApiRequestBuilderOptions configure an ApiRequestBuilder. +type ApiRequestBuilderOptions struct { + // Sending shows the request in flight and blocks another send. + Sending bool + Disabled bool +} + +// ApiRequestResult reports this frame's events. +type ApiRequestResult struct { + Element *ui.Element + // Send asks the caller to send the request. + Send bool + Changed bool +} + +type apiRequestState struct{ tab int } + +var apiMethods = []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"} + +// ApiRequestBuilder edits method, URL, query parameters, headers and body, and reports a send. +func ApiRequestBuilder(c *ui.Context, req *ApiRequest, opts ApiRequestBuilderOptions) ApiRequestResult { + if req == nil { + panic("mujica: ApiRequestBuilder needs a request") + } + if req.Method == "" { + req.Method = "GET" + } + if !slices.Contains(apiMethods, req.Method) { + panic("mujica: ApiRequestBuilder method " + req.Method + " is not offered") + } + u := Density(c).Unit() + var r ApiRequestResult + r.Element = ui.Column(c).Shrink(0).Gap(u * 3).Disabled(opts.Disabled) + st := ui.Local(r.Element, "mujica.apirequest", func() apiRequestState { return apiRequestState{} }) + locked := r.Element.IsDisabled() + full, urlErr := apiRequestURL(*req) + r.Element.Children(func() { + ui.Row(c).Gap(u * 2).AlignItems(ui.Start).Children(func() { + methods := make([]SelectOption[string], len(apiMethods)) + for i, m := range apiMethods { + methods[i] = SelectOption[string]{Value: m, Label: m} + } + r.Changed = Select(c, &req.Method, methods, SelectOptions{Label: msg(c, "api_request_builder.method"), Disabled: locked}).Changed() + shownErr := "" + if req.URL != "" && urlErr != nil { + shownErr = urlErr.Error() + } + in := TextInput(c, &req.URL, TextInputOptions{Placeholder: "https://", Label: msg(c, "api_request_builder.url"), Disabled: locked, Error: shownErr}) + in.Element.Grow(1) + r.Changed = r.Changed || in.Changed() + ready := urlErr == nil && !opts.Sending && !locked + send := Button(c, msg(c, "api_request_builder.send"), ButtonOptions{Icon: icons.Must("upload"), Loading: opts.Sending, Disabled: locked || urlErr != nil}) + r.Send = ready && (send.Clicked() || in.Submitted()) + }) + if urlErr == nil { + codeMono(c, req.Method+" "+full).TextColor(Tokens(c).TextMuted) + } + tabs := []Tab{ + {Label: fmt.Sprintf(msg(c, "api_request_builder.params"), len(keyValueEnabled(req.Params))), Panel: func() { + r.Changed = KeyValueInput(c, &req.Params, KeyValueInputOptions{Label: msg(c, "api_request_builder.params_label"), Disabled: locked}).Changed() || r.Changed + }}, + {Label: fmt.Sprintf(msg(c, "api_request_builder.headers"), len(keyValueEnabled(req.Headers))), Panel: func() { + r.Changed = KeyValueInput(c, &req.Headers, KeyValueInputOptions{Label: msg(c, "api_request_builder.headers_label"), Disabled: locked}).Changed() || r.Changed + }}, + {Label: msg(c, "api_request_builder.body"), Panel: func() { + if req.Method == "GET" || req.Method == "HEAD" { + agentCaption(c, msg(c, "api_request_builder.no_body")) + } + body := TextArea(c, &req.Body, TextAreaOptions{Label: msg(c, "api_request_builder.body"), MinHeight: 120, MaxHeight: 320, Disabled: locked}) + body.Input.Font(theme.MonoFont) + r.Changed = r.Changed || body.Changed() + }}, + } + Tabs(c, &st.tab, tabs, TabsOptions{Label: msg(c, "api_request_builder.parts")}) + }) + if r.Send { + slog.Debug("mujica: api_request_builder send", "method", req.Method) // never the URL: it may hold a token + } + return r +} + +// apiRequestURL joins the URL with its enabled query parameters, or says why it is invalid. +func apiRequestURL(req ApiRequest) (string, error) { + if req.URL == "" { + return "", fmt.Errorf("empty URL") + } + u, err := url.Parse(req.URL) + if err != nil { + return "", err + } + if (u.Scheme != "http" && u.Scheme != "https") || u.Host == "" { + return "", fmt.Errorf("the URL needs http:// or https:// and a host") + } + q := u.Query() + for _, kv := range keyValueEnabled(req.Params) { + q.Add(kv.Key, kv.Value) + } + u.RawQuery = q.Encode() + return u.String(), nil +} diff --git a/api_request_builder_test.go b/api_request_builder_test.go new file mode 100644 index 0000000..151a7c5 --- /dev/null +++ b/api_request_builder_test.go @@ -0,0 +1,93 @@ +package mujica + +import ( + "testing" + + "github.com/egoist/mygo/ui" +) + +func apiTester(req *ApiRequest, opts *ApiRequestBuilderOptions, sends *int) *ui.Tester { + return ui.NewTester(func(c *ui.Context) { + Use(c, Settings{}) + ui.Column(c).Padding(8).Children(func() { + if ApiRequestBuilder(c, req, *opts).Send { + *sends++ + } + }) + }, 760, 520) +} + +func TestApiRequestBuilderBuildAndSend(t *testing.T) { + var req ApiRequest + opts := ApiRequestBuilderOptions{} + sends := 0 + tt := apiTester(&req, &opts, &sends) + if req.Method != "GET" { + t.Fatalf("default method %q", req.Method) + } + _ = tt.Click("Send") // disabled without a URL + _ = tt.Click("Request URL") + tt.Type("ftp://court") + if !tt.HasText("the URL needs http:// or https:// and a host") || sends != 0 { + t.Fatalf("invalid URL: %q", tt.Texts()) + } + req.URL = "" + tt.Frame() + _ = tt.Click("Request URL") + tt.Type("https://court.example/guests") + _ = tt.Click("Add row") + _ = tt.Click("Row 1 Key") + tt.Type("page") + tt.Key(0, ui.KeyTab) + tt.Type("2") + if !tt.HasText("GET https://court.example/guests?page=2") || !tt.HasText("Params (1)") { + t.Fatalf("preview: %q", tt.Texts()) + } + _ = tt.Click("Request URL") + tt.Key(0, ui.KeyEnter) + _ = tt.Click("Send") + if sends != 2 { + t.Fatalf("sends %d", sends) + } + _ = tt.Click("Method") + _ = tt.Click("POST") + _ = tt.Click("Body") + if req.Method != "POST" || tt.HasText("GET and HEAD requests usually carry no body.") { + t.Fatalf("method %q", req.Method) + } +} + +func TestApiRequestBuilderSendingAndDisabled(t *testing.T) { + req := ApiRequest{URL: "https://court.example"} + opts := ApiRequestBuilderOptions{Sending: true} + sends := 0 + tt := apiTester(&req, &opts, &sends) + _ = tt.Click("Request URL") + tt.Key(0, ui.KeyEnter) + if sends != 0 { + t.Fatal("sent while sending") + } + opts = ApiRequestBuilderOptions{Disabled: true} + tt.Frame() + _ = tt.Click("Send") + _ = tt.Click("Request URL") + tt.Type("x") + if sends != 0 || req.URL != "https://court.example" { + t.Fatalf("disabled: %q sends %d", req.URL, sends) + } + defer func() { + if recover() == nil { + t.Fatal("unknown method did not panic") + } + }() + apiTester(&ApiRequest{Method: "BREW"}, &ApiRequestBuilderOptions{}, &sends) +} + +func TestApiRequestBuilderSnapshot(t *testing.T) { + snapshot(t, "api_request_builder", 720, 330, func(c *ui.Context) { + req := ApiRequest{Method: "POST", URL: "https://court.example/guests", + Params: []KeyValue{{Key: "notify", Value: "true", Enabled: true}}, + Headers: []KeyValue{{Key: "Accept", Value: "application/json", Enabled: true}}} + ui.Column(c).Padding(16).Children(func() { ApiRequestBuilder(c, &req, ApiRequestBuilderOptions{}) }) + }) +} diff --git a/messages_devtools.go b/messages_devtools.go index 57d55fd..1b8a9e1 100644 --- a/messages_devtools.go +++ b/messages_devtools.go @@ -95,6 +95,17 @@ func init() { "schema_tree.column": {"Column", "列"}, "schema_tree.index": {"Index", "索引"}, + "api_request_builder.method": {"Method", "方法"}, + "api_request_builder.url": {"Request URL", "请求地址"}, + "api_request_builder.send": {"Send", "发送"}, + "api_request_builder.params": {"Params (%d)", "参数(%d)"}, + "api_request_builder.params_label": {"Query parameters", "查询参数"}, + "api_request_builder.headers": {"Headers (%d)", "请求头(%d)"}, + "api_request_builder.headers_label": {"Request headers", "请求头"}, + "api_request_builder.body": {"Body", "正文"}, + "api_request_builder.no_body": {"GET and HEAD requests usually carry no body.", "GET 与 HEAD 请求通常不带正文。"}, + "api_request_builder.parts": {"Request parts", "请求组成"}, + "alert_list.empty": {"No alerts", "没有告警"}, }) } diff --git a/testdata/snapshots/darwin/api_request_builder-dark.png b/testdata/snapshots/darwin/api_request_builder-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..96395613c90b97e8ffe3cd1d0d4d18676113bd48 GIT binary patch literal 23383 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 TjjO=F)|*-ydZ1FZhcEsKD9Ez| literal 0 HcmV?d00001 diff --git a/testdata/snapshots/darwin/api_request_builder-light.png b/testdata/snapshots/darwin/api_request_builder-light.png new file mode 100644 index 0000000000000000000000000000000000000000..89bc6c36e515c2672718ba179ff43ce0c7dfcebe GIT binary patch literal 22442 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 z zEjiBg>P5Yh9OmlYeW%50V7uG<%G`dLk0dA$D!h%S{YXMLn>eLpX#d#J(@QrSG@_Xe zsx=;{Gv#Bz1qf_3(btszKD(TuM8d^RN-`yW{zzU>HxImxPBmZXtdATq!2xo0b5c++ zGh9UK`rWTI@&nJeoLbu-&)e03<}4Wlfq^6)&6AQ}z!h*_#`iRWz)hxzFQx3r}vNq7JF4d^vZ)GrJU8J#+qf+_F_0>`s zBPpzP-jSA?wxqKvYr-r_^p7(zYs`RY7%zs!a$o>4MuGw%-oYai?r~Oe)8VYdxc*?A zhMwmB>PEB6sa2LHAz9kn+iN}aA_576`c?(S0IV}y9l&qwGD65a8fbZ9T z1DBx|b1sRRx4ad97o5x@t$%v0A5Lhpr>V#J&o{nj=;?Gnt!a@O ztggz8jXf5YKm}CR8Ld|9g#`wR-yWpN7TZ5w3hHPIb>mnkYW%`w4v8E{rhft#Qav}{ z6M*yCZCP{-lanIukJk71Z30AkoYjs~?1V0L0d&-HS&34n)6CPXW|KcPM&gJCq|bAn z1=GAb1&)&n%cfOKm9?~#@BqUVd)!*wA`07UFwU=czI8}-aF48PTywtd?W8rNJXAan zt(v91h9q~Ioj>Bs?+*b?+>KDN94k50}a6{68l(CF!DCMKwAj*n;W ziN?7~&*!Pe72x3!hbKpyT%f>S&Iw0joswl%Si3Gb3!z9*iZTal1=R?yHSA*{oHGqh zDX)+}+=mbnTf*YyU08@Apy)4_y}7kkFVok#RyS3{?qqy=c`E!^Y}Q3B>F)l>QC3;k z)iwX~2hcO4cFD%Z;pXb@%)O`K+-Ebn);={ce-9LVZP@Dz-P6FS)M-2kQ+|FC?Dnnd zLIsp#vjeWIlxJr47L)08RrR%wX>W?rjAE)URsmZS7+Ot27+A8+YuH^I8_hht_SRlc z9SB@yJ|E|gccRvc=GVQw!fC2a79;sbsTP1O-WxP?;)T)Q& zgeuCD{J7mkN@iPI+WzVoheuUJbtx#Ihs4B8_`1J1i{yE?NMNO!DG)OehB&3 zFY8v6W?>433&8)krKExP&hfHq{qhuC^vT1q{#z)9fd;z|VSuFb5J&s#zm7#IX> z5Sa?B z1)}F}co-_&X84bUaBstK>C-DP;Csr=#uUdht2UQG1w(<8H&|+juI+8z-QBxpaO~E( zb{gGGuP`TUQzp#RaI$vl9D=zdv`uf7_T1kI{}|P}*{$O7P^x0kAf#c4$jwHh`sRT0 zf{%L~!Rr0j{_}?nBX|-{Zd3tGcLXQar*Ur{A*4S_kCo1=&cS^tLOIDg$eKKDSaj&yl_n58fTuvXIXm`2Ef$QUcXr7%@)-``Qk#y@g zv@y4Kz6_ae7#`;5btC5WJyBMhk2ltmj5YObah!b%fd+$zEUf3@r069}kz1}C@drFI zDxq&mWSgE`ps~l#%;~LbPaC)!Yo3&$EN*kEl%e_4`%T~X^{Xp~-&~qobX(UR$&M5g zLna%ZNOZi(_k&xncdTsR+ibs}fw^~=fUR$6Wj;t7{M|RUYCZ94ebMS1-fNON-fhi< zCp)+_;csThl18x@D<&Irbbg*BWvVn=5EdSuR>?~t8iV1<+C0tGmOW4x`xz#-W35A7 zts-Ch6FOGF?KiHRpnrZ*+|ltuUMTKPm3zotktg@YBHrk4WJ z-l&?U)649zo2LP2-aJT;J;&MkX*M)9J943t*ss`_o2O)DwEI4CJ`adL2J{cy>^Evu z+&j3JoDTlbr%wxW#DTvk&pNugp{j{MK%u@xG3nd1S+I2F6Hrj9I-0(-0XeWHEiG+! zY_iyKWb0>_n_<7(SSU5|wUA7l^L7^rPi#4GWwwO0fKPnSiQeeoQ3=!Ed(~23iQ`Jn z=Vx?`Q~Z1d6Gj@Eo4YIA$VF;qRZcKGqxLlRM1w`$9tv}`b+m15ZTZEs!HpN3N=ih( z?X|Q$$=#@^e#3GS_c7wri}@FAkBxvDvhQDK!M^F=?w?kQ*P>R4b=2D{U6(Gz4Y{yO zso0mBm?&T+M5AABHAe-x>@An%$VWzXJKfU&-X5rh_(Jwn2T)8^9E5uK9nB^Bs=Z1WQ8RiuQa?)OjVbL_USM-nCOlTN@c;xo4AYz!3`Dgf|t zh=RU54K+FT1`bABxh@SvMTPMhpvNt^L7)(gqK2RJt;H?q4()OBSRx(F=aVIk6UPQ z3^OF#xNmq%y46mYd}ghm`1XZX<4=+JYVQ&xo|!J4W0Wxht30b@nyQYF!m3J>OSsDU zv2eIp>FVR7uEUMsIRc^AwPjApN8>x$*v))d#VFIzUeZWL_htj<>Mmn+zQ|_o)cj$& zeI?okg8tJ@P}PzdOB!MnVrQ_vDZc|P)0rGT4+A~zy;lb_x4N!!WigeiXiC=$Tjh}n| z8D{&tr_Zm4wwHMC|=qcM})}Su(cgRg| z646DzljG$H`m4oh5FRI2YH67s92~Mtq{Xduv4Tql^QUbp)Vy~#DRH7EU?E$%xyvwP zueXJztgNhk2aO#4x0N4MzfY9-`j$-j`JH9C>!1=TFRG)D9Z+BJX(dx^|1g79i5-pq z;iiO_2QRZdcfVg;^Pzt_WT-6)p1kTX^ z`jR$rFsISi_a>5@S`=UHzv92siAkhK)P!quf=?Fm(}RQWY%g7-iTyQx1s$4VU6%&so9tp(Y>&QV~3|f(`Egews;JO5AkMN+?0){ zT@;Ius7z=wB0aV?O@rz}bI}b};r*4NR`Q4TTnnPNd^}B{lr67`&>VD_v}|z!f^+t{$6Cb|L-4?+T;J$+JBYA-teQI!A_i} zEf+~=ISf&?PG*1ORVwhyowd|L4bTV}s&~eT2`I4JsOE&V7dLRqHUdmu|9O+QbFh4}d+X1Agf2*G!3RHpCJou9!Yc zEct@}^ev0mEQv=(edp(4DLs`_b8n9aw+k{OEh|2m-2OmRI3B_yhyVW3P}M&z(I_mo zGFUt&DPM;N4fp>#Br{+n3FZesYFj8oY8K5+t7{HRfam&neUe3zR@oG^Ap?^DE zqn~Fjt z@AuPDZwSookJ7f6g~kf-76z4B^}^qcfX}}x@c@7$WsAlYFgyFTgOSW6CgmulZimJI6#0mV^*Tyw zX%69v`7}X$iBM^^^7b?KK%+2Gzq?qJW z#QRctv$g{l*L}nO^-^@(BZmBH;)>{(4xffIMrI_(kvL73huZGpxjd@gY_h7VDkd@#9{vg!K2zYGO~4^U@Y6SJP7*=Cv;BR% z*LYzE*(?svgGx>R4{-UHK96r(t5lMIuFw&ki?g$74MOD)9Cj5AjT*(T^^3NNV?%Q3 zZ+Y@18ebD#T_=q4^YVntI+mA~zKa3rfUUIdaXx)5o8zhA-Hi=j9T8rW0DvCKUj`9c z&Z+ZZUo`uwyNia5!NFe|y+7g}tk)ZmZ)ZQyX$kpnlPa zM?eT0BA16O`Sa%sIC*^Ee77) zogm+SQ=qrETN6CR@zGZ|H>CN!!Eo5v#Mh4`)$>-}f=)K_YjjOGnz&tE0?F*o=PM}W zqr=~k;IS~3eBUvfuJFK)uCw!FVKIHu*wCl=jEvK_r%)M^Qtx{wGZ_FNfK~Q}+#e|L z$rYw?+LH(s<+TsDonG$>Gv)d?=l=0tRbzAGdIaN^5_R`*Lw|x-*|+1~>%B^+nNhc; zaPc(w3xzDNG90Q;vqH{>ni}pBQ58khFC1(ls{Fcy5)ytdEId3=rg)Dw-ofrf*wm0} zU^tp@PHJi*_{;$s$m*Lc8ot5oGJf{&eL@-pp0k3i1nkOZYmhUUjK@+gk%=0vG^@9x z3?_CnwWQ)yAD1y0Y7bXAGa_okxfHV5(3P6)e40X@e7e3ZOPH`6pNvhrdIG zwYRp`()Nrp8yMeKx-Fh&e3&52isImE;06i_fp>i$HIqy^e2Q3d1yf~=+XO7?T@@-h3XadYl3_9z-9b$#XT13KpwuEd-~VAxtSa1 zZqEJ9?Ck!r_1ka1vh%G+VqYb74&{1HDddCaq)o;rPJ0K3BS$)luic`)pPeNW!h&5h zuDq9PYcyD&j$$|n3U5HrqaHrIl=6fhH?P#~JaV;k!c#6=8W!0T@Zw}u86d+e20zaQ zl5wZZ(4IF#fk1(YvK|EtSB^r*YwnDF>EOu#N7@X{dup4=z;g7&X((&|;Xm-Qd_hQ3 z)bEi(-xnnToJvdAU6Fe9j;E%41MQQsE_BTYYyeM6NH9WGd9)~a-H*i&g{_@1+=XY` zmd{j56V|PNlaqtMk@R<{)X>FOE@3z>=pGc4tc@fRiA*d>$63Rx1uC`0yt=Aq46DS> zQRmvTUjb@hFg1XZOt-zo$7$;7Z=B`ocagIC0wIK&}Zi`l}2xJ7A^F1FL&Pn8D?6BZ#sFe0g!HHo?eyf*rF2 z1jaS44+#kcpdzM0yiYXNK71SqqbS;>G$Gxh_X=w+Ypv9wU99 zbEtnPXnJ3O5|WNjyoo6OIJ8p8R`}RC?Hm3z2x=7auDyq4{fdX%lyYGvTxrj1tE$Waj>&&?iX357JazXTrr1c%4MTqtw@ z8IAleRnkA~-T&Tk@Z?#zN(?y>T*s~)>7&p5*tUNF8PR8EuQ~@XKPBrSS`k_sQp`8| zZ3P+fkUpk!3wMsyu}i3h@2qXPjgun_6kOfNN+Zj&(V?#j6rKtwv(cADsQHrx&igPWg&`K@C&G}M@Me}6DN*}IG#%@0L zRr{BYAZAke0{$F>&veRg7aE{5KHs(#SS>*h^BcuhUUiD7HYr}Bm?0|M1I9#=m08)` z3$A#yF7=5_UBBU(|-9otRUnBIE?d)`c*DgL!sj5U92Kp=GXIwF#0P$O!oGz;I-kVjhI>j ze;e^>abrkpYmuxcp^I(H8bPoEBMa*YJ*oYj8kl=orh0OY?id)L z3l)71_RzdTuMPY+e<{YP<1Rr#oGLDsiA-@GghA;(&hzIPZX@(ZRJmIzX)ne_^Zo2j z9{m%E@>6i~YJ6QJd^1V9cSBSHtT{bgGNdn*-dpXq>1o7FUBzK49w`+GidyC;+nCyh zKK5MAyf)r+lUOtWpmY>s9f{t$$J7~59HB&-Ng^Mt7(q^w>Vc|P8vSB<+8(4?2F^YR z8UfxL_SkHur>X;XY4Xj%p5{&=Z@mWDzyTr9n+x~&abB&4!rkPcYxP*X2Wf8z?p6UF z4R0pC&Ckw4KUhx)7vH={{q4_5f1Q3}@C#Ac`X&Ox01>?s{iFJq|3I7^BbozDS9zQi3(KG_brf=p` zm&a|o(JAa_`LspiEq*O7TT6-a8;e-NMU?@$p9HVW<*%>4e!yr%aqG_^u%BRvipiwl zq?Zc|tIGG@JR1U@v8*7ulU6rW#x;DV^FZ&SVYH{1xNfrJg1h&}lHvo>m7mUQp7K9e zS|DbxWxZIxj}o6qrL%A(-5?EWfM~OooxW+lOy4{!c-tef>ctj9_eDeLtHX3HFQm+w z=I7Q2!8FllV0L=+cjC%5{=}8LiYyiAu-*4oev#e?ne7##lsHIr<&Hw