From fcc1e9913d5d038368bb71de93d31f259d5a6389 Mon Sep 17 00:00:00 2001 From: SinuS Von SifriduS Date: Sun, 16 Aug 2026 10:40:23 +0200 Subject: [PATCH] v0.1.4-pre.004 --- .gitignore | 4 +- Cargo.toml | 9 +- crates/ksp-app-config-desk/Cargo.toml | 30 ++ crates/ksp-app-config-desk/README.md | 55 ++++ crates/ksp-app-config-desk/TODO.md | 16 ++ crates/ksp-app-config-desk/USAGE.md | 34 +++ crates/ksp-app-config-desk/build.rs | 12 + .../capabilities/default.json | 12 + crates/ksp-app-config-desk/icons/favicon.ico | Bin 0 -> 458870 bytes crates/ksp-app-config-desk/icons/favicon.png | Bin 0 -> 7759 bytes crates/ksp-app-config-desk/src/errors.rs | 7 + crates/ksp-app-config-desk/src/lib.rs | 16 ++ crates/ksp-app-config-desk/src/main.rs | 41 +++ crates/ksp-app-config-desk/src/tauri.rs | 33 +++ crates/ksp-app-config-desk/tauri.conf.json | 50 ++++ .../ksp-app-config-desk/tests/public_api.rs | 14 + deltas/0.1.4/pre.003.md | 20 +- deltas/0.1.4/pre.004.md | 259 ++++++++++++++++++ docs/plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md | 32 ++- docs/rules/RULES_KSP.md | 6 +- 20 files changed, 633 insertions(+), 17 deletions(-) create mode 100644 crates/ksp-app-config-desk/Cargo.toml create mode 100644 crates/ksp-app-config-desk/README.md create mode 100644 crates/ksp-app-config-desk/TODO.md create mode 100644 crates/ksp-app-config-desk/USAGE.md create mode 100644 crates/ksp-app-config-desk/build.rs create mode 100644 crates/ksp-app-config-desk/capabilities/default.json create mode 100644 crates/ksp-app-config-desk/icons/favicon.ico create mode 100644 crates/ksp-app-config-desk/icons/favicon.png create mode 100644 crates/ksp-app-config-desk/src/errors.rs create mode 100644 crates/ksp-app-config-desk/src/lib.rs create mode 100644 crates/ksp-app-config-desk/src/main.rs create mode 100644 crates/ksp-app-config-desk/src/tauri.rs create mode 100644 crates/ksp-app-config-desk/tauri.conf.json create mode 100644 crates/ksp-app-config-desk/tests/public_api.rs create mode 100644 deltas/0.1.4/pre.004.md diff --git a/.gitignore b/.gitignore index 591fff7..04bbd11 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # file: .gitignore -# version: 2 +# version: 3 # Rust build artifacts # /target/ # target is outside. @@ -16,6 +16,8 @@ pnpm-debug.log* # Node / frontend dependencies and generated artifacts **/node_modules/ **/dist/ +bindings/ +gen/ package-lock.json pnpm-lock.yaml yarn.lock diff --git a/Cargo.toml b/Cargo.toml index 6757e5e..4d55251 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ # file: Cargo.toml -# version: 64 +# version: 65 [workspace] resolver = "3" -members = ["crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib"] +members = ["crates/ksp-app-config-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib"] [workspace.package] -version = "0.1.4-pre.3" +version = "0.1.4-pre.4" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" @@ -14,6 +14,7 @@ authors = ["SinuS von SifriduS "] publish = false [workspace.dependencies] +fs2 = { version = "^0.4" } serde = { version = "^1.0", features = ["derive"] } serde_json = { version = "^1.0" } jsonschema = { version = "^0.49", default-features = false } @@ -22,6 +23,8 @@ tracing = { version = "^0.1", default-features = false, features = ["std"] } tracing-subscriber = { version = "^0.3", default-features = false, features = ["fmt", "json", "ansi"] } tracing-appender = { version = "^0.2", default-features = false } tokio = { version = "^1.53", default-features = false, features = ["rt", "rt-multi-thread", "macros"] } +tauri = { version = "^2.11" } +tauri-build = { version = "^2.6" } [workspace.lints.rust] missing_docs = "warn" diff --git a/crates/ksp-app-config-desk/Cargo.toml b/crates/ksp-app-config-desk/Cargo.toml new file mode 100644 index 0000000..822576c --- /dev/null +++ b/crates/ksp-app-config-desk/Cargo.toml @@ -0,0 +1,30 @@ +# file: crates/ksp-app-config-desk/Cargo.toml +# version: 1 + +[package] +name = "ksp-app-config-desk" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +publish.workspace = true + +[lib] +name = "ksp_app_config_desk_lib" +path = "src/lib.rs" +crate-type = ["staticlib", "cdylib", "rlib"] + +[[bin]] +name = "ksp-app-config-desk" +path = "src/main.rs" + +[build-dependencies] +tauri-build.workspace = true + +[dependencies] +fs2.workspace = true +ksp-core-lib = { path = "../ksp-core-lib" } +tauri.workspace = true + +[lints] +workspace = true diff --git a/crates/ksp-app-config-desk/README.md b/crates/ksp-app-config-desk/README.md new file mode 100644 index 0000000..755c5d4 --- /dev/null +++ b/crates/ksp-app-config-desk/README.md @@ -0,0 +1,55 @@ + + + +# `ksp-app-config-desk` + +`ksp-app-config-desk` est l'application desktop spécialisée de management et de validation de la configuration KSP. + +La crate est un package Tauri mixte : + +```text +package : ksp-app-config-desk +lib : ksp_app_config_desk_lib +bin : ksp-app-config-desk +``` + +## Responsabilités + +L'application restera une couche d'interface/composition. Les responsabilités de parsing, validation, profils, environnement, `.env`, secrets et persistence restent dans `ksp-config-lib`; le runtime Logging restera possédé par `ksp-logging-lib`. + +La première tranche de squelette établit uniquement : + +- le membre workspace Rust ; +- le couple `lib` + `bin` ; +- le verrou single-instance ; +- l'assemblage Tauri minimal dans `src/tauri.rs` ; +- les fenêtres `splash` et `main` ; +- les capabilities initiales ; +- l'icône de référence reprise du gabarit desktop éprouvé ; +- le port de développement réservé `1430`, avec `1431` réservé au HMR Vite ; +- le répertoire frontend cible `frontend/`, dont `frontend/ts/` et `frontend/ts/bindings/`. + +Le frontend Vite/TypeScript/SCSS et les dépendances npm sont introduits dans la tranche suivante. Tant que cette tranche n'est pas appliquée, `cargo tauri dev` n'est pas un scénario de validation attendu de ce squelette. + +## Artefacts frontend + +Les artefacts frontend construits ne doivent pas être versionnés. `tauri.conf.json` fixe dès maintenant la destination contractuelle : + +```text +../../builds/khadhroony-solana-project/ksp-app-config-desk/dist +``` + +`vite.config.ts` utilisera la même destination lors de son introduction. + +Les commandes npm servent uniquement à installer/mettre à jour les dépendances. Le cycle normal de développement/build de l'application passe par Tauri, qui déclenche les scripts frontend déclarés dans sa configuration. + +## Ports desktop + +KSP réserve des couples de ports distincts par application desk. `ksp-app-config-desk`, première application, utilise : + +```text +Vite HTTP : 1430 +Vite HMR : 1431 +``` + +Les applications desk suivantes incrémentent le couple de deux ports afin de pouvoir fonctionner simultanément sans collision. diff --git a/crates/ksp-app-config-desk/TODO.md b/crates/ksp-app-config-desk/TODO.md new file mode 100644 index 0000000..bceb0f6 --- /dev/null +++ b/crates/ksp-app-config-desk/TODO.md @@ -0,0 +1,16 @@ + + + +# TODO `ksp-app-config-desk` + +Les éléments suivants appartiennent aux prochaines tranches de `0.1.4` : + +- ajouter le gabarit frontend Vite/TypeScript/SCSS sous `frontend/` ; +- ajouter `package.json`, `tsconfig.json` et `vite.config.ts` ; +- réserver strictement `1430` pour Vite et `1431` pour HMR ; +- introduire SimpleBar, `resize-observer-polyfill`, Bootstrap et Font Awesome selon le gabarit retenu ; +- intégrer `tauri-plugin-tracing` et `@fltsci/tauri-plugin-tracing` ; +- introduire les DTO TS-RS au premier contrat Tauri réel, avec bindings générés sous `frontend/ts/bindings/` ; +- introduire `AppState`, Config et l'ownership de `LoggingGuard` ; +- développer les panneaux Documents, Profils, Environnement/`.env`, Secrets et Logging ; +- démontrer le hot reload Logging et le panneau de test de routage. diff --git a/crates/ksp-app-config-desk/USAGE.md b/crates/ksp-app-config-desk/USAGE.md new file mode 100644 index 0000000..949beb3 --- /dev/null +++ b/crates/ksp-app-config-desk/USAGE.md @@ -0,0 +1,34 @@ + + + +# Utilisation de `ksp-app-config-desk` + +## État actuel + +La tranche `0.1.4-pre.004` introduit le squelette Rust/Tauri uniquement. Les fenêtres frontend ne deviennent exécutables qu'après l'ajout du gabarit Vite/TypeScript prévu par la tranche suivante. + +## Entrée binaire + +Le binaire `ksp-app-config-desk` : + +1. acquiert un verrou single-instance dans le répertoire temporaire du système ; +2. collecte les arguments de processus sans lire directement de variable `KSP_*`/`KSPB_*` ; +3. appelle `ksp_app_config_desk_lib::run()` ; +4. convertit explicitement le résultat en `ExitCode`. + +Les arguments Config `--cfgpath`, `--schemapath` et `--filemap` seront consommés par les APIs `ksp-config-lib` dans la tranche de bootstrap applicatif prévue plus loin ; le binaire ne doit pas en réimplémenter le parsing. + +## Fenêtres réservées + +Deux fenêtres sont déjà déclarées dans `tauri.conf.json` : + +- `splash` : lifecycle de démarrage commun ; +- `main` : shell de management Config. + +Leur contenu HTML/TypeScript est ajouté avec le gabarit frontend. + +## Développement frontend + +Le cycle normal utilisera `cargo tauri dev`. Tauri déclenchera alors `npm run dev` via `beforeDevCommand` et chargera le serveur Vite sur le port `1430`. + +Les commandes npm directes sont réservées à la gestion des dépendances (`npm i -D ...` selon la politique du projet), et non au lancement quotidien de l'application. diff --git a/crates/ksp-app-config-desk/build.rs b/crates/ksp-app-config-desk/build.rs new file mode 100644 index 0000000..9abcc5e --- /dev/null +++ b/crates/ksp-app-config-desk/build.rs @@ -0,0 +1,12 @@ +// file: crates/ksp-app-config-desk/build.rs +// version: 1 + +//! Build script for the KSP configuration desktop application. + +#![forbid(unsafe_code)] +#![deny(unreachable_pub)] +#![warn(missing_docs)] + +fn main() { + tauri_build::build() +} diff --git a/crates/ksp-app-config-desk/capabilities/default.json b/crates/ksp-app-config-desk/capabilities/default.json new file mode 100644 index 0000000..ba19e03 --- /dev/null +++ b/crates/ksp-app-config-desk/capabilities/default.json @@ -0,0 +1,12 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Default capability for KSP Config Desk", + "windows": [ + "splash", + "main" + ], + "permissions": [ + "core:default" + ] +} diff --git a/crates/ksp-app-config-desk/icons/favicon.ico b/crates/ksp-app-config-desk/icons/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..9dfa172f89d8511207a622ccdfac2adc03cc460c GIT binary patch literal 458870 zcmeEP2YeJo7e7Shvmi(ku%N--EBbx*AS3}S*ih`)d%=d?L{UMJA|#i)gr=Z?y(e9u3Jq6FXcXPRpJvMNCkjr&Ow#(J6 zoB#UI?k?AgT$ig?uR!^}C_mWcI{$qC^{Sguexb{C%{77YMK0G9JzBX63j^i<)5>-5 z{8lddBYF<)vc0QM#dfYkagC;N$A2G@?SC(?I^o7ypWk-hyGPyLqOIcU%vNvz({t)_ z?~_Nh$PC<={#!o}%kwgX(?g$e*JNz>_mqbz&l{S@uZ8mO@q0W*`v)wV{D`IU4d<88 zbBFP=6r=ntt4BUV_Xmpi#WZh!Wzo~WtKG(Y)m6-q5KrhNK zWx1uTt<(@&-cyx*FlZp&uZ*TVm09%aBX6PnY<5mioQ73}woebfS&@6nmTQZM23UFc zKj3~ao_m7jHdpb@w~V?0_pb|II=WoOT&|Vhxm?QvVF}_TkXQwztp)T|6$LKW(j0$S zhIkq7tsL)iQCU)EbbT&#Z(9Eo9ToPJg6)vqTB@8sYxQ}mbnLn#>Sp|USp{SRM8f}} z-jeqgp2L=oEx0QDlTEpJp85dNoiACh7n(A3QdED{lSAp*wL00lw}kqs+<$c+3fx5f zV`8lTwOg}JGi(C&Pv3CwNTTB(!-qmLLg=qY+5JNv(JoPU+p%kp*x1-h!}1HgXY}sz z`l}xq)`+@^wqm{KQ2*UCf4qkBDLke3jNUz7e^ueoY3SdH{*4G3;*HI|JdDIiKFeX@BK%S4W<08jJN8S9jQd?a;y7 zQ2!c)olx)T2#db|`X&~@{a@U-UO8Il^>4U;#9OHA#PVORoQ^u!uhZ`O23$K)l*!ZbzJ2$+PF>|Cq`N~Js+8U_Ij%Yvg#rZTs%wSN24N<` zGK3`v)d)U>Itm3^om4L=aTkPc2=pGP{vtyFeFD8}U5{|p=uOtfjJySu*)>p>%12f> zPzlPNiuY&s-M2Tebwh?c%<{(Z>P=&m?J{QFVOm-I20sN`ikJp!|HJv`iN8~(?<<@z zYJc+b)5WqU@I0%rPWzvL4@<>Sc7Ge%r{9L7tq_3rkD0RU43Yn?R?3Ob= zS>j{5X_UR}?SFcS?MM8}Z_xf0(9l`8QQ;=aju*zi^_b__S;)4O3~j&u5;x~3?28Y| zwMVE~Jn=F9{2Z%#BN7(c{(?6L@-dDN_36%iY=^l%lzV#I+N0RE%ZqvYgT#8RrbFBR zZ0;q4H{J0fuk!)DdQj$ggu359xR*a?_zt93Uc49DKHf?hQlO`u<<%GC@9Dk)JQt7il`Q{V!%; zaSiUBURHTX*~Yv5JWl`h(k(BoK90?Q`vKN?Zt(qt+P>UMKM{_m(DvtkbPt;`=Q8%u zpBFI9fob|xck?J#-!~|0y0;1;_mJ(Iy|RXD#xO6meblS9X%Lg1Pe6$1ebTUTWazX{ zeP0s|{`V2OUWjmDxVoarYteqLHyiEjj?i)Sh-cg5{hjL}y|?Jjb41fFFQe1`zC4d& z=;7TP@7CsL2!kRqPe$6TedM=Ae?;&3)cyv9*p*Ho+^l_ibmGeYKC|T-Z7!E+cuXiB zbJ9xtcxI13-@Tva)T#Z|2>1n(^Jk@f`n`L7N%yk)i-LW?jdEJou|M?xdUb=&L;rc@ z(S`s9^D$4bo!)bi-m}6#)#>(ia(%F%&pVC1gZ(eQ)W!a1wS8&{&z|J6UK^Eglb$~g zp)10krUldk8UmOr(EjQR>I&*}>T>F{1EC#a3T-I(3hG-^s3@p!RnF>L6|#C)D}>ev zZ4lZa%lJcmM+)^){TIlnSF8hdYV^O4 z@1l6$W?0`*jc-L*J5Uz$Z#t*{+wZQ|>aKcf===Dcc!1?cJsmHSuBAEs)CTGnL+=(3 zM|gwNzpSiKtoPSA(LaOr91$VK%^y!N{WLy|cn(tJ0Q4V&z!HQ%$$T<9!}=VsNIRuL zd$$DBPw&rBzo&xsYl(h*rxW%6XVe+WYSh=LU+aH#?3cGWl68-+J=8tAxH^Mj9*x&U z@y9f7mGx(V{%VZlBYsbIS7o$9-SbP=@6Wbg6`a>3`h(wrW$<8uerofPOEYN1X#EfM zo`}%+-rr|4@Gs(jll5B6pVXxv>*Y4x9-Nb;wz2M^)be|Jpw97#*}SHCU8;w^M?^fY zAC3zn`hP0FmFDn8+^#>rz2UOWW=){ZLlCxL?VUI`OLhIldX0(scU^2umww>%@gEmX zUGu_Vk^Xu3-P~R*tCudS&D;)keuBX8{a!3j>4HOA%NbkowvFgtQ9S6BPv>4z8CV0s z>CpX#&g&{+t*I}&n`p;+QMU1(;9MP*--NI?e^yp(L_ciqFwB!Yv1Zt_oOX)8L};bg zc5#i?51)nlwh;Xb-h5CjN9ob5cl5u7dTr9ZaP%Xu{ipLTUMI+a?C+$|x}ImdQ$6P* zY(!vR{nVccb3;TseV33nPc62O_Vj+g8VP@T4(rAq!`!imcatp5 zUWIu{&a;R0l0H=(WE*XV#h)I83?9KcY)%iWeQ@|YDE9_}hWYq4$1u!iGJ!|_?1xwz z*6f>Guwh9FUHZ}QPyg-G5y-m<;W~teKbn8Rw!j)bw(U|kdwEq?R#tg1`|+!r8P+Cq zdd2ty$SLA)z>%_LL_gLEGWsU#soF<-FNO?QVC}v}BdwdNyMDAtFHh5Z!)TTt{hGW; zc+jOk@E%F)59@9it^I~>zI~MVeVNl58q>G9zO4VqNJxn3*>LoOmQebPu4(;RF@pI* zhnT2sSvW@eNmrgl$Y$M3T2>y1J7MT2x`rZn5yl~uZtY*>UOeeB{@sMj;*vO!Y1o7T z{lVWav0y322f>iYmpJVde;{*CDiU5JrN8|F+S-xJ|9t0uk$zfBaAYLy%6c|Z`l($v)}%~Xe)>Py zA13lJE)g<_hW_>kYBCrgtk)twYr+1;c~GJJJYe!|1@OaUBBNu*k2^lPx}$# zGNy=?{(uZl|LMzH=slhDKxxZsw_}k{fc|`ZBN;BVzZqjP%k%zNOVa9%B|XkTd0NlJ zdH96&^29<~L}k$~?Nj*$VF*HJ%-2R%KEH(4qTGu>d&MY_4(YC6il}0X4+C$P7J8If zEYb3@U>&@!kUhvPOQ<^v0s9c}FBqad=JY4TGa18{C>P^#Ib*HqxY+%9n4uHJvY4Q;(by*zI1Yo}eeuJFf$3bVEAC$SQWo+}T;E?0qejeGvMbs%mN zh_eH6jz6voT!+T>?fm&XZVZ&?G1$@S55`Xhu6YbZG+dg;OPH^K?rVR>F|E_;hERy0 z)@cC*^`{U}2q*+3Ab@f6H3(k_VQNuLrs?1P`hSt08t4GImXr2}c1wtV?5!5t z;c3f$*pBAdS7KhWtqeNQ=3WR} ziT}T+J}mN0Y5L7MSVlWm>WF}UqU}~Ib(%ecHg6@~u`ZJZ{P>9&_*hR*8?!oO zpRumTLVL8X_b1|i(#rp_hCa={+Y$~m=92z#1bV;Le)su0PSnTi|I?~}T`Rm< zc_tkH6)!!034AGS3|WUvw7!isvJu%R+Uba}lz1oEhpdI>{13d^b+r|;r9Bgle~e4v z+a_!D!q*<+^zoS1K3m@ZQS8g0wcT$M?-a^sU8T3ixC-+PiCA}OH2$Ntf8^U|U#xN3 zAozgd7uh(1X z0Qo)HD_lE)ao%HSL$i5LRc8iY4Iwo7i_GRsex<_)}-PN7gAMf6$_pV33saM;vr^E^dR`?&~>p(g%;;%Pa?1iCq z5;#$y>94ppgZlY>5wzbq#M(FyzF-;i`#k0{=kc2?wLm4E%8l(_MT~bh?;yhiDhZNQ|?Bt2%!LtVeOw+S6rDzIxvj( z#)t5~aN+9@?SgvFL!fc3X6uL<@WSsL_M5bv-=I2ZFVYdb{zS!~(-QwgAD-Ro)qhW^ z*>+iT9l+fAN|A5c>+$_JH?l4I`#Oks{MLC-ll49w3wDP5LuT+NKMnb`|7K7iQ2H;N zh0sg~Xb%gGX*^Z?vd?E{ z1A~5z))3b|Fv3XJIbMH)|HGWZJK=B0Kl(WGJx=c^%f=RHek*7XFa_!ZG+vwcgPcdb zn116XBJA!2nTs@p;~(_u@e=7h`j*xY^9B*UFNieSnYNLCnlpV4p#$p^|8cEV%xvI) z0eF54Vb4VE#j?sfJ!ir{jmv3I9PLY?_kJGg@%~${GdDuEF@oj9Z@p|Jyy`t0hJSkh zI~jXfb_P$Kkv<6Eu2Vh@kmEd=k_X|}R?5zv%8L1_$S&>{&>t0)`#qYBmT)hmTl{2WZ@Dk|5z)A`K_#JIERIJ76Xm> z&Xz@|UG>DuKhcFc^56aYtj3T%c!b3a=zt@~eH>3I{MU49jc<8BuNw8VD1S=Rc;Uai zEhok@_$R(l&lT{Kqc^WT1!I3FvaZo2kN=D|CBD5E!M9{~-zE^ZqZkNf2jZMSTo8y6Q9c!;m>#D%ClF_AaT_0x{rPR`cudWp zy}CdQe-zp^kKw06yKW@%2tOVZ`E*^#;{yM6IgfJ!*MYbq+kdUaa4Vq^(3tIyk!9tN z{6zFZAg`m!PXs_ve+mJGfI=Wm5Wssp&Asi8Kxg9yLs4yqxV~!!=%BNpgER!v30)Hq zpzBw};(EqLtkXX=o+=WcFU6WYT5_FZf`Kx_vBYEw1_R& z0XoV<=@?zJ%fmzih1EG>k0Q5|E)Abwr`^SB2M*W5hWkcr!+J1U%YF&V8)Hk`Vx=NJ zKV$zer(?_YMYN`z*54)42eF2a&bc8p|Me5T1=~LFBjPjf!+XSeH=drBKp)NF@FDv0 z+PLhU9!_$6K4rb17R^C=I?3@NAHZFugCbOj-%A>2Xy?5op?#5!y=15TV;w-yzeAGZ zL+6@j3nG`AgznJZz<&@z&s9r?{+ZJ_9N#vi;=(I~{?IwNn+c!!Z$H4>6XTB{AO3jU zV$ef&Z}$7e=_DWaw(RW#4RqcV#>*Mnz9Y25+vM>pNq`U0`hXpK;DF5?etbHxHN&6f zn27NUtY>6?wPh$7Xo^ICh~6cvhu89XjaFz3(C|Lt6crkCp1AKVyau^_6R%A53_J zh8gVKmtvx)-^e!9_tDvun(j{x#Z1Td z(Ps412SnQDQ}eVlUk^pw+HV$O_Ot%_W5VcOoO>K;6Vjfyg3ri}t~S_9@`r&Ay@+*F z5zzym9J`|39|;!<7{6Onu$m2+Gu z#@;~f{9K_tuZQAyWUek7_vA;6-sK1%`UY?*JLzpZ*^Rhv&>tS3T<>)F+;o-rGF?T!POedi zv5y2gw8xy4MT5B9{c*mx5w;W57s3W(Z}$0|%Nf4MHh;FM-=h2EyYWWWJu&Z~;XpIP z2Y#k}nDeB$tFZ{95sDCALipvw`RB97vzz)8MIr?eNrxugXg8A<}zoy_hsOJK-pmgOw2q73MOPw_AF>LBx$sF}Rr}y@y*_CX z%Z=Z;0g>`<<#`MEOlLhjF7hb@ELuYsc0kO-_Uw$(*6>$(_xpdHt2e($?;Mxo+l&=b z9QlkTd`y0W-PIZGFgLWuV1DrjU~2pNJiEY^fzPP*Xn60$0M})*TeB0YpcO`tO*R~xF_46EPRM>e77MTdV9`icZTW@UE{r?G;U|w z%aVgkd^C)(Z)3umV;i;-L+F4684b~8@L*JA%ep&L>KGGWi!p+A1yGuq-i(DD{@`2Mlu zFBoSh#XE;x_yqjsJ@%>ox)^$I?U^v8h@fG+@F5cLT{#oqcYIY(wKy}5Y-}3qS?2ip zGCT1ps@au(%SL{MZ>GQ_G|;@Ft$TtZP!q#P?Zk&Zp`-o-OV{te+2HJsSJ;UY3~6&h zhOPL3!RLIM|IW#@r&LFW=v&7APQby42D|YI=ulUjd(cpKgYKG*VNM<|+KvwdgLS-g z#^K%*Fm7OWy z-y`a!vsh>^AhpB8CrF=^X#UOGCq3c8?Ad7Zp(nvMcw0PQXQ&C&bb;^;(k@;$#~__8+!xbuTNy0dj*)XsjZS#W z6w~#fiQjKZw}xL#eqT(tVm|QWf4&tXhn!lvm5Y_b`z22o2GfNF(JmcbwPexG0pL)#&7eq z=72PCzgaqnA;~RJC+Tl_fxmt>NJt}}{-_@Q7e$)|p&DzPJ7XTyHT-~!qNq8xBF*s&(nPckCYrR6pZIm? zB7O^}KFnwx9S@D?c<9^2ES6Um^BG8yqRH{2y?o@ufYXHY);EJD`i8U&Yx@$uc1^@@ z@x;gY_X#nkwHtkpYTY_g2x59Zh#&knuYX#1y+jT0OZay<3-}S1-(Zh-T=)E>7=H9^ zF2{RtznF9?lENv5AL-z`tl!W`Ac*N%;CD8H=6gK_?3eJPedR~R1WE*jf!zfuh96;A z%DNXvfI{?-0y|m*kxKeU_|+inbK1_?(F0?#B(Qso<5%@$i}S$!v?R~(FVV}xAl7}H zMHHfYthie9t22S{gWnHU^YAdQMDUH3nrR}3b##$+?)u;F0 z*@RzN<-u&z9lCx3X`gXI&tf5NKx3r%5r%i#;WL?|3*TNHLDvSt@8wk|82R-2jh!<# z;p>f~#P2Woh{OSU^8b!HcRP~l`Gg$>tZ`##E7S()`aQdRr$@q7l=#s*-K8!Yrf0Cm zo$w=nJ{o>MeR+!>c64tr>zR1}MN#5M^uxbFiS(CZ?3v62P3t&*%gPFk@VofnwrwL( z6<51B-buIWg+cQeOs?7G8P-Hp0yoM|i{v7c6%wDP)4@dI`%it&BB zY5XFlKV&^$<5k5RgO;5T=5hQ!pAG*3`tAM>*~N7}!-zR6_?7>1rID}UE#U9id-puk zsCvuy1N#oH4n6<%H@cVn}!}pI}cf{6>_voI}fH~^YE+_3A{Z{zm zTJ3#f2*-}%e_nZ14?DW|E9+4v{dX%`9kqgAt* z;U5HTyhr{kLwyvYZjR+j{BE?f1BYnq1l?xuZ;gcC3!6I8*kbaJU)^lvKl)ylZ{6qQ z#UpT*f=~PQ-0Td^Z@;}>&+ik*?;$7nU>32qiXWB1vqz7qJan^x4laV8SUM-C?+e;F z6uY3lcMvqaqxg*_J=oU%x_;pazr_fS(!BsCmh_LFEv-6+Fnr~QnYubi?{rtvb7p^- zA83zy-W9Qf4-)1p(Dj8x*iqe=JJA;jc(9CL?wzd(zaufHRA=B{cNWVx{%!R7{blcm zI)Y~r5QcP?g@IoHZny6_)jvQa_>o?X@cSE?sQJ-v>eo4+v`1Tapa1CE-CLopix5^3 zW;~PuyMIdz$H`PL`3=P2h*sn}?+<)f);~@?${bEQXt0MHegKMt^Kx!>6!{I<6J^FD z)FTA3dwJD~Y_d&2PaJN119TX za|_-Y$bOi46Z`D9e(ddkda>7*c4JePpRT<#e1vlcHr+l-#3Iu4Vqn+9>Aw|vVzh#v zs8`yzuuU&|*hS7Aqv+wr??sHCV57}&jr{&UDBqX$aJPPLKia95 zoPPF63fDm@F?KmTsMWYtemm-!k7!|IWqN+TJ>(!&JG#z!C!qC_Rs0* zAf4soxbJAz5o`XWZ2S&9Q(HfJAlCnETw^>pf5&&nKHHp-B)yW2AHh&kt@&_$WyY_U z>76M`I*D_oN^M#H8*ee#fgkaJdQbFJ?X%V3n=XyXFJWgm``d;US=fc2+ozp%{HZp# zsy{dL9o~Z-vyRFNR(j9ag&(zrx_jc=c*Dj4x@(ST?l1O*mwHUa@t(c-(YY0wKmRiQ z4!z%Dy7wMCt0<}F>qA9jCw^Cdq3PfiFRebFZMj}|9XNd-KRT4AWV*H!Kf(dNuy@9I z`tLt+#xStb3u9RC#QTAWR%UU)kNUv@^sUL@9C-3ku}`%0C%T;7_z{0-qXTqs)#8_R z{Zo)mKFjjPCK`&U+K&f*qt|O^L_JIA`!t-V`%bq8-$N3<7d09HjeCd@j& z0r2q=4GRe^6EFPSHQK(wG1GqUi}lUAI`}tq(9-(fgaQ`Qc)al28?%0B>!=dn-dku* zht6-Ye~qrg*Qn6#KZEe}`a#cs5BfuOLz?~(hFC|q9oo9T;^oI_ z-Aia&bo~+Qm0WAExNd;<2_Zaf#PzHAZnhVHMple^+au80@K6kC{qJ$I+KFd9pxce$ zLm<9}BOJ(j*fXZUZ!P)odjf&l3k{=L_et?&T}oTfvt1D~5Zbc*7hSz~Ib2G;MwI}! z9RuxAn__@&Cf>J!5#8#(LO>y)5Kssx1QY_XA;6Cqp#w)^OVgJigaEx+%| zK^iycnP%y1;l5ECr?dpiifObNyf3D4VoUHo(*A>W&@PCpL(pgt)1b+J$c{fxO#2Vq zq5B1_0(EI+k@g?HL)SUY(uDyWT&`^9KYRzzbjfb?AHGBBcJ;yZvF+;cn7__r+xUX% zliHLMMp|1ZwJt<+fiyOyu|WDXNMA@3?ta=_E|3{agPdSGTS(^!>4K&-Rl}dBqCB0g zo6aex^K<;qeNFY`h-vLyoj_SJUEf4QPO~(z8+e`{7=-H_I$DX}r{kA&(}g)r z_r-L1cGG<^tudqFnZv}i#z5eHvoy!NB8MLm#8?Gmjr{&HA&n-rKTUv9r)gL5H3;Ph z>YN{dp#BsB3ITy)5Kssx1X2zGe18h8sma^}>Cn)=s5UBBaJJ(FTFF0YD82le zzT??WgNz1VYW}<&mir`PY ze2cXbj_}2mrudT$3wMvvC_5dP#3It>0dvEI)0@f0Ke z#P?6^OmEzLkGN~J^H^Wt_gq_8D#K&i`dwqx2p!qM3 z|Af`Yv2B+Z8|gpo!E>~aI0^U@?;o@L`1s!ey{8~-y@&7v%pvpyw`KKDQp z{^S$*SnO4d!15x0w8*FRJtZ7_>ig+z0JLos-auaD4aKoUB=|R+uj>br^ZqL9ZC@XB z+{N3uifXdpdn41w@yGsiW^ir|r|F(JLK#(4mhs1#3X3N_#v0Go^;barVt;GtK~WK5 zp-_O%0**b!A1;7jM&olZ$R8jyXn~@1!yR ziYENC7|!z4qk-RB%z8QJd#RC-E#r?qcIJ=QRFlsRe1qgXhQ?Ja*OPI6`=gASi|T&y zhCOM%q1l1m3wa+1!r|eTx$<4Hy$2`&8%iqLOz!3q^uIeBk!O^W~q;yCj?qEW-EhU1jia z{AKvftA&O$fn$=dFKwGL!S>@VlElzM~?=|HTa*={Ms{x35z}n7L|t?>{G6{d4=@vyMPJ zYenq6Rb5yZKh|9LZ)JU6j<)Hzmx~mCBCw>UBl+$5_#>R**VxN&Ec7Y*tBC!v!pA1w zvoiNX{R>6x>CC{n#^-r(?Ek_$$S!dQF@^@C!JnE#xttPT=dG(=e1`KL8j}s2#7?o^ z506{Dw+nO2T~PP@=GbSlPv>4@bPfY=m*&FHPnLe4i!56Fr`EXWxA8M={>#_~rm&vY z&Y5vnYTwjPK%Et#*uS|rk8Qcm@GNhR|GQ2-SD9n27e|Xfe3Fw5oaFZHQNMEZ(;S1) z80#cT4|sY%y`k5Y=iq%`oYC*n`% zVO`03yd;vAG->b}HU6X5Yh%Pyi>o^}tR89jJ5Yr_c~23L!Sma>%KYcIcSrkkIo@LY z?%!uK17Gzb_LTOrbB9L!J1^3c$aF0D6WyNmne;pGu69PuS%!0?53$?{8k+;zktA;# z-_RbFtq8%fJ*Cl~u`g#`#kO7*c5a8uU}*#Kkyv2{&Rj&$EhX0y|%0@ z9>j48jhEva;V#8B83*FoVuWghX1LS!*mZ|#bDCk^>%@BJVy>?vry*Hm@EtS$*xkZ~mVZ@S~fYw-Ne zZeQm1p307`%2sB(M&Rsq{0{Gc_okC^&eFDjOPH)JG$w^1?TDemCyAu zI>QOiX!=fPZZCZ8A-3%bOZ_~T!LJx+9*A|1M(iZrec)lO{{jAA!dJB)zb5_FyIxtO z|2{yz!fCDe3infy=sU+AYb-pOf1f-k=`apQux0%5{fqjE&*xmurY_52%#ypJ= zH{3UZRXsHn--MrIYljZjz71DBIaK={t?%oO1Y@IT>3qXJ%=L=vk-hSh5??c-BgWr& zt&I_e(cGuGyQv6!vGeR((_)5AJn|igKjx6VXXE=#YnD?K4+s+4hJ)}YKks*AT)C?g z_P54MC<(@&=InpgaL@DZ#?FlWn{m8wlhsZF@!t&GyJ6if`OSA7vqM6*GRcB5>Utu? ze?Bl!-?VlE?gv6| zyCR&1^qGNhI&eP$^I`{;&${|#tW()K?}K~8trekpoc}l&9@|Na5dUW1U&0ahQf&s! zOTRX0<^Rmq30_0}PrClug+Jkmy3fGe=C(zX9|`vk@B{EqyhDiuDS(~$4`1DmY~Y(W zEAq^K2YRqOzfBMViEY+S{6}x-L^f~^zE84cV+zCBz;t%fSBV88L9N?~Ke2@R+aS!* z)@m8=m7ueq|C=BP656cY_>cBw(VD7TuwKz@@5Tft`w6p4NOt4j{(x34cTE<~?ao|2 zW6ova`k*@4zC}4d#-I4cdQR$x9`vr?pV4O|ayU+vB#n6CKf>=b=|%cxw)yr^M)=eHf$8)+5HI|DJhBV* zLHi?YH1QX+1mD=&N>VS!R}(M%35cTgyV3jDtJ<7I|FG+s`@8ORJakeBj3@pjRoWW! z6X8Fp{;xM44A%$E#+*dle5yzRGRjKgi9eC(uGQ9yemrH_8R7PE(VX_VcKefv5;Cd9 z;*Eb`KW{I0b;tT8jgXTx(GFo>VX73Ey#%p+2a3E^(NJ<3$L_PMs(454A&wlF{&IaI9#Y@LBrtDv$ z@b^?{`-tx^!TLpCLD)HoVJug^PZg&KG*S2;lG_?{5*c(((b}0mUE^QZs6Q{{!@kBW zr{E=JeFij)8^87_w&nWK;bd?f%TK+vxQWJ}YyjWK4hJ1u=6!HaxV1vR!v<1r zorDwcch_qB-`}CVU?#p)o$t_!6e@y0C*bd?ZBKpBnMKtd8p+2Z^xP_p$J`K)-eR{D=4-(5o-nd&NF&Uher%{3N28a&kn%WBOzhgiH$>ep`%ac+zz%d1Hq4<-& z{fclc*3@Fnt^H>$U_Cm~KE?9=895DTlveNFksQ zPzWdl6aoqXg@8gpA)tojc_07M*a*KXr89Zn9qg#^{zk~jEU(wejm)#xpMr^Bkc>O z3s{AaF4q`BHtz9sE2IlGFqp3u>T~gQVRoZS0}}TO+tmlt(i~^p=giQ33iKD6uH*7T zK2J9y-58K*Bh|;#)P5jMb=mhv3t|dMQ!oqIO4Bg9P=5*mg@8gpA)pXY2q**;0tx|z zfI>hapb$_9CwKuLMxpKhk0|<>KJkyy!ZIX+#$f*U7R^(rg=WZxnpBnC! zz>AW9KvgWtpZbQMN7rQR9Jz)$uY{6+lT1S8@2SdWzfQZ0&HZRVvoP<&d$!d)Jj{dj z+yw~du%4w!J01v?Kb?C;=QE2u&wu-YRSg%E3<~f|Jl_J}W+^uw2$g?XTf5WRghC(tv+8dXEc{0A z2-$xn%Kz7CcZWMyhL3a9{BP5kk7FXnG$(uu{EIod4}ibMogn*VP4drTzrT-jUc>W7 z`E=H_n*R@v(`@tsyF~Vj9N(vlt2448`%PT_Zr>j4uh$>4BLB}J|D>DiGD8&&;h{{w2Q7Z0XnnPJg)gP4Jdf^Bh({mMnk5ZwBUr9NABf zUf+Sva4J9u&i#0+y0Ep+4+$rCp67;dyQFx>wIX-^j1BUy15VvocjwIqVGSd#b^RD2 zIQLUlc@W!l`-@!0;o`^HIm!2}i_6(4wn6@c;Z)Ycd2_<+*(s&HP<9^iGgD1T4YzHHU#r;TJT-k$@0966?q2^18zQU2%yJcS4epAQ;crS<){ zf!796{x7XQo^8D*>{+f{2gskcWBRgKWiFPFTK?vJCSkXP_4Fis(+JKf`VaX0R+K%Z z%V%B58qe`?85_m)ZQ~Z!GxdCylO9ARf2@_>aNh{l*xM3zcfb#Q!uo)sYVEwpT=2O? zlz)kDZ?<@1*z-!bKH$s%?~$U-q*6L6`S<3{R?itB33K#!|y#l)?3N!FW-+Mk3 zWl!met4?HfH-&qI>H=Xk%y;y+#4UYbv8LixTxhUn| zu;Jbj^c`ivgf+)%V?kgQ&WSG zcRr-GZ$6*=aS_A5UMuq70Qn#Jp6fl^=}lw%1n(rfC-UF?1>93Mi{v{iRY+nY^GTkrl=JJ1e)d^PmDBg#C!+LvC z_MKl;zF6fysy2(p+3=NxKH$q+tn>lPU{@VQ8f2s~&qwci;}Dw7@#C6#s`kMezXut^ zyan{nEYKRZzO1+X;}jWu$65#QiOK${eRp+MJJj?0__gQ*9vp6lFU_Y9VEJxobHZqw z*7X!3H2b!XYy1XuWaYnH$r>)SH0C4Pzr=h`TG&3x4j9EOe^2!u=mWBDqCP;IS2G_6 z{DJXfTWUYnKhRIIr~Q1*=J#=}{r*o~mcup{a$4XYxlm|{R%b%Dn zs>#?5bM_9#~KkZB)4bHZ1!Zqbjg9$&3-tkW$E`h*!B-r|)hMPp;a=D?7!k&Jzr#2` zt0UTZ1A+WfhP>y|cyC|Lmz#xe`AMkD@{cUk|hwbn^?|-2ACxjMrd!!2_d-7k{aGvGy9_he)EVm@8z9@mBlhU+I z`4d5IUq)Nh`RmxVN3iNgP5XdN!2U#bwmU2@#kCnUzMwTcH0Qh?p@q%UJ@Q>S=hKK~ zUxE3E16Ysr8S{x?12<{*PxTg6Y3psS#rwdv@4vaxN*^#5>qHIbgiB!apz&~o_Yrj7 z@BF$z`|WNir6MYTDJU&XLnWRuBdm;*mZ}q>c>p` zfGWt~pwRCuUig~EeBSQp@9EucErPDi(|wxTTUJ(RWy~qco__Nm#(Sut?j?i#Vxs(ef=y@yt8${XD9F&LUj!U`yypieyu#_%;O$U5hA26Hx0N>*;h}i3;S8VNHw3oXk zb9c~l9l|UG!@eGQ+8Q2OgNAZ?^@Y1f`t~05tu5<4DpI&o?s;Hoi~L9W`vBN**0yiI zyWXr1X!z%qM`yTwohm?&?mHdHoC4YWn-#fih5IQhYqvx7fMowA`ut8#+@G2V<+4lu z#EaWMM>A{8y2F_7iST^@eK(+a11{&5aTcR*jO43j%XP4MYx@sG`IGFwK-e>#=6B6> zDk}NsdmAy{nuxMz75g%ear-i}OE4!h+LyT-(z}hW&dkJqt1iet3gJQY0rlU0cU@R} zrMUun2G54*Ko%Q=vHdsSU&m@58D?djktlPXCfR?Ex&1w0`(}`&Ren_Rr}f|Ex6-(b z=3Z%?4f#{q7!v5-=0F}Bv+hvlD;&z@p&M5|^BjKH4-EG6WbZV7ta@^2Slxkab?Xb< zC%;5*;~l>v{P(AoY^^X0`9%Kv@0Wjwof^NNp$*Nh$%ezt$u^wl(Z1DwH2-{t^EcSW zyGNRh8^Xz*^5|Rsi`a*}tFn82ZHQL>n;o1|aO1%Io*_rE2|dRPslEOFlDbOh>+xe*fTJMst1Bf9uEI{-+n4 z{O_sSoXpsDM=+WPrhT?kmY>evTAa^j!`GsPb%LR?jO3br>*pcluw0Myb!OI)O^zp{ zl|S%pk_l{;_JY#fE3K_2-Kf9Vt(_xOe+gnrlU-vC4#iQ7-J9AH+ijVq{(d6Dp6r6~ z-xQKVI`&g3MgA@2U{zkSo+F!If^apyCG2SSJ;{Dllc$9gkUyP4F$N)vcqXE1l$6>%HZLw&Q8saHuL|3A^k?NljiVO8GZn zKAq-P+u)nZE(rS|eJldz+`PRJpO0`c!X*fO5za&Cg>Wjuku&C8ei^<8ZFzHX9@~0l zv6VT1nV2(5%k{bPB&}al4D!EAzee-BUcW!SZ-PG9%=>^Xkbl1VZf}llq)#M5{^K_9 zMt-@-H_x{B7oKay|2Fjz@1SoCX0qMEEMw7-nj{Hz;d z=?oUyD{x@wUIE#dq*HwfmA@~8_Vx_In!iTPH;=h5W_s@&j6Ohp*O#QEiIhLFfcE!8 zsHA-Y7WNF&IPhPDPLkM9`)U#`|Dx)w*6`y{f;D7~7X1j)-kp1s;jGzYP_9JFf9R@C z)CU}cu;Jy^UD>8PES<%LIpI<@CoDn15-opX0e&ROU*~JIw`U&K62cFI*_`r|^Uxcl+hP=L?+w3=h^54feK+Orqpp{OMKe13; z+llrf%_e`H%f=L1k^d%q7y4f;e&k|+E+vXON&arX@9vvHQ+)(}+Q;LpSv4mdr8YWA z{=`DDuR{mW^bh&(TQmGwD}8_u`+N?refZ%h5lHW1PLqF0mF7>HeEu|iIqNDb{@Q6y zcqrD0sX5^&RM~0rCl>H7&>3{CE~`A4`3l1OjezW_k64Ls!+S=-e%cptr2K2N??NN! zyO2d+vNR_=ko7D{JO7=rDzFpfPvH_rk|F#sMdTzRjI{bQR4t>8sK#Kmu}vR`hflj z^&c$g!y3qcjJdy&i5LfLZw-O;e#+_cC+Xna2Rc_|zW?lAOXqji;JeUatXpAv3)ceo zNg;n7IhR@S@lSKYBcKCnPS`{Tl0p8&0>%NIKSD^CQ=dAn&tcK6e zy-nCp`|My#j-2PWT)0C2W4S5X}ic!g_ktoNyQ|PZs&Vhhx~?H5mth-u2_) zQ_{jYqtr(%g5Qwc!?2(BS;-=Q5){sWY7Kfzao#xlWybARe1X^FyWmX-X)k9Z0Fp`m zqbfVlyWsH%8z-(hk!`+hl$CM7EXaRnBmUFAc^!Xs^H@@84%-uK>*n zUx4|5bQiTQ|H&r*l3Hz@a4zWHLT5W~?QdyL_%rB$niJO10N_aTt6wAhOaWu7&)~zE z_Sb~#Tgm@V)c#*|$cX3`&K#k=0+TQ&JZpJr;c^pNi4XEkbq4FDz6*r{4IJq=mGIQU za_^Jvffb#L7*75)uDU(6u6SJ+`Dh%Fu>;0|ZE*hAe=X2Uyi6uPvGJlYS=#|lyC7^p z(33x{v8cd1RU!7^J7T>ZXxRZYy=6eJ$U`mW1CLZ^zJ=FJ;HW2m`aSp+;WmT}mgk&x zcC-f+H02`DelStqq3K4*f0UXNHkJQQz>)lF{~QP(BEA@Nsk^WviuwSrUcm&crm*z?k+5|(McL&yOs-r<6AK;Mv4ndkl`a{!sYChP}!c@d51QY@a z0fm4K#z7^-$b8AXqf9(RKJjKcI~3)>ze8hT>Eha z9vUEZF5~MQl=lhwq1V|V`E*_3(%5EHKczyi%R}}fbToXVc*Nv{A zez;r!q`u{K0ZQSo+|>;L)cLs%Ui(}cD%$lm0Hh-1x|}N2{>ohgkYCvBItSzg)C=`L z>6x{5rd-KCOIC$ThzVJRfW>a2;$ea2>2Za2>2Ja2>49 zf8A8vf8A7E|2oKxhDT!%Y*Y(8SWp;7*G=SW&liSX2hsJ{SKi{fAjkl2_p%DA_jcT)_^uR<15|rT!EG3ITy)5Kssx1QY@a0fm4< zKp~(IPzWdl6aoqXg@8gpA)pXY2q**;0tx|zfI>hapb$_9Cy)5Kssx z1QY@a0fm4hapb$_9Cy)5Kssx1QY@a0fm4_F*Fv{b~r)`F{%<<5P*DF z#VBL0Bk$NkpsF-s2*}WjB45V1gfXBRHbp?$zbQm%`+yYtug=^udSm8ZX$zvm8CUl2 zZ~-NdJQ?;+brjWP4M`vys_8HYDEkisPMT-QvVYX$8@o2MZJNXBNcNTeJ5nr;AWEYB z(;U$~j$ld=(F_4)|IOe~7Xkv3?SE+@b8RnFuaXWxK-s?ogy2vTB-=lY0j_W;t7%J? zvj4Ody(!~n_RsRXEXL5tdX}p70j83$?*TLWue)h4UQW1OcA} z&Bo9Ey*}1wtXdzC0G&|wp8(L4q{%qiKV$h`wLTyLdZFw;0iY*IlX12GJnvH0tx&BG zh_h}e`;Rl=Nz`1N?H^-+QnfxH&ibM3KhA(BQFC#(|2*&PB!VX;NLBWq5@NK4i3Hd` zW4WcdwlJeA)kQ$rzb-85CJ;!7{pWe7De&WmfU^JiVV!I(CCL66>*+Zr*|17hdX@br ztK{sbJ7M;p=bdOjcd7+KK-s_210w_yX#d+-e(^p=bgBFD5K#6n56e`mDS`G+`+{Cb zH9Vcj4`jSPoSfDEKPO5pJ~V~X3(AXe?{E0>x!xL<>unbwPSdv)$gwt@oR$5jwQg|V z?z*w9_V4#CFfe`5j}Pat{W@j;@xeOTS_!8Ylotp4|Ce?3?vQM_B@4Z;F;{Cd8LQv_ zWRa$PI?Z$=4)#y`g8IwnOw~vT6xFoNFq5&e|Mb-rGu?=b{pWe-rmqC-<$RQ{;{j$e zR`zeN44hoMnQp|%{%K!OZztoFthBj(nO)6ftn5Eor5iilX1WnK`_J`$9y>RxhEN2G zYckI@ld-b@P}I`r+Dtd%X8-gpAiu0@`XC=4PK$k+H=D_LYyToS^SsLcZ+wI*u@y7j zh^zhQc_&L`O;rU1Am69VWW4QCx3d2<)fO||h_n6IvtA`#(o__-uw7i8^^%#48!jkO z_HPUN&ac!=H{xvnq=zp$AGPG65c2&#oQz9+o#og*y}#vp-%cK66s3efINhMU1la!u zcD8$WB^@~k6jf(dhLiDx)yK$`@yl|kR<*t z)z~e`6w0CYzl!x9)h5ZHbR4;EUq-H(eBb^1Y=MbA8!at zxXLs|ptvSupqYGU%(;x?Eg>$w+mtBvfTIvF(~kt&|DVjoS~&`=BqPRMojKV|zCX;o zNrK(;Gon40B^g9%Pp+AMB+&k`9>{ZU+6yQK0E(+K7Msa;-iP3HJUkjl?01 zRmk-+Gucvp{A1v_Rsl5qRa^ER;V-UHG?7!sMpx}POxvX#2_C*S+6MDkTtaYDdMM;u`PSXb&P zjuRXy(Tu0I!{KIfrTl60E|xd;&o6x`CBREFCd~B25%!M{Gm|UjFCAAP&;D~uFG(_( z6uCgaOivtR|CnE5ClB9A2`xMV#g!fQflM3Cf?#Q{>rxo_8ZFEZojXI4D}IAz-E}&a!{+LZxTn!sR)?qio@)mz6D(9R4fxsS4nlof6e5{^A}BiM56tdmL!^Y z)qZpcaD6a}9cTY}p6{aLH1$gr`!dEF$uac)rn_F0XaBjLt5YA{)Z{Q!FG8=KXaBU< ztmM?xlt%PiVZHxukllt*`RQH1S&=Kx_Gyi_TK6Bl;1XO?Pd~(aj7#_QZ)hlDu4^fnt= zQ&roHK&XC%Udyuo+6RZrU2m4>-OPHFb+Q?rq^RCqo$*Pi+>EYgeSVv~KA7J9^E?M7 z1xSiWT?CBugx{BG|8FemA$JUrTRKRWsWiG-RGslZE-NdsuVAQ5`>#kNs8fze<+>@-8VU7>i5+(~rz#$n)P{DACui|GW>cMKaZ64n=_LhEXir{$F2m zCTl!P;yggE=T3)0Ydcw(^FPr@ZhH6sne?d4zE_>uZo7o-Z$M8^#Cx*spX#o7NutlW zJnv$5@oTN^N6L9Ez`yVNqO8KD$F4h!)%TNh&ZqykzXrJP=sZ*uMUfy7PG=}jn*9$P z9^8}m+R|>4#{lWK_cYSq{vW@(MVc%%oyhf0iA2xTv(7{yUiQEFmQht=TT=bb=6Qc| zCc5@e#h9xVunc>yr-(JBeFZPbwf}riUwgPywZ;>HP(2B~mi7DJaIR+&e9~><^-o=f zd4O|d&I6QmizjZ0Yzw|UZ{Rpui9ej*N3QMXd3~(g`2Qpl1J$-11kChDmi@zj&wR91 zBDS}9qRjcI(hubz;_O=7zIM9?{?ccxm8^PVm2Od2^wKizpJ;+_ zx?-^%$={^LS>-z!pDXfZ9uMpqMEuOsD~q0%ZU2kZxs1uEJ^D18>5pvtFWHo_H`=V@ z?M_*K25aaeZ606?%Nx6wKBvijw~)EE2WCHU+^od!Ej&lI?V~==gUOAgqFzM6Opj#S zKeb&{omC{ZyXaM^^8iCc{!%6FuE}^t#LP;1Me$Rz?SBjFJuWL%FjaiS(f=>z|2W>G zH)iey{oTxUc=GboWsU)=Sf8~r^iDQ7-k)z?95&>V{!SNBt$Shts^Uaoj}7zPRA=NPGSugNSgHg{%MVS=Ex&9c%cH2LJgm-`!#P+3*0eX6lNeV%L zNUZVM4mi9gVqrD?`}_CWVf!@a-#=OKQ*=g-fSG>9!~RQZJ9Pvd!LutSt;9UQh0?|V z6C+0@F(uFs4mFd9Uj9daUSQ9-KhOJz`t47QF2t)CbyR2n#}t|o+C+r``z_&@M-p&uYEIYRBQd@%Gs&4ndw`6 z?Z0gX=&9v2OLh) z4j2PGE7COoElFd5JnwSW)w@F?u@>8gACv(ipOK_#ofP~jhT5#G>ngR!Pn1+@r_J;( z-u6GHdbbXssfN?_@~RVA{l${}Ry*KZ7JcJ`?GNMpMH-J;cc^?{p3TSqx2LwoEAB)D z%=9nb_D{57U%=BMUGv|5K-O>DT#@&9r-!cUvYZ`K7%Rfq1~@!7R>GH1VJB+%MRww6*=b&N`M` zc32{?QEes;0W&>Jg#8m5pzA4-Hfg$_>-{|5Xct#y9gBJXWg`gnUfLO!sEH@L2W^Akxtd%DfGkMmPf#a}|a z5bMoQ%|NE0%kgxNc$(O@f-StNTbAkZZFD~rT^Pr)?=V|n@Dps=~-XI z!S?gKZ_36%)t6idnCWJs?SE8dhuuKqdQRtrHOI1TmlezSUEILVcJD4rTE(>)hv7H> z8%}4mF@58k|F*O>PvLO-i}fD0n=JlSJ;{lHnVu%v{s~o_7eK$MjECPpkha(TkyxeX zWv(4S*W(DmGfg<1k;Y>x4`cs&B%jsS^qw#F1)ErpvZG_=U6oIM1k7~Q3HCpBZDw1n znXBZql8?)6S4bHHtio?#n`ot2;>+j`n*QW;#S)XxgQ_QoiZ*IrTKV&eR^%yH&P->W zVE+VqkuU2Jk=A)1-XqICZirN<#wJekGIakCaG(DVk-YKw8e~s9F zEa`D;k7P^7+2p%J@0}n1JYsp*3B^QRrv?IMI_xO>*Tw*uYdOtiLtC$wFa|L2b>km% zXnXWm@HS<{A1vs@HVzosvLDj3TRHa@u%2T&q=v>SE@UELrq52Yf8x8iCS#yT^KT!^ z8Ur+)ZRbCuT-$>f{O%~W8Eu-r?fv@6U99mOY3H3==|k4C?%o4r>Wr!@br3MqWvAJH zU=M9@96om4VQg#v*w4FaJj?T@yE?0Pu`lZ_U{Y^}OQiX4ROGUCLxxzfSDDXjWIa45 zr;fHNPGlosrpHdRf8rnS;`fVm({F05bT;<#vZ>2*f@=)K`eR9Zs`h2SeR8+VGfk}W zx&i)sb7bp_sxP$=Fw^Z=?J8J4*E?Spa^4kt?F_nCaoWeOJA!SwCiXD^ojbzolo5aO z`^Bv0q2X5TRpxWGEZ=j6EWA}csf&P_o(JtW)_Ew2_D|Bl8Cmy=wEsFS_AvnY8l`>4 zV!hF%$vxcBVeS<{Jt#v$M-H;AO9iV-94CNdlY+Rt}d?1 zJhP-K>p1+DAA-HU`_dU8G`~mt=xAL%jq}M52%Qm1&$z2QoP;$%y^!uxuf4NAurF-Dm4=7|lv6_m8L#pyz3yK9!Y)SH$&@?dO-ClG@s;xRZ^5Xg_-C zi1pCG#%doN9+6Gy(V}xNcy2J|X=8xvV;=+jF!Lr$v{HMtM}PCJw)x_=+Mld~504XN z>xQZ?^%2mqsb;$GaQkmYEAb-3SJsC6M=<(UZ-fnKz8GREQMzg*F*?vkp#*!15nWy`&V3~L3f{w~}6oaYm@ zhu;pXNS`{nog2l;X8**;FB1G^Rt|oSd8_tkpU%6O(RV=^eTpnBwR-ZsZgnR8QEHl3X~1}YfL;2a>P;^MxSbotvh1JU zsc8&t)Q;@?aq*qmWRo_J^Mn1vI+o`-E4?&Sacd_6W_Bmb{;3{XUsL(ab8OM%N7&Es zZ#fNPYg#MguHIXEzd+yQY&;_~UiL1uQ~Cw#Ub45Hx}fS#KLp}w|3-ZYjkBvCe}Szm zdYb(`(SQZ>Ri6`v(0#L>;(I_%a){_8yx>}KL6cc zqSo@p!wS;3ja0UOyZV**)a!XmrhoGE)o{gmI0Tga2j|xKZ*HRF^(^1pKb*`|o5I?O64mAWFw&LKcV)IxqZ(_)@N*6 z*>Y9&DFmz_pzJ?k_Fv9=dXBLoBlV0zz%B%o{m0$@|AB9=zIMq|)vFMQ1Oa9LakKx` znAg7xdvv-_=ezs=5^d(IBAg-!}VShPl0aS^mqr zL?ahfMj;Rn1eE=mUjv3R=9mUGIDytBP zHv-E3W3m6z5|&@qHQq8)?I{FgA)xF(7W?-;Crb{h9)&<65K#6Xll}XY|II{bt!h&i z0?PhlvVYnuc%Lj;sCpCv@kSty_P>c@sj zIV)74dl~EM-67s`Q|&1PWFQbX``0=Dll)h+-esL>4h{XnSvbGy3fR@{STFMszM&05 zJRE&SG47QiOhA~3HFU4wT89)Hi3GGw9ZPzb~ufkfJWym&3D$vj8olYE12k#J5J`W=5ilJEUBUc9Jw z6aq345bZ)Q9n^8I>(RzKzyC7%2v-+lu2x0WnZJwt(K)};c=KbDAC3lf<$k%y63_C?^-OY_d=)(k zfp7?j_Mn$eCHwcT-@P4ptP%NKR`#UKF~Bx_lTc@Ihf_F}=O_e3ThL3VlKm6#mdu60jmhArJ-u(H8X5sb>G9Dm!!rpIb#<*A5*luMf!e*065l+lPT% zWhn$4gMerUdg)ZNe*)fJo%yoJ>x^G7muLUlw?NN>juEgTL?I9c0nrZh(y3_wqc?Oq zO5~N!AFg?1n9M$4h4S+kM(I?RV-OJSKrfw&_D|rut1~_k`Th3W>t)(M<^ep{I!4Hf z5QTsN0-_D*rBl`Zi+mY9M1H+h`?Jlrjgr|1%wsOr$^dqCTOr^G1VkIqOQ)*+6ZqhF zmdNw$&+d?E|ESAzmLr6$h)@U^ARy|$UOJWSAKwBm6?q=F_DHtvvSNvSKc3t(mF*vEf?F3?XDk-^p8xhhiS|$X13f1=LePo`g@6tMqVDUZQ`!Crj-u+U zyF|WUS#b($=p%DYuy=|M?CO?6z!3s?#ob52zG*uXyQknPY%@_#xTP z5yDnPCfe+Iusqe5U;mkI$#ARy|!UOFA@zofQP zNASK`|AWDQgNQ-ZlS5_p0jqFUKpRI0T@j%W2p}NpyIwk7?4RH$_GP{; zV(`^Z*T}Sgi~&k-REnn%a1sKdzU!sa$^MIKI-D$GP~z*%HVzmmuMdC^G_@w!N!qVy z5%pa!olf>o+JL>mUyC^WTz;!O``7jcm-bOwrx0)o0-~<#rPIy+i)u2?6LA<*d6?|; zf@V5J_KFULfT-(w>2$MyIx7G;%onlv{eyeujsbGZPEwku5O4wlqMqxe)6M=#CYT4j zQN-fqRb5%bg(dR(fPC*8PLRE#K_MXOxn4RQ?SB+Iunn+SCE~GY@+0!>KhN8M^*{$I z?NbOO8Uazq_0s8R|0I)QU)G}{9jXXWv|*+2#ETv zmriHB&*{gOH0-|o~rPJO1ORBPt6LF!l0%{)|E^`d9kQEkgr!-L^kO%}s-PTK|yZw_) zfXinhHs61JlT7=kvjQ$mgzQzD3IS2C_0r1z>04ljd=VS(`Yx>QrqL4n0MB_1@t)frcc_>5n3 zG~0HmTV@~dZhC8?;yw`wh&rv8R`&0gN+EM?4}6x27%g~H>RADete5+cMCgEOGkp;d z^;s{i?7vAj@D6aVh|%Qbr?bX$B%T$J?at#1*?)8Vi)*q5i#WZrsFzIp$5{d1r<=E^t`q|H zAt36qURv3IbL_nvGxx-KL0d$uR*!r}W*<<=@?YMid8_J5Az%*zq8{s|mHoHGz1WvA zR>bPlxtGYae_9W8XUkUA9fg2=1Vla7ODp?tiFt8t#$mv!QN+tv@Pf=fU@^P+wbm_L zR(BKvauE=9STC*YUmtH^^`3~=*FQ);Dabo~*}pN?!0USvvss_tCe!{g4^Yz0xOJ7I5QrTC zQGfN)%KnWp9_{ORk%-xtb%(O8R~Jj{14=(MZe8Ul1Y$=()L*@{vVUWYu_kysVD^`Y z+aK@UC(-`BjjU(s5ylj#9ECt^2#C6?msa*49$&XF^A-`eNh|-$8qSk=RzRM2TzCpp zu0kMs1Vr7{ODp>ikL`HY^d4 znW0aJ*u7Da%UDnB>DSKtlo6K>F{eU3p%9210a0)D($aJoXJ1E7U2>Ets%e`6>^6${ zt#Cgj(f++Y)@N+nzzcF@tIhY4LOT@8s{ei6R z2AO9Cc&F*LtL`ZTqCr5^RlT&b|Hx2f%+;#cmo-RAC3!?myS6arQe5cO0qt?b__l1WV&xNl&By$X~mE{-j)6}9$DFmWJK-5vaw6g!`a4o9Id_cr=$}*f4aF)cg0`j~s zL`Rt_r4VRBK-5vaw6gyutRhK`s_d{E@T?XwT{>3cSpm7;8k`x}E)ts5vkHMw1VsJR zODp>iMa=p&-T{V+n7;S-*)rb&sIGz5sZmcV1X>~>>Ze{>*?&vStllZvl(9E3-74a` zX4qhveZX?o)w_dLden0YflvfQ-PB7f`wvAdvTLjdm>}Xh{kMKH?Vt7q^^c4q^|V5u z2?0?z_0r1zo3M%|RZ_eAks_|{>b+U@W5ZZO(a$F3*9^&=76 zZ@#}yru}0KP})0oDpd{Xh=8b(fRpyx@ww|he+2-3vN$dkWGh(Mw)sT(| zhvJv)t92-#qVRIh3ke6aq~MNYl?0_W##}R%9ub?-?o$-#l+U>s8VvG78kw z3W4wlNYhVc|1E%K-Anccz7od(o{=pYQ8yFTm?2(9KQw ztuJADo`W3Dnq$ZV#vet2v!3nKdcc*e_oz0GVNMZ})CfeNmuv8QU%y)5Kssx1QY@a0fm4ha zpb$_9Cy)5Kssx1QY@a0fm4hapb$_9Cy)5Kssx1QY@a0fm4nh4M>ZVH_LBhQr;}TwMlui{8lFA&GH2WAa%%Ts*aI;hzb&2enGHC{qkn{ zIVR=J^0Q6Ko8>ntfIj_A`E|PZH7P)J`ArI-U*3AYPnW(X1+kJ}5JVFRO+T6xh{}h{ zuh7pA7Kt)H2b5@k<@)vK1hSbyezus;(^~B3FPLA5`lHAXDhP_}l^64ygh37Y+Yc%b ziigXm?pZIND~MS>wZ@ZwJ#KuBf#H2FJEDlU*MiU z_kuB}&cAwllt-;PAL5T+YJ!#-Z>vwKAFaJI2eoQ^Dc#yz@MGKB^z3+he&VgX9bf0q zZTsHpQpM?})hFJTv3u}ya67&p&)klu^Y-W{i5-vSJL+!~r_U7d9B_+k{h8lw-*C5m z!`=1_--G%o;Q5;S_H}*VzUIDt&39K`bKjokK3s9op5mZA#X)C%#|BKVy#c;`}E#yJUUhOFuGwkxsyfLjlJA=y-pZLqSG<`iwXfXvCMFr>prF zUglueh+hdJd9uND&-}oGNWNdiKd~Sje}f}|IR3-}Nsa_zz7R-qBnb0`K$0T?nA1hX zkpLXO5ac#r2*UAqc=7_wcr1|OK%mL7Ac_M)CdYy(4g{GT3v!=hL1z374g{I;bQN(R z(2U0dX`bB!o*WCJdAf>+f{6HtLqSCR4ekXJ@h3WAH1`4t4h7QOpD=<$K{WRQ2u_!8 zaVUt$A8{y%h=;uV01fMC08pU2oKo$-cD?3M7Mvd{Z4e2e*ASc6!Jz%m@9zfTJEoI~W-=j(g8(aNf2SZQ?opbSE}63f5IM%=b*|mEIl`@`PmX zh=1_?nSO^)bWawl=C!SNvJ zR=M{(;){JRr=&m;V;!jr$HojD!)Zn&$OdI11G+rJ;kzeLs-KS=DYrm0N43BC!1aiaOT@k#epJ)x96eFT!W1Cz3*-2;F*dMZo#cFQhQB^71KQG(z61!%<+tq}G2K`xl|J8C! ziUAP?Qy^je|Ng07Ai~dky!3rWl&KtadWPuF{1p*{cPB+XCm(1!4-5K8QC~hk7L|Bx z>j1b2qeL)2Mz9hn&Pay6zC0S$ds&0jTbCeI-3xL*|F5ECW%skX3JGa#1yR}Oi0ppN z>9En9svED1Q^jy1nD0v?6x33kS$UB01$Kp`XhgP2Zt0Ncq&JMifmM`Q=$o;$+3x2& zE*@xp5sAK4BM{@Ie>83D5Xi>SEIu@OwHE6F;#bdBL6+y5HHBOD=isxK=f(noorM;}P-Z9hr=Ss0KT@gb& z0JGU}xAb{>-|&cHr~16-*5LaY^Pk#Ko^zp{r5-VP^Y4=)>ct+p?Zz%jNjaOCX~P{M z!=7^qv!CLrrJxUKIG+iU5>GJ!OaXs!|L%lMh1=)8nGU?0>U)HSzhEfwx^%YDkBM@L z1JE6=iovT43x4X>?V=`P+Vd(tWKynVu7FeJhN@F6ujMVG&Z^S6-m960UpzVn1pOtG z+aMUH=ouS%tTo#(0RkwbGu!N4LSH$1g{A?0*$zL@?hS3b?1H(fB z>)%Y$>Eqo*R@MDfeaF^jiOoStpPaukAuVaE)3)J!$g1; z)myyvZo2ZTkMroEvwlwi*a-CoJEs6c^`39$f25Zbx5j_Pt=cw2Er(^7=0D51OC#cIV)csWNkn zZSeq)R_@!rC;u=f@1_X?p?X!Yx7&AO-b9E;Vs7#c^1vwmsg9vEtrM|im33+PGwWCV zz*M>UAr)`=wUe+0AyAaq!ez;TYA7bAb15;K-c6O`n#m(ohJ|$F6CTfYF?el)q3$k0 znvMe;@IcTGI)xJC?~AozD~l$aLXatjRoQi#qt}!0Oj~(x`6*4Y5J9+6L*1`J>!+;I zcpHdc88G7%fdm10O?wh~TsmGll|Fn| z*H^(v586<3EYsu`U7OUsl|i9eTO-xTMRdcBgT4wF+8IdUxyG7pKiD^l=egLt=+*p| zhLNsaz`n>o%!msMgl@{k*GN?fkxPpniuwh2#tAQ`d0j`k--%sw!FgP1mg~2Na6LP| znf6)pV|9GCu(#dW!FFi;y*Us~TtwhXwAV~TF+jcxy z8Y_eKQw6i)GqlS-&&FS{LUu#82l~@bu2*z_n|XB%o{b`EOhnjF@!{d^*9632pey zgf2vZgstG)($BrHV1KvgZQK8gFQu&Y^SsOb1`j`U{^kL0dJQ=CV!EuI8&sbAG=6g_l&L zG>cq$jZ{l9@hj20Sqv99b1%+}xUbFqun~5=?@Ev}O3RNfCs!~)K+oFt;ON*>eh%`0FV2 zUq1rR)S4d{8M2TWsG!&}F{x1FK#W1AG`?mPpVco$&U9MXjBdI7FY#_68@+i@y=5V~ zjSP`UCdYcTwov0!;eWWOKybfg&zsNQCzD9P9bLC;u0!}G``PCyHX_LpWAbpT(=6kx zs+FyeCRNwC$bGUj{%elwi$SbJs(MYZ_&c5lzAc-yARZ;~rd&a%9}*l^m(D%P96qWk zC)-D)KYmXvm+j9w{}l^F1h4I{Um*AeI&G+VL!TgQuT$cYvzn_2LO9En!Px!X0FM+v zqyMiettfx^^d2Naeyn1^jyOxD^1_7m%*F>x$p70o95!04D03FQr~LfrM~*1s{#P&9 zjcBm9QMqlNd9x&kri)|dL=l&!b3YOP9PLvMqc*(g#K+hU1GMsoZ7umVHY%@lGO*He zFD?dka}i`e1i#rF;<%n={nXv8zBvOr4fpY@sJtWy?1;0ex%2&2cf}nDS{2l?DTa%R zMbIs3%r_%yl~_sr^X*ajAh}53h^L%Tr&@Fnc-_kPx-+hjc4$V*m#>iQC=Cjz;(k6M z%n?lALegU$fS>icLQ?2XZ zt@B^q&RzX*+x#5_@W8J{{3SH3WeS?1@AdT}8a{Q10GY zO*VniZ=BFipMoKB1c?WdVpnqX_5i!!Ku)_U4;L7UB18}k$zt^xMCIsv?B*`TaPdgT9D%n5$1zwNXe z(mx`uj@dUYNxxN2HG%I|% zzo0eG;6A--+D!AMGM})gobgn(`mFF zO}k5EmCnZ}T-va^GSYRt{l5tI=5in2xBX6q2^xLqnR)L&$oOVCvte`}OVHc<_siPP zS{0wcJo!EhZIpG`3`HwSI0TMuyVU=84?PA*8iRcuTIVl4a;?bBgg^I>cR%gES$kj1 zFszfNhZ$%l*(B~ooDL<4G8H*LzTs)t9)G{=;%P@2HtE$2Lnks?e0j?h#y6-r(^)t1 z(GS0|9-yay{wDT71wosUJyDkRA_tbD&j(gPyG7p?F1RA5_%x&>NEH1jluu@Y6yU|r z+RLrA)acIznw86h1z2N%pJt`ZbW^`nEuS>`Fz2&F3fI4kk ztYYG}%6!M{%P&igBnwCx!x}rUC0Iu@`4hWokUv92iuGzS_D>j*QAW#$*)5OhsAkFB0N|d0RY}N3GrvuJe*nTpd+!*GZg*-D{ zY$8AFqG+&REXQQ4X!`k@8~{(T3kga~{bT&wi`t({#&tujyOg<3TcB^7b8B4mf{5-D~rr+U>7X z`?kHn0@HgOS~iTooJK&4uOm*orYD0XXFE2AAep8%dML&Lu*l4{^Xw`(AL_kwQ{;lw zFwg#44Sj%B&nCf)|5{~C;8H$jVn}ZH=dI{9Xl#{6n4|6M(h4% zZjh!U>xbV?ZI`(OM6a$izmUJ(Z;c|ov=4xX_Vp@(Hw@URWEkE?5C^a2`YhAg-#yQ( z^ja=m>YO>(W6xQ1y~~N zO~p*D?F7K6ZNC4CE1GXFHE7zW3zZtF$=EW;5O3?!o+SjwgNZY|XI>_k6&kC*QQbA! zYS~xUta5qJ>5a?s8b^~ol^Gqh z@!b*;P{`1C)!Nr-xFQ5Qs113ZyX{o7t4o)97V+&3(D{~Ex;JynZ+R7YK26wr?7sT6 zQ4()&EPv2lnvRe*&&5oh$ z@o&LK8Ms`ZO4tN3phTZ|)sbhnHt_4^sh0Bmj`V89+gy^RBfKv4l%~}m6z#_|+%Noz z#%yi5=Om%|tFb&tO3sd8WHVBe{rnx=o@Y>cB6;zT`AJL#x{%b3_;7&t?2|E7WvQFO zn%@xi%Z(1d!0m3vMg_QB4CS2$3WC_fRJVl(#iNDPDl9=xm zxk@@rqQkRMEuc_!<*U|p*9oOYCAeJc?SIE$!=0nlnu?b~wAgSC`itUD?!^YKzy<5z z6sP=Yd~NIbN__BC<(y;0L?ejvO90=o}YSy}F!Td@1D9%-UvP&w+zdf)%Kk7kuqb4=Fe}KCTHdI{0&~anx5Ebn*8=HEZ|Yw>xcV6 zw92;pQI{ND9!~xtr=aZSAptdIr9a1c_XqhKS>6$|zgV|kZ$1b}uzk0-mIWj=UN94R(D6P-a<#}$l zcMGKFz84NWdv1!J>YV0!V+QzSX^T{JwAZxiTR0bp0PQPBvm=GLVy4vu%oFu@FLTbF z=7qyQ$AFxj|E&8SUf#+Z!w$2QiBx@H^m`vY30yv!jP4moY9E%Ss6xvxPGdEhWZaI8 zB)&LwC-Is6-g~CHH5M=u<)HQ~bjjIC#@OvU*Xcfa7#R^H?e$8YJN5xo3n4&PlK?X{E*`_-XEo~OVS8IH#RVzcYQuv?S zIi^`F8xp&5A3{xc%*5JbUdSAkYTN0I<($euQFLqtJWVkGecM6O&AJUtOWxbAJaPTR zh8&Bc&*j6kK$X*o-Ds4->iF2@E2W+m-OuDcO*-|hX26#WNeKmD+N6l zT6qq3-<=KT7N>}xfLEKrt`uL8q>}&{B@)A@%qn}58e`mBbOL`i(Y`>$w~20{R>{TL z0W6Kbgm=rr?9W9awxmz#@j9B5pvOf3d{Hk*KfHN^!fGwv#j{kMl+%>C{P~CHT(z8= z?4<2sU$-5A_XPVyMIr#Wo7(Qg$o|q@KHSE3r6?*vY7hWfNJr_x(+I@ET9A5%M_1 zd}>4XX)E6Fr7npxJs8&$8s+CQH}iWoQNBmwu9C_2-WGya;dtVB&cUhESBaHFx0CBj z6Pr=Rpft=QCF+k&4_MBuf3H8+?(CJF1XP_`5)ziLO8=}PpuL5-{Wm7gBq9=VMZ&Vc z&ylnp9I{LHh1jUm0o7ILd=4Dnw0mc5V|q*PYi8V4#dRwQQkX`Hbx}XLM^@3ZX{P-~ zTQBhsI@-CHl`Te`K|-srsuR(wh4GLnW!fXMO3wf4IR zombBHq+=Uj{M9|#adx;fQAAn7m1DzijC!Yvx7b&i&~14Bj!L=GZ50EHg~<@@gp0wY zXMjdNey*o7*ku|g2qd0p15Xa+4);0)G>vjTT4$0MgOZLNHac=8=4?1q?X}b{FG;7< z*5sw=f@m7LhA~>ht37`B-ZX|0HGwO{dQ|qvQp@;7Hap9wEkWRb14WzoLr{(Gi*`+q z!|W3fqw_#p8S$H;tilc-JWkB>xM)*TYyJP!j|hI2q;30tn7NVC@N)aN06G54O-gy1 zc28x{-C-MIflDy9%p7Y?EqJcf@Yb;_<=6@aMV$&?3Ru*dIdXNEaQ`S|?Dm zt}bw%Ild4C3)98-skjwyV)ULI|J_)T>AAbbh(C;8IgF#%g-MHJAAfMCLsVKkGguzbgR&F?S((;OX;G(XY&a!^>a+h4#`!?@7_bMJ zWR2G9kvOX%dbAwaT{0{;l{1SwQWjoa7SA&dFfw0B9G}oWNK*#qgTyn1xq5j9msoh% zQtSZ{K;^GNuZ%-J+0}0g%@cy9dPg`4tZSfvzq7j}ho(W6`s;89@xk0>FcAPQx3s)Z zl3PgjM?sncFDcAh?;#zO!xPY7CJV1Z-xlA( z%klrxisE=3J`ND1qzX6Qe?X0wd54FFMh`MM5L0WB5_RA_B~h@;j~+E2xVu{J6ig#m zAFhb8q{=x19|Qe8;-*xF{Q;p0U=ke~`Fim&ZM=L{iQkG6|K7r5V}DU`j4nyZ;-xY_ zso`IflHIKT$lb}|8rCzo;(Fqln^#K=OFC(f`+NESy std::process::ExitCode { + let mut lock_path = std::env::temp_dir(); + lock_path.push("com_sasedev_ksp_app_config_desk.lock"); + let lock_file = match std::fs::OpenOptions::new().read(true).write(true).create(true).open(&lock_path) { + std::result::Result::Ok(file) => file, + std::result::Result::Err(error) => { + eprintln!("cannot create application lock '{}': {error}", lock_path.display()); + return std::process::ExitCode::FAILURE; + }, + }; + if let std::result::Result::Err(error) = lock_file.try_lock_exclusive() { + if error.kind() == std::io::ErrorKind::WouldBlock { + eprintln!("another ksp-app-config-desk instance is already running"); + return std::process::ExitCode::FAILURE; + } + eprintln!("cannot acquire application lock '{}': {error}", lock_path.display()); + return std::process::ExitCode::FAILURE; + } + let _lock_file = lock_file; + let arguments = std::env::args_os().collect::>(); + let run_result = ksp_app_config_desk_lib::run(arguments.as_slice()); + return match run_result { + std::result::Result::Ok(()) => std::process::ExitCode::SUCCESS, + std::result::Result::Err(error) => { + eprintln!("application error: {error}"); + std::process::ExitCode::FAILURE + }, + }; +} diff --git a/crates/ksp-app-config-desk/src/tauri.rs b/crates/ksp-app-config-desk/src/tauri.rs new file mode 100644 index 0000000..99b933e --- /dev/null +++ b/crates/ksp-app-config-desk/src/tauri.rs @@ -0,0 +1,33 @@ +// file: crates/ksp-app-config-desk/src/tauri.rs +// version: 1 + +//! Tauri runtime assembly for the KSP configuration desktop application. + +use tauri::Manager; // rust-rules: trait-import + +/// Runs the configuration desktop application. +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run(_arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<()> { + let mut builder = tauri::Builder::default(); + builder = configure_setup(builder); + let run_result = builder.run(tauri::generate_context!()); + return match run_result { + std::result::Result::Ok(()) => std::result::Result::Ok(()), + std::result::Result::Err(error) => std::result::Result::Err( + ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_RUNTIME_FAILED, "Cannot run KSP Config Desk Tauri runtime") + .with_context("tauri_error", error.to_string()), + ), + }; +} + +fn configure_setup(builder: tauri::Builder) -> tauri::Builder { + return builder.setup(|app| { + if app.get_webview_window("splash").is_none() { + return std::result::Result::Err(std::boxed::Box::new(std::io::Error::new(std::io::ErrorKind::NotFound, "splash window is missing"))); + } + if app.get_webview_window("main").is_none() { + return std::result::Result::Err(std::boxed::Box::new(std::io::Error::new(std::io::ErrorKind::NotFound, "main window is missing"))); + } + return std::result::Result::Ok(()); + }); +} diff --git a/crates/ksp-app-config-desk/tauri.conf.json b/crates/ksp-app-config-desk/tauri.conf.json new file mode 100644 index 0000000..e103725 --- /dev/null +++ b/crates/ksp-app-config-desk/tauri.conf.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "KSP Config Desk", + "version": "0.1.4", + "identifier": "com.sasedev.ksp-app-config-desk", + "build": { + "beforeDevCommand": "npm run dev", + "devUrl": "http://localhost:1430", + "beforeBuildCommand": "npm run build", + "frontendDist": "../../builds/khadhroony-solana-project/ksp-app-config-desk/dist" + }, + "app": { + "windows": [ + { + "label": "splash", + "url": "splash.html", + "title": "Chargement — KSP Config Desk", + "width": 960, + "height": 637, + "resizable": false, + "decorations": false, + "transparent": true, + "center": true, + "alwaysOnTop": true + }, + { + "label": "main", + "url": "main.html", + "title": "KSP Config Desk", + "width": 1500, + "height": 960, + "minWidth": 1024, + "minHeight": 768, + "center": true, + "visible": false + } + ], + "security": { + "csp": null + } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/favicon.png", + "icons/favicon.ico" + ] + } +} diff --git a/crates/ksp-app-config-desk/tests/public_api.rs b/crates/ksp-app-config-desk/tests/public_api.rs new file mode 100644 index 0000000..b2d0d30 --- /dev/null +++ b/crates/ksp-app-config-desk/tests/public_api.rs @@ -0,0 +1,14 @@ +// file: crates/ksp-app-config-desk/tests/public_api.rs +// version: 1 + +//! Public API checks for the KSP configuration desktop application library. + +#![forbid(unsafe_code)] +#![deny(unreachable_pub)] +#![warn(missing_docs)] + +#[test] +fn run_entry_point_is_available_from_crate_root() { + let run_entry_point: fn(&[std::ffi::OsString]) -> ksp_core_lib::Result<()> = ksp_app_config_desk_lib::run; + assert_eq!(std::mem::size_of_val(&run_entry_point), std::mem::size_of:: ksp_core_lib::Result<()>>()); +} diff --git a/deltas/0.1.4/pre.003.md b/deltas/0.1.4/pre.003.md index 9ff4f87..dab5449 100644 --- a/deltas/0.1.4/pre.003.md +++ b/deltas/0.1.4/pre.003.md @@ -173,16 +173,16 @@ deltas/0.1.4/pre.003.md ## Fichiers modifiés -| Fichier | Version précédente | Nouvelle version | -| ---------------------------------------------- | ------------------: | ---------------: | -| `Cargo.toml` | 63 | 64 | -| `crates/ksp-config-lib/src/document.rs` | 4 | 5 | -| `crates/ksp-config-lib/src/management.rs` | 1 | 2 | -| `crates/ksp-config-lib/unit_tests/management.rs` | 1 | 2 | -| `crates/ksp-config-lib/tests/public_api.rs` | 11 | 12 | -| `crates/ksp-config-lib/README.md` | 2 | 3 | -| `crates/ksp-config-lib/USAGE.md` | 3 | 4 | -| `crates/ksp-config-lib/TODO.md` | 2 | 3 | +| Fichier | Version précédente | Nouvelle version | +|--------------------------------------------------|-------------------:|-----------------:| +| `Cargo.toml` | 63 | 64 | +| `crates/ksp-config-lib/src/document.rs` | 4 | 5 | +| `crates/ksp-config-lib/src/management.rs` | 1 | 2 | +| `crates/ksp-config-lib/unit_tests/management.rs` | 1 | 2 | +| `crates/ksp-config-lib/tests/public_api.rs` | 11 | 12 | +| `crates/ksp-config-lib/README.md` | 2 | 3 | +| `crates/ksp-config-lib/USAGE.md` | 3 | 4 | +| `crates/ksp-config-lib/TODO.md` | 2 | 3 | ## Fichiers supprimés diff --git a/deltas/0.1.4/pre.004.md b/deltas/0.1.4/pre.004.md new file mode 100644 index 0000000..69adb74 --- /dev/null +++ b/deltas/0.1.4/pre.004.md @@ -0,0 +1,259 @@ + + + +# Delta 0.1.4-pre.004 — squelette Rust/Tauri de `ksp-app-config-desk` + +## Base requise + +```text +0.1.4-pre.003 +workspace.package.version = "0.1.4-pre.3" +``` + +`pre.003` est considéré comme validé et commité. Les validations locales fournies avant cette tranche ont réussi : `cargo fmt --all`, `cargo check --workspace`, `cargo clippy --workspace --all-targets`, `cargo test -p ksp-config-lib` et `cargo tree -p ksp-config-lib`. + +## Objectif + +Créer la première application Tauri KSP sans commencer encore ses panneaux fonctionnels ni son frontend complet. + +Cette tranche établit les contrats structurels qu'il serait coûteux de modifier plus tard : + +- crate workspace `crates/ksp-app-config-desk` ; +- package Rust mixte `lib` + `bin` ; +- `main.rs` launcher mince avec verrou single-instance ; +- `tauri.rs` propriétaire du runtime Tauri ; +- builder Tauri configuré par étapes courtes ; +- fenêtres `splash` et `main` ; +- capabilities initiales ; +- icône de référence issue du gabarit bot3 ; +- destination frontend externe ; +- premier couple de ports desk `1430/1431` ; +- conventions `frontend/ts`, `frontend/sass` et bindings TS-RS. + +Le frontend Vite/TypeScript/SCSS et les dépendances npm restent réservés à `pre.005`. + +## Version Cargo + +`workspace.package.version` passe de : + +```text +0.1.4-pre.3 +``` + +à : + +```text +0.1.4-pre.4 +``` + +L'identifiant de livraison est : + +```text +0.1.4-pre.004 +``` + +## 1. Nouveau membre workspace + +Le workspace contient désormais : + +```text +crates/ksp-app-config-desk +``` + +Le package expose deux cibles distinctes : + +```text +package : ksp-app-config-desk +lib : ksp_app_config_desk_lib +bin : ksp-app-config-desk +``` + +La bibliothèque utilise les crate types Tauri usuels `staticlib`, `cdylib` et `rlib` afin de conserver le gabarit compatible desktop/mobile sans déplacer la logique applicative dans le binaire. + +## 2. Dépendances Rust introduites + +Les versions ont été revérifiées au moment de cette tranche : + +| Dépendance | Version auditée | Contrainte workspace | Usage immédiat | +| ------------- | --------------: | -------------------: | -------------------------- | +| `tauri` | `2.11.5` | `^2.11` | runtime/application | +| `tauri-build` | `2.6.3` | `^2.6` | build script Tauri | +| `fs2` | `0.4.3` | `^0.4` | verrou single-instance | + +Les contraintes sont déclarées sous `[workspace.dependencies]`; la crate applicative les consomme avec `.workspace = true`. + +`ksp-core-lib` est la seule dépendance KSP immédiate de cette tranche, pour `Result`/`ErrorCode`/`Error`. `ksp-config-lib`, `ksp-logging-lib`, `tauri-plugin-tracing`, `ts-rs` et les dépendances frontend ne sont pas ajoutés prématurément : ils entreront lorsque leurs premiers usages réels seront développés. + +## 3. Launcher `main.rs` + +`main.rs` reste volontairement mince : + +1. construit un lockfile dans le répertoire temporaire système ; +2. acquiert un lock exclusif non bloquant via `fs2` ; +3. refuse une seconde instance ; +4. collecte `std::env::args_os()` ; +5. appelle `ksp_app_config_desk_lib::run(arguments)` ; +6. convertit explicitement le résultat en `ExitCode`. + +Le launcher ne lit aucune variable `KSP_*`/`KSPB_*` et ne parse pas Config lui-même. Les diagnostics précédant l'initialisation future de Logging restent limités à `stderr`. + +## 4. Frontière `tauri.rs` + +`tauri.rs` possède dès maintenant `run()` et le `tauri::Builder`. + +La construction suit la convention KSP décidée pendant `pre.001` : + +```text +let mut builder = tauri::Builder::default(); +builder = configure_setup(builder); +... +``` + +Aucune longue chaîne monolithique n'est introduite. Les prochains plugins, states, setups et handlers seront ajoutés par étapes courtes/réassignées ou helpers ciblés. + +Le setup initial vérifie uniquement que les fenêtres déclarées `splash` et `main` existent. Il n'existe encore aucune commande Tauri ; lorsqu'elles apparaîtront, leurs annotations `#[tauri::command]` resteront centralisées dans ce module. + +Une erreur runtime Tauri est convertie vers le type commun `ksp_core_lib::Error` avec le code applicatif `config_desk.tauri_runtime_failed`. + +## 5. Configuration Tauri + +`tauri.conf.json` déclare : + +- produit `KSP Config Desk` ; +- identifiant `com.sasedev.ksp-app-config-desk` ; +- version applicative `0.1.4` ; +- fenêtre `splash` cachant le shell pendant le lifecycle de démarrage ; +- fenêtre `main` initialement invisible ; +- icônes `favicon.png` et `favicon.ico` reprises du gabarit bot3 ; +- capability initiale `core:default` pour `splash` et `main`. + +Le plugin tracing n'est volontairement pas déclaré dans cette tranche ; son couple Rust/frontend est introduit avec le gabarit frontend de `pre.005`. + +## 6. Destination frontend et ports + +La destination de build est fixée dès maintenant à : + +```text +../../builds/khadhroony-solana-project/ksp-app-config-desk/dist +``` + +`tauri.conf.json` l'utilise comme `build.frontendDist`. `vite.config.ts` utilisera la même valeur comme `build.outDir` à partir de `pre.005`. + +Le premier couple de ports desk est réservé : + +| Usage | Port | +| --------- | ---: | +| Vite HTTP | 1430 | +| Vite HMR | 1431 | + +Les futures applications desk progressent par paires `1432/1433`, `1434/1435`, etc. `pre.005` configurera Vite avec port strict afin qu'une collision échoue explicitement. + +## 7. Convention frontend/TS-RS fixée + +La structure cible reprend/refond le modèle bot3 : + +```text +frontend/ +├── ts/ +│ └── bindings/ +└── sass/ +``` + +Les bindings TS-RS seront générés sous : + +```text +frontend/ts/bindings/ksp_app_config_desk/... +``` + +Ils resteront générés/non versionnés. `ts-rs` n'est pas encore ajouté car aucun DTO Tauri n'existe dans cette tranche. + +La règle npm est également formalisée : npm sert directement à installer/mettre à jour les dépendances avec `npm i -D ...`; le lancement normal se fait via Tauri, qui exécute les hooks `npm run dev` et `npm run build`. Les lockfiles frontend restent ignorés. + +## 8. Règles KSP complétées + +`docs/rules/RULES_KSP.md` ajoute : + +- `KSP-APP-022` — builder Tauri progressif ; +- `KSP-APP-023` — layout `frontend/ts`, `frontend/sass`, bindings TS-RS générés ; +- `KSP-APP-024` — couples de ports desk uniques à partir de `1430/1431` ; +- `KSP-APP-025` — npm réservé à la gestion directe des dépendances, cycle dev/build piloté par Tauri. + +Le plan actif passe en version 5 et enregistre les mêmes décisions pour les tranches suivantes. + +## 9. Générés ignorés + +`.gitignore` ajoute explicitement : + +```text +bindings/ +gen/ +``` + +afin de couvrir les bindings TS-RS et schemas/capabilities générés par Tauri. + +## 10. Tests + +Un premier test d'intégration `tests/public_api.rs` vérifie que la fonction publique : + +```rust +ksp_app_config_desk_lib::run +``` + +est accessible avec la signature attendue sans lancer le runtime graphique. + +Aucun test frontend n'est attendu avant `pre.005`. + +## 11. Fichiers ajoutés/modifiés + +| Fichier | Action | +|--------------------------------------------------------|:-------:| +| `.gitignore` | modifié | +| `Cargo.toml` | modifié | +| `docs/plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md` | modifié | +| `docs/rules/RULES_KSP.md` | modifié | +| `crates/ksp-app-config-desk/Cargo.toml` | ajouté | +| `crates/ksp-app-config-desk/README.md` | ajouté | +| `crates/ksp-app-config-desk/TODO.md` | ajouté | +| `crates/ksp-app-config-desk/USAGE.md` | ajouté | +| `crates/ksp-app-config-desk/build.rs` | ajouté | +| `crates/ksp-app-config-desk/capabilities/default.json` | ajouté | +| `crates/ksp-app-config-desk/icons/favicon.ico` | ajouté | +| `crates/ksp-app-config-desk/icons/favicon.png` | ajouté | +| `crates/ksp-app-config-desk/src/errors.rs` | ajouté | +| `crates/ksp-app-config-desk/src/lib.rs` | ajouté | +| `crates/ksp-app-config-desk/src/main.rs` | ajouté | +| `crates/ksp-app-config-desk/src/tauri.rs` | ajouté | +| `crates/ksp-app-config-desk/tauri.conf.json` | ajouté | +| `crates/ksp-app-config-desk/tests/public_api.rs` | ajouté | +| `deltas/0.1.4/pre.004.md` | ajouté | + +## 12. Validation demandée avant commit + +Après application du delta : + +```bash +cargo fmt --all +cargo check --workspace +cargo clippy --workspace --all-targets +cargo test -p ksp-app-config-desk +cargo test -p ksp-config-lib +cargo tree -p ksp-app-config-desk +``` + +`cargo test -p ksp-config-lib` est conservé dans cette tranche parce que l'ajout d'une nouvelle crate workspace doit rester compatible avec les audits d'ownership Config qui parcourent toutes les crates. + +`cargo tauri dev` n'est **pas encore** un critère de validation de `pre.004` : `package.json`, Vite et les fichiers HTML/TypeScript sont volontairement introduits dans `pre.005`. + +## 13. Suite + +Si cette tranche est validée et commitée, `pre.005` introduira le gabarit frontend : + +- `package.json` et installation des dépendances ; +- `vite.config.ts` / `tsconfig.json` ; +- `frontend/main.html` / `frontend/splash.html` ; +- `frontend/ts` et `frontend/sass` réels ; +- Bootstrap, Font Awesome, SimpleBar et `resize-observer-polyfill` ; +- `tauri-plugin-tracing` + `@fltsci/tauri-plugin-tracing` ; +- Vite strict `1430`, HMR `1431` ; +- output `../../builds/khadhroony-solana-project/ksp-app-config-desk/dist` ; +- premier `cargo tauri dev` du shell minimal. diff --git a/docs/plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md b/docs/plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md index bfcd6d8..9d6e085 100644 --- a/docs/plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md +++ b/docs/plans/006-V0_1_4_CONFIG_DESKTOP_PLAN.md @@ -1,5 +1,5 @@ - + # Plan `0.1.4` — `ksp-app-config-desk` @@ -220,6 +220,28 @@ Aucun framework React/Vue/Svelte n'est nécessaire pour ce manager spécialisé. Le premier éditeur raw utilisera un contrôle texte natif correctement stylé ; aucun éditeur de code lourd n'est ajouté tant qu'un besoin fonctionnel ne l'impose pas. +### 3.4 Conventions frontend, artefacts et ports des applications desk + +Le layout frontend reprend la convention éprouvée de bot3 : les sources web sont contenues dans `frontend/`, les modules TypeScript dans `frontend/ts/`, les styles SCSS dans `frontend/sass/` et les bindings TS-RS générés sous `frontend/ts/bindings/`. Les DTO Rust restent propriétaires de leur contrat ; les fichiers TypeScript générés ne sont jamais édités manuellement et `bindings/` reste ignoré par Git. + +Les artefacts frontend construits doivent être séparés des sources. Pour `ksp-app-config-desk`, la destination contractuelle est fixée à : + +```text +../../builds/khadhroony-solana-project/ksp-app-config-desk/dist +``` + +Cette valeur est utilisée par `build.frontendDist` dans `tauri.conf.json`. `vite.config.ts` utilisera la même destination comme `build.outDir` lors de son introduction. Elle remplace le `../dist`/`../../dist` historique du gabarit bot3. + +La gestion npm suit une règle simple : npm est utilisé directement pour installer ou mettre à jour les dépendances (`npm i -D ...`). Le cycle normal de développement et de build passe par Tauri ; `cargo tauri dev` et `cargo tauri build` déclenchent eux-mêmes `npm run dev` / `npm run build` au travers des hooks Tauri. Il n'est pas demandé à l'opérateur de lancer séparément le serveur Vite. Aucun lockfile npm n'est versionné. + +Chaque application Tauri desk KSP reçoit un couple de ports Vite/HMR propre. La première application réserve : + +| Application | Port Vite HTTP | Port HMR | +|-----------------------|---------------:|---------:| +| `ksp-app-config-desk` | `1430` | `1431` | + +Les applications suivantes incrémentent le couple de deux ports (`1432/1433`, puis `1434/1435`, etc.). Vite doit utiliser un port strict afin qu'une collision soit signalée au lieu de provoquer un basculement silencieux vers un autre port. Cette allocation permet de faire fonctionner simultanément plusieurs applications desk en mode développement. + ## 4. Audit des dépendances actuelles au 2026-08-16 Aucune dépendance n'est ajoutée par `pre.001`. Les versions suivantes ont été revérifiées depuis les registres/documentations officiels afin de fixer la génération candidate à réévaluer **au moment exact de l'ajout** : @@ -329,7 +351,7 @@ crates/ksp-app-config-desk/ │ │ ├── frontend_log.ts │ │ ├── state.ts │ │ └── bindings/ # généré, ignoré -│ └── scss/ +│ └── sass/ │ ├── main.scss │ ├── splash.scss │ └── _shared.scss @@ -1117,10 +1139,16 @@ pre.004 squelette Rust Tauri - main.rs/lib.rs/tauri.rs minimaux - builder Tauri configuré par étapes courtes/réassignées, sans chaîne monolithique - tauri.conf/capabilities/icône + - frontendDist externe contractuel + - réservation 1430/1431 et convention des couples de ports desk + - layout frontend/ts/bindings + frontend/sass fixé sans dépendances npm prématurées - single-instance pre.005 squelette frontend/gabarit - package.json + Vite/TypeScript + - npm utilisé pour la gestion des dépendances, cycle dev/build lancé par Tauri + - build.outDir identique au frontendDist externe + - port Vite 1430 strict + HMR 1431 - SCSS/Bootstrap/Font Awesome - SimpleBar + resize-observer-polyfill comme socle du gabarit - @fltsci/tauri-plugin-tracing avec plugin Rust diff --git a/docs/rules/RULES_KSP.md b/docs/rules/RULES_KSP.md index 1a76317..a65a388 100644 --- a/docs/rules/RULES_KSP.md +++ b/docs/rules/RULES_KSP.md @@ -1,5 +1,5 @@ - + # Règles spécifiques à KSP @@ -214,6 +214,10 @@ - **KSP-APP-019** — Les applications Tauri KSP utilisent l'écosystème `tracing` à leur frontière desktop via le plugin Tauri de tracing retenu (`tauri-plugin-tracing` ou successeur explicitement validé) et des adapters similaires au modèle éprouvé de khadhroony-bot3. Elles n'utilisent pas `tauri-plugin-log` ni la façade `log`, sauf décision architecturale future explicite qui remplacerait cette règle. L'application ne configure pas directement `tracing-subscriber`/`tracing-appender` : `ksp-logging-lib` reste propriétaire du runtime Logging, le plugin Tauri servant d'adapter d'intégration desktop. - **KSP-APP-020** — La première version de `ksp-app-config-desk` n'est clôturable que si l'UI permet de créer, modifier, sauvegarder et sélectionner plusieurs profils Logging, dont au minimum un profil mono-fichier et un profil multi-fichiers avec sorties séparées plus sortie logiciel/console, puis de recharger à chaud la configuration Logging et de constater effectivement le nouveau routage sans redémarrage de l'application. - **KSP-APP-021** — `ksp-app-config-desk` est conçu comme un gestionnaire Config extensible : la première version peut fournir un éditeur Logging typé, mais son architecture/navigation/état ne doit pas figer l'application sur `std.logging.json`. De futurs `file_id`, schemas et éditeurs spécialisés doivent pouvoir être ajoutés sans déplacer la propriété des documents, de leur validation ou de leur persistence hors de `ksp-config-lib`. +- **KSP-APP-022** — La construction d'un `tauri::Builder` KSP reste progressive et lisible : une variable builder est configurée/réassignée par étapes courtes ou helpers ciblés plutôt qu'au moyen d'une longue chaîne monolithique, afin qu'un plugin, state, setup ou groupe de commandes puisse être désactivé sans restructurer l'ensemble du runtime. +- **KSP-APP-023** — Le gabarit Tauri KSP place les sources web sous `frontend/`, les modules TypeScript sous `frontend/ts/`, les styles SCSS sous `frontend/sass/` et les bindings TS-RS générés sous `frontend/ts/bindings/`. Les bindings et artefacts générés ne sont pas versionnés ; les DTO Rust applicatifs restent la source des contrats TS-RS. +- **KSP-APP-024** — Chaque application desk Tauri KSP possède un couple de ports Vite/HMR exclusif. L'allocation commence à `1430/1431` pour `ksp-app-config-desk` puis progresse par paires (`1432/1433`, `1434/1435`, etc.). Le port Vite est strict afin qu'une collision échoue explicitement au lieu de sélectionner silencieusement un autre port. +- **KSP-APP-025** — npm est utilisé directement pour installer ou mettre à jour les dépendances frontend (`npm i -D ...`). Le cycle normal de développement/build d'une application Tauri passe par les commandes Tauri, qui déclenchent les scripts npm configurés via `beforeDevCommand`/`beforeBuildCommand`; les lockfiles frontend restent non versionnés. ## Data plane / control plane