From 22ac06ecd77358f09194a95173c3ed76573f88a1 Mon Sep 17 00:00:00 2001 From: neon <18559658+neonsolstice@users.noreply.github.com> Date: Tue, 12 May 2026 20:53:21 +0000 Subject: [PATCH] feat(client): add PWA support with installability and cover caching Add vite-plugin-pwa with Workbox in generateSW mode. Includes a web app manifest (standalone display, shortcuts for Dashboard/Libraries/Settings), iOS meta tags, and a service worker that pre-caches the app shell and uses CacheFirst for book cover and thumbnail endpoints (max 200 entries, 30-day TTL). Add useInstallPrompt composable that captures the beforeinstallprompt event and exposes isInstallable/installApp. Wire an install button into SidebarFooter that appears only when the browser signals the app is installable. Generate PWA icons (64, 192, 512, maskable-512, apple-touch-icon-180) from a new pwa-icon-source.svg using @vite-pwa/assets-generator with minimal2023Preset. Add dev-dist to .gitignore to exclude the dev-mode SW output. --- .gitignore | 3 + client/index.html | 7 +- client/package.json | 5 +- client/public/apple-touch-icon-180x180.png | Bin 0 -> 2009 bytes client/public/favicon.ico | Bin 4286 -> 962 bytes client/public/maskable-icon-512x512.png | Bin 0 -> 9852 bytes client/public/pwa-192x192.png | Bin 0 -> 2971 bytes client/public/pwa-512x512.png | Bin 0 -> 8460 bytes client/public/pwa-64x64.png | Bin 0 -> 816 bytes client/public/pwa-icon-source.svg | 25 + client/pwa-assets.config.ts | 6 + client/src/components/AppSidebar.vue | 9 + .../__tests__/useInstallPrompt.test.ts | 108 + .../pwa/composables/useInstallPrompt.ts | 45 + client/vite.config.ts | 113 +- pnpm-lock.yaml | 3818 +++++++++++++++++ 16 files changed, 4136 insertions(+), 3 deletions(-) create mode 100644 client/public/apple-touch-icon-180x180.png create mode 100644 client/public/maskable-icon-512x512.png create mode 100644 client/public/pwa-192x192.png create mode 100644 client/public/pwa-512x512.png create mode 100644 client/public/pwa-64x64.png create mode 100644 client/public/pwa-icon-source.svg create mode 100644 client/pwa-assets.config.ts create mode 100644 client/src/features/pwa/composables/__tests__/useInstallPrompt.test.ts create mode 100644 client/src/features/pwa/composables/useInstallPrompt.ts diff --git a/.gitignore b/.gitignore index 9040ee054..dc7305a7a 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,6 @@ test-results/ # IDE .idea/ + +# vite-plugin-pwa dev service worker output +dev-dist diff --git a/client/index.html b/client/index.html index e793e3160..f3962579a 100644 --- a/client/index.html +++ b/client/index.html @@ -2,8 +2,13 @@
- + + + + + +^Tls4DqS~e6Ch=EoHd!-*aIWNJA$5lbP7y zkX#gEQ~3Ly;`jvjwl>G$Y{+n&a-kMlm^-<)bB#I+eZdqrRkxD*Oqu0haRD z^)J_$6LtUJ&p$+0JFhe=LL*o_W3>m1;IlYq7&}6diQ|8HH{pNEgB>0Q <7jl$`pbXCyVOZb;BySo z`%svS0~zr^^mMpe>Ng)r{uye+wxQ<)fTNVG^1mXXPhZwm_{E)Y{g_@keC)9s|LQ8U zcVNFZzR0d>Q5C*HR#{1zC;6h1^!mm Bah@o;9lx!Bl$A5Ny=}i$Rwmv`m|*Toj=>YVr@v z9|6;u&F@%l0_E%AU#+#G(gHg)&qAsfW=IbTqd4#8cx2=A+wd}XaF?+|i728u>+w c$#7@n?>prBZu48d7k9TKf)8zAO)+(JzBX6yw9XNQS@MkuG>&qc&L3 z36FcPq3p|oBUaegm4{^6YvsTiu~npi1PW#AvK1i*w&YlF`mI>PH}KqR*(O@y-^cWo zSEXC0h3(cHjCd&C@QFfp= B*y_5~j@yJvZKw_EQ@9>s^yQG%sk~ zdA$+upw8fFMFoI;U*L#qe6SCN_={=<+DkORETiUMb>{?=XCj6NX4!fu1GksNs0!hX zq%But5NhhC_fz2omRmRMF6&G(63pHXIKQN-{szWX69c)&5uc>LF%fjO3hA>3Iet%u z-yP40hz^ImFG># (c={0$FiGMhbc^g%dC&8ih3WtET20QM z+FPt6Y>}Iv2V1D%nH2AK5!f4Ccf>By=j@Ej1;j!{(|L9F1B$T|aPv>d8ao}%Dmxc3 z)#+kZQmiFS*#B2oNB9w{xFR>-BE%;~ejT^_5dVWpS?1cdktE}0D^^u0JRQ%e|IjH4 zLv0ISK_TpRpiroahErRLJ*&Xk*_ehAc^o)|!qXbWe#-k(3#;mbuVVJvLa{s}7P%_p zN>97F0O5W4w8KrOG;PXppU2i+HzbE#UYL%zv+}E9DSC613Z~BG>}dBanD}eL (hb{3ukDA2GIblqq-t5J#`~uvM9{sW&&igw9pj %UE9l)}w{1(I4og zBzrD=Rph%kEXPYxu}HF9kyC8nyw9B|nzn5NaO1~@IC>uFbQ# 8-= r%`4B&c_C^I1RYd@A&uU$=n9`q`Ih}{o2vK*_Z=b +C#Jn5Sw2hKow*Y^h3ojjU zYebCX51J9;{s>K _QCD-#k J~Fm=Q$bXDwS&iTM|Vq0BSqF{p5zuTxLEm5iK3?|IK|eL%*I}PiC>2 z3%`XJ*)v~VpjJ)b2TIQHDsatdA}bYgi#vId@z!>#ZQU8pKE2M%A1OpC8gQ%l^YQau zH6v*&87EQW-GFGB#a~AkeA~u05qTy9w+L1kWM|c7jDca-V}?ijqO{0Ggyb{@OM|7q zi-oyeaQA8;JN2S;6xRku^6iG2DqF3H>Dd$`e_-?s|2QXk0~*Vj=t@o{?MK(5e4S!8 zQ8$zx1b)M{3W|2R`lT0R;VT7k)SN+>HW9}PEpOu+W+GOnn?BqnyLHHpJ>2jaSpQt( z5$5$V$mo|HazRqV=LmNJ6nhJ9{X10cK>QrXIdXXJn%T&j6NB~I37Bvb%JPW4Xi1dQ zks}~y82(eY&@-R+dc^L{qf8x|4)~_)aO#_Dwkjnm9tQ5f%q#JWP6$1@*1jBk(IM*b z>@kGn7&{-DFO+QCpBlUVt`j>7+lW@3C)-*{6CKPNJf5YRd~IazIDYeim;2xXt;1W^ zAYmJTv$)Bm5nAmgx>C0&GQ3-E_d&JZ)RcN}5LlvtSbt_-Xo80wz$tg(TNwYe+x3@} zabv|Yca%BJObHArJl_Kr;CQoiCUC7aktx~4slQxHZCaF3)B};P7HE~{MJn+%V}<`< za-^HbA_dK!UH=Y*Eu!=LMUAL)TJvMbvm`nt(H>0xl4B}*L_ejt_%4}Q$((;S=G{?1 ze1faY(z14dyj{e hT$tY^8_&r4(79 z{u&^{gi|<~__I)CRLqNupyr%Ix-`#Qg8w8dml4xf3qAFtO@~+AteE|TEhK@DY2qEk zi!RvUM=Yp3P8`{Rm4H3KV-L}ld0DOSuvYV)Ke1Ob39?L71Ox9OMG@ngS|cEeNk}hI zXX0K@>)N5qN#PEexsuWfbds`Q3g^&`q2D0q7o(Da8g2J@fz@4b9O);=L3PY#mMIn+bS>SV!~8EHx0(>L8nGt8KD z&AY+RgF}aChCAlDeFHiP@)dLG%$0t&^R{5&^YTv#7n%weD0zE=RWB+_V*>`KFKIhn z4SXp4(T3Xy`6I5iriQc~bPcgD;8ZOf)gtz}2l+tV&98~mr>7me@I810e7XN8UQ37Y zZo04qRvNti!MR?6@)MM{B^`e8cu>{V;l&4bau39wgxVQjCoV3I84SjLAT_#APtvUN z9LNuf-g}`&&!mALMAi@ecF5IoA3JpKCAT2Wt%0z{f~cg8Ae;tdPKdsCDfbqe5|ds~ zi{AQ#F1@BvGj}TUYfotyBmY)~i#{w%8b32{^ #8dLFD=Y=6Qky zad$FL$2J<3shvEvQxx=lui4Bl^JQ~CkE$c~QjYBP7>%C%;T@u@Is4qn@=!4UK9aj= z-k1GhRJY|!quJZT=w)4`$o;_Ai|1^Oty-C7+4$I3;` Lu6fr9$IwFv`_seplYkndP O?~}B`X$QOtqCC&w9%g5-o4rW^#8*(YM6;~oG-CyN=?QZ0m|;8L z5>32LEv)*KdPiDm7RgFePg+XvN&v3f2!9@{EC7kyuUfZVgT0zgYM@U!q?6(H{W|=; zZSx02YpAy85%4`HISqQ{!3hQYp(2 ze?@=J&%v+xktO#SP7+Xm8hV_+HylY>MXGfor|}+AF?ZFZuZnN!e~N5Yd1i zjkf)9-L?Y^Ix?vlH8C|(z_nr)vC(8x#bWySf(hk&ZJr|N_hMSs6NarO5Po5VX;~|J zy3Ir5u2`^(+REjREOd%6& n)f|$p1q-T?3yOx_kdxm286#Ew(rie z2JPNu&-y10Y|d`rdR0dYt6=Q!^~uwFkxBIMbgh#=80~ghCOkhabnqeRxP(oQB%T0} z|7L!|zNEeWK(Ey>ysmWp6iz2_{-n+V@=b`tCu)x?*LUm{-SLeW-Ey+Hg({c=uxqUK zDTzNDV_Fj2?fg7^CMbw4ioK!>tp;B&Z8mJt|I;Iboff)$emKkP8!N|<=+_w30^ly4 zGx6!iKl{e3w>bSXUx)euiR9W9I5m~epcE>jve$#j)8A(u(ARPM0zd5~)( 0?cvpac(PY>o$R zRzz>_Wn1Plb+QP6xpe1}Ja0v=hyuPXuT@rc+sF9k2xea(wAr!u5zs^*>^L-x$8bDr zwC_fJyymiV 6Ul05 zzCwoTfP)-%8+2B1xnbcyyDGzz!YDE0@$o)8k}Lpb$TD^yt~s=_fOjigL>sqN4}+Ws z<~BX0#q0DqBZs!=G+&uHlU%x*Z=&FpG)uJx8F7cdWTqUC+;uSGIxPL*$q$(F3u9Oq zf*u*}F{oQHAuXpz98B1LUZtu7(QCzz>fE&k?G79%?wl=JogLXW;rsQ>+#q0lG}^CZ zBIz*rjsTC Mh_Oh3!-&`3%82b>{9#)ClIscAl=#F-5VM z;rKta#M3LQfLl16%xHdmXWk%j&1NIVQVjMDgP33cf$Q#6N(IPWW0$j7bUD!4+8z6^ zb7#3+Jc*TG!;clbB-!R%AntH#nG7qIP*~Ug!SRs{9`)3dM!||