A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go
72
fork

Configure Feed

Select the types of activity you want to include in your feed.

update seamark theme, add 'delete all untagged' option on record page. add garbage collection flag for untagged

+1304 -321
+2 -1
config-appview.example.yaml
··· 45 45 # SQLite/libSQL database for OAuth sessions, stars, pull counts, and device approvals. 46 46 database_path: /var/lib/atcr/ui.db 47 47 # Visual theme name (e.g. "seamark"). Empty uses default atcr.io branding. 48 - theme: "" 48 + theme: "seamark" 49 49 # libSQL sync URL (libsql://...). Works with Turso cloud or self-hosted libsql-server. Leave empty for local-only SQLite. 50 50 libsql_sync_url: "" 51 51 # Auth token for libSQL sync. Required if libsql_sync_url is set. ··· 74 74 relay_endpoints: 75 75 - https://relay1.us-east.bsky.network 76 76 - https://relay1.us-west.bsky.network 77 + - https://zlay.waow.tech 77 78 # JWT authentication settings. 78 79 auth: 79 80 # RSA private key for signing registry JWTs issued to Docker clients.
+4
lexicons/io/atcr/sailor/profile.json
··· 15 15 "format": "uri", 16 16 "description": "Default hold endpoint for blob storage. If null, user has opted out of defaults." 17 17 }, 18 + "autoRemoveUntagged": { 19 + "type": "boolean", 20 + "description": "Automatically delete manifest records that become untagged after a tag overwrite. Layers are cleaned up by hold garbage collection." 21 + }, 18 22 "createdAt": { 19 23 "type": "string", 20 24 "format": "datetime",
+189 -189
package-lock.json
··· 11 11 "actor-typeahead": "^0.1.2", 12 12 "htmx-ext-json-enc": "^2.0.3", 13 13 "htmx.org": "^2.0.8", 14 - "lucide": "^0.575.0" 14 + "lucide": "^0.577.0" 15 15 }, 16 16 "devDependencies": { 17 - "@tailwindcss/cli": "^4.2.0", 17 + "@tailwindcss/cli": "^4.2.1", 18 18 "@tailwindcss/typography": "^0.5.19", 19 19 "daisyui": "^5.5.19", 20 - "esbuild": "^0.27.3", 20 + "esbuild": "^0.27.4", 21 21 "glob": "^13.0.6", 22 22 "tailwindcss": "^4.2" 23 23 } 24 24 }, 25 25 "node_modules/@esbuild/aix-ppc64": { 26 - "version": "0.27.3", 27 - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", 28 - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", 26 + "version": "0.27.4", 27 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", 28 + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", 29 29 "cpu": [ 30 30 "ppc64" 31 31 ], ··· 40 40 } 41 41 }, 42 42 "node_modules/@esbuild/android-arm": { 43 - "version": "0.27.3", 44 - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", 45 - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", 43 + "version": "0.27.4", 44 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", 45 + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", 46 46 "cpu": [ 47 47 "arm" 48 48 ], ··· 57 57 } 58 58 }, 59 59 "node_modules/@esbuild/android-arm64": { 60 - "version": "0.27.3", 61 - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", 62 - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", 60 + "version": "0.27.4", 61 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", 62 + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", 63 63 "cpu": [ 64 64 "arm64" 65 65 ], ··· 74 74 } 75 75 }, 76 76 "node_modules/@esbuild/android-x64": { 77 - "version": "0.27.3", 78 - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", 79 - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", 77 + "version": "0.27.4", 78 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", 79 + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", 80 80 "cpu": [ 81 81 "x64" 82 82 ], ··· 91 91 } 92 92 }, 93 93 "node_modules/@esbuild/darwin-arm64": { 94 - "version": "0.27.3", 95 - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", 96 - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", 94 + "version": "0.27.4", 95 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", 96 + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", 97 97 "cpu": [ 98 98 "arm64" 99 99 ], ··· 108 108 } 109 109 }, 110 110 "node_modules/@esbuild/darwin-x64": { 111 - "version": "0.27.3", 112 - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", 113 - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", 111 + "version": "0.27.4", 112 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", 113 + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", 114 114 "cpu": [ 115 115 "x64" 116 116 ], ··· 125 125 } 126 126 }, 127 127 "node_modules/@esbuild/freebsd-arm64": { 128 - "version": "0.27.3", 129 - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", 130 - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", 128 + "version": "0.27.4", 129 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", 130 + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", 131 131 "cpu": [ 132 132 "arm64" 133 133 ], ··· 142 142 } 143 143 }, 144 144 "node_modules/@esbuild/freebsd-x64": { 145 - "version": "0.27.3", 146 - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", 147 - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", 145 + "version": "0.27.4", 146 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", 147 + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", 148 148 "cpu": [ 149 149 "x64" 150 150 ], ··· 159 159 } 160 160 }, 161 161 "node_modules/@esbuild/linux-arm": { 162 - "version": "0.27.3", 163 - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", 164 - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", 162 + "version": "0.27.4", 163 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", 164 + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", 165 165 "cpu": [ 166 166 "arm" 167 167 ], ··· 176 176 } 177 177 }, 178 178 "node_modules/@esbuild/linux-arm64": { 179 - "version": "0.27.3", 180 - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", 181 - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", 179 + "version": "0.27.4", 180 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", 181 + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", 182 182 "cpu": [ 183 183 "arm64" 184 184 ], ··· 193 193 } 194 194 }, 195 195 "node_modules/@esbuild/linux-ia32": { 196 - "version": "0.27.3", 197 - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", 198 - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", 196 + "version": "0.27.4", 197 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", 198 + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", 199 199 "cpu": [ 200 200 "ia32" 201 201 ], ··· 210 210 } 211 211 }, 212 212 "node_modules/@esbuild/linux-loong64": { 213 - "version": "0.27.3", 214 - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", 215 - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", 213 + "version": "0.27.4", 214 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", 215 + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", 216 216 "cpu": [ 217 217 "loong64" 218 218 ], ··· 227 227 } 228 228 }, 229 229 "node_modules/@esbuild/linux-mips64el": { 230 - "version": "0.27.3", 231 - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", 232 - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", 230 + "version": "0.27.4", 231 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", 232 + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", 233 233 "cpu": [ 234 234 "mips64el" 235 235 ], ··· 244 244 } 245 245 }, 246 246 "node_modules/@esbuild/linux-ppc64": { 247 - "version": "0.27.3", 248 - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", 249 - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", 247 + "version": "0.27.4", 248 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", 249 + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", 250 250 "cpu": [ 251 251 "ppc64" 252 252 ], ··· 261 261 } 262 262 }, 263 263 "node_modules/@esbuild/linux-riscv64": { 264 - "version": "0.27.3", 265 - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", 266 - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", 264 + "version": "0.27.4", 265 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", 266 + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", 267 267 "cpu": [ 268 268 "riscv64" 269 269 ], ··· 278 278 } 279 279 }, 280 280 "node_modules/@esbuild/linux-s390x": { 281 - "version": "0.27.3", 282 - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", 283 - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", 281 + "version": "0.27.4", 282 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", 283 + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", 284 284 "cpu": [ 285 285 "s390x" 286 286 ], ··· 295 295 } 296 296 }, 297 297 "node_modules/@esbuild/linux-x64": { 298 - "version": "0.27.3", 299 - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", 300 - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", 298 + "version": "0.27.4", 299 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", 300 + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", 301 301 "cpu": [ 302 302 "x64" 303 303 ], ··· 312 312 } 313 313 }, 314 314 "node_modules/@esbuild/netbsd-arm64": { 315 - "version": "0.27.3", 316 - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", 317 - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", 315 + "version": "0.27.4", 316 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", 317 + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", 318 318 "cpu": [ 319 319 "arm64" 320 320 ], ··· 329 329 } 330 330 }, 331 331 "node_modules/@esbuild/netbsd-x64": { 332 - "version": "0.27.3", 333 - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", 334 - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", 332 + "version": "0.27.4", 333 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", 334 + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", 335 335 "cpu": [ 336 336 "x64" 337 337 ], ··· 346 346 } 347 347 }, 348 348 "node_modules/@esbuild/openbsd-arm64": { 349 - "version": "0.27.3", 350 - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", 351 - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", 349 + "version": "0.27.4", 350 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", 351 + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", 352 352 "cpu": [ 353 353 "arm64" 354 354 ], ··· 363 363 } 364 364 }, 365 365 "node_modules/@esbuild/openbsd-x64": { 366 - "version": "0.27.3", 367 - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", 368 - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", 366 + "version": "0.27.4", 367 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", 368 + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", 369 369 "cpu": [ 370 370 "x64" 371 371 ], ··· 380 380 } 381 381 }, 382 382 "node_modules/@esbuild/openharmony-arm64": { 383 - "version": "0.27.3", 384 - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", 385 - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", 383 + "version": "0.27.4", 384 + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", 385 + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", 386 386 "cpu": [ 387 387 "arm64" 388 388 ], ··· 397 397 } 398 398 }, 399 399 "node_modules/@esbuild/sunos-x64": { 400 - "version": "0.27.3", 401 - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", 402 - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", 400 + "version": "0.27.4", 401 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", 402 + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", 403 403 "cpu": [ 404 404 "x64" 405 405 ], ··· 414 414 } 415 415 }, 416 416 "node_modules/@esbuild/win32-arm64": { 417 - "version": "0.27.3", 418 - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", 419 - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", 417 + "version": "0.27.4", 418 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", 419 + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", 420 420 "cpu": [ 421 421 "arm64" 422 422 ], ··· 431 431 } 432 432 }, 433 433 "node_modules/@esbuild/win32-ia32": { 434 - "version": "0.27.3", 435 - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", 436 - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", 434 + "version": "0.27.4", 435 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", 436 + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", 437 437 "cpu": [ 438 438 "ia32" 439 439 ], ··· 448 448 } 449 449 }, 450 450 "node_modules/@esbuild/win32-x64": { 451 - "version": "0.27.3", 452 - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", 453 - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", 451 + "version": "0.27.4", 452 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", 453 + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", 454 454 "cpu": [ 455 455 "x64" 456 456 ], ··· 824 824 } 825 825 }, 826 826 "node_modules/@tailwindcss/cli": { 827 - "version": "4.2.0", 828 - "resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.2.0.tgz", 829 - "integrity": "sha512-C62SWDp+6Rj5DHJDlMyAqESpmljiQ35H4SncAcVn3Gm0rEPrKFDIdAheT74s9zAbrsa2D/L+jJaPgCO1fyZG6g==", 827 + "version": "4.2.1", 828 + "resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.2.1.tgz", 829 + "integrity": "sha512-b7MGn51IA80oSG+7fuAgzfQ+7pZBgjzbqwmiv6NO7/+a1sev32cGqnwhscT7h0EcAvMa9r7gjRylqOH8Xhc4DA==", 830 830 "dev": true, 831 831 "license": "MIT", 832 832 "dependencies": { 833 833 "@parcel/watcher": "^2.5.1", 834 - "@tailwindcss/node": "4.2.0", 835 - "@tailwindcss/oxide": "4.2.0", 834 + "@tailwindcss/node": "4.2.1", 835 + "@tailwindcss/oxide": "4.2.1", 836 836 "enhanced-resolve": "^5.19.0", 837 837 "mri": "^1.2.0", 838 838 "picocolors": "^1.1.1", 839 - "tailwindcss": "4.2.0" 839 + "tailwindcss": "4.2.1" 840 840 }, 841 841 "bin": { 842 842 "tailwindcss": "dist/index.mjs" 843 843 } 844 844 }, 845 845 "node_modules/@tailwindcss/node": { 846 - "version": "4.2.0", 847 - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.0.tgz", 848 - "integrity": "sha512-Yv+fn/o2OmL5fh/Ir62VXItdShnUxfpkMA4Y7jdeC8O81WPB8Kf6TT6GSHvnqgSwDzlB5iT7kDpeXxLsUS0T6Q==", 846 + "version": "4.2.1", 847 + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", 848 + "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==", 849 849 "dev": true, 850 850 "license": "MIT", 851 851 "dependencies": { ··· 855 855 "lightningcss": "1.31.1", 856 856 "magic-string": "^0.30.21", 857 857 "source-map-js": "^1.2.1", 858 - "tailwindcss": "4.2.0" 858 + "tailwindcss": "4.2.1" 859 859 } 860 860 }, 861 861 "node_modules/@tailwindcss/oxide": { 862 - "version": "4.2.0", 863 - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.0.tgz", 864 - "integrity": "sha512-AZqQzADaj742oqn2xjl5JbIOzZB/DGCYF/7bpvhA8KvjUj9HJkag6bBuwZvH1ps6dfgxNHyuJVlzSr2VpMgdTQ==", 862 + "version": "4.2.1", 863 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz", 864 + "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==", 865 865 "dev": true, 866 866 "license": "MIT", 867 867 "engines": { 868 868 "node": ">= 20" 869 869 }, 870 870 "optionalDependencies": { 871 - "@tailwindcss/oxide-android-arm64": "4.2.0", 872 - "@tailwindcss/oxide-darwin-arm64": "4.2.0", 873 - "@tailwindcss/oxide-darwin-x64": "4.2.0", 874 - "@tailwindcss/oxide-freebsd-x64": "4.2.0", 875 - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.0", 876 - "@tailwindcss/oxide-linux-arm64-gnu": "4.2.0", 877 - "@tailwindcss/oxide-linux-arm64-musl": "4.2.0", 878 - "@tailwindcss/oxide-linux-x64-gnu": "4.2.0", 879 - "@tailwindcss/oxide-linux-x64-musl": "4.2.0", 880 - "@tailwindcss/oxide-wasm32-wasi": "4.2.0", 881 - "@tailwindcss/oxide-win32-arm64-msvc": "4.2.0", 882 - "@tailwindcss/oxide-win32-x64-msvc": "4.2.0" 871 + "@tailwindcss/oxide-android-arm64": "4.2.1", 872 + "@tailwindcss/oxide-darwin-arm64": "4.2.1", 873 + "@tailwindcss/oxide-darwin-x64": "4.2.1", 874 + "@tailwindcss/oxide-freebsd-x64": "4.2.1", 875 + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", 876 + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", 877 + "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", 878 + "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", 879 + "@tailwindcss/oxide-linux-x64-musl": "4.2.1", 880 + "@tailwindcss/oxide-wasm32-wasi": "4.2.1", 881 + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", 882 + "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" 883 883 } 884 884 }, 885 885 "node_modules/@tailwindcss/oxide-android-arm64": { 886 - "version": "4.2.0", 887 - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.0.tgz", 888 - "integrity": "sha512-F0QkHAVaW/JNBWl4CEKWdZ9PMb0khw5DCELAOnu+RtjAfx5Zgw+gqCHFvqg3AirU1IAd181fwOtJQ5I8Yx5wtw==", 886 + "version": "4.2.1", 887 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz", 888 + "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==", 889 889 "cpu": [ 890 890 "arm64" 891 891 ], ··· 900 900 } 901 901 }, 902 902 "node_modules/@tailwindcss/oxide-darwin-arm64": { 903 - "version": "4.2.0", 904 - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.0.tgz", 905 - "integrity": "sha512-I0QylkXsBsJMZ4nkUNSR04p6+UptjcwhcVo3Zu828ikiEqHjVmQL9RuQ6uT/cVIiKpvtVA25msu/eRV97JeNSA==", 903 + "version": "4.2.1", 904 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz", 905 + "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==", 906 906 "cpu": [ 907 907 "arm64" 908 908 ], ··· 917 917 } 918 918 }, 919 919 "node_modules/@tailwindcss/oxide-darwin-x64": { 920 - "version": "4.2.0", 921 - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.0.tgz", 922 - "integrity": "sha512-6TmQIn4p09PBrmnkvbYQ0wbZhLtbaksCDx7Y7R3FYYx0yxNA7xg5KP7dowmQ3d2JVdabIHvs3Hx4K3d5uCf8xg==", 920 + "version": "4.2.1", 921 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz", 922 + "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==", 923 923 "cpu": [ 924 924 "x64" 925 925 ], ··· 934 934 } 935 935 }, 936 936 "node_modules/@tailwindcss/oxide-freebsd-x64": { 937 - "version": "4.2.0", 938 - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.0.tgz", 939 - "integrity": "sha512-qBudxDvAa2QwGlq9y7VIzhTvp2mLJ6nD/G8/tI70DCDoneaUeLWBJaPcbfzqRIWraj+o969aDQKvKW9dvkUizw==", 937 + "version": "4.2.1", 938 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz", 939 + "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==", 940 940 "cpu": [ 941 941 "x64" 942 942 ], ··· 951 951 } 952 952 }, 953 953 "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { 954 - "version": "4.2.0", 955 - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.0.tgz", 956 - "integrity": "sha512-7XKkitpy5NIjFZNUQPeUyNJNJn1CJeV7rmMR+exHfTuOsg8rxIO9eNV5TSEnqRcaOK77zQpsyUkBWmPy8FgdSg==", 954 + "version": "4.2.1", 955 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz", 956 + "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==", 957 957 "cpu": [ 958 958 "arm" 959 959 ], ··· 968 968 } 969 969 }, 970 970 "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { 971 - "version": "4.2.0", 972 - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.0.tgz", 973 - "integrity": "sha512-Mff5a5Q3WoQR01pGU1gr29hHM1N93xYrKkGXfPw/aRtK4bOc331Ho4Tgfsm5WDGvpevqMpdlkCojT3qlCQbCpA==", 971 + "version": "4.2.1", 972 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz", 973 + "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==", 974 974 "cpu": [ 975 975 "arm64" 976 976 ], ··· 985 985 } 986 986 }, 987 987 "node_modules/@tailwindcss/oxide-linux-arm64-musl": { 988 - "version": "4.2.0", 989 - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.0.tgz", 990 - "integrity": "sha512-XKcSStleEVnbH6W/9DHzZv1YhjE4eSS6zOu2eRtYAIh7aV4o3vIBs+t/B15xlqoxt6ef/0uiqJVB6hkHjWD/0A==", 988 + "version": "4.2.1", 989 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz", 990 + "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==", 991 991 "cpu": [ 992 992 "arm64" 993 993 ], ··· 1002 1002 } 1003 1003 }, 1004 1004 "node_modules/@tailwindcss/oxide-linux-x64-gnu": { 1005 - "version": "4.2.0", 1006 - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.0.tgz", 1007 - "integrity": "sha512-/hlXCBqn9K6fi7eAM0RsobHwJYa5V/xzWspVTzxnX+Ft9v6n+30Pz8+RxCn7sQL/vRHHLS30iQPrHQunu6/vJA==", 1005 + "version": "4.2.1", 1006 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz", 1007 + "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==", 1008 1008 "cpu": [ 1009 1009 "x64" 1010 1010 ], ··· 1019 1019 } 1020 1020 }, 1021 1021 "node_modules/@tailwindcss/oxide-linux-x64-musl": { 1022 - "version": "4.2.0", 1023 - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.0.tgz", 1024 - "integrity": "sha512-lKUaygq4G7sWkhQbfdRRBkaq4LY39IriqBQ+Gk6l5nKq6Ay2M2ZZb1tlIyRNgZKS8cbErTwuYSor0IIULC0SHw==", 1022 + "version": "4.2.1", 1023 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz", 1024 + "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==", 1025 1025 "cpu": [ 1026 1026 "x64" 1027 1027 ], ··· 1036 1036 } 1037 1037 }, 1038 1038 "node_modules/@tailwindcss/oxide-wasm32-wasi": { 1039 - "version": "4.2.0", 1040 - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.0.tgz", 1041 - "integrity": "sha512-xuDjhAsFdUuFP5W9Ze4k/o4AskUtI8bcAGU4puTYprr89QaYFmhYOPfP+d1pH+k9ets6RoE23BXZM1X1jJqoyw==", 1039 + "version": "4.2.1", 1040 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz", 1041 + "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==", 1042 1042 "bundleDependencies": [ 1043 1043 "@napi-rs/wasm-runtime", 1044 1044 "@emnapi/core", ··· 1066 1066 } 1067 1067 }, 1068 1068 "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { 1069 - "version": "4.2.0", 1070 - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.0.tgz", 1071 - "integrity": "sha512-2UU/15y1sWDEDNJXxEIrfWKC2Yb4YgIW5Xz2fKFqGzFWfoMHWFlfa1EJlGO2Xzjkq/tvSarh9ZTjvbxqWvLLXA==", 1069 + "version": "4.2.1", 1070 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz", 1071 + "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==", 1072 1072 "cpu": [ 1073 1073 "arm64" 1074 1074 ], ··· 1083 1083 } 1084 1084 }, 1085 1085 "node_modules/@tailwindcss/oxide-win32-x64-msvc": { 1086 - "version": "4.2.0", 1087 - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.0.tgz", 1088 - "integrity": "sha512-CrFadmFoc+z76EV6LPG1jx6XceDsaCG3lFhyLNo/bV9ByPrE+FnBPckXQVP4XRkN76h3Fjt/a+5Er/oA/nCBvQ==", 1086 + "version": "4.2.1", 1087 + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz", 1088 + "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==", 1089 1089 "cpu": [ 1090 1090 "x64" 1091 1091 ], ··· 1129 1129 } 1130 1130 }, 1131 1131 "node_modules/brace-expansion": { 1132 - "version": "5.0.3", 1133 - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", 1134 - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", 1132 + "version": "5.0.4", 1133 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", 1134 + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", 1135 1135 "dev": true, 1136 1136 "license": "MIT", 1137 1137 "dependencies": { ··· 1175 1175 } 1176 1176 }, 1177 1177 "node_modules/enhanced-resolve": { 1178 - "version": "5.19.0", 1179 - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", 1180 - "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", 1178 + "version": "5.20.0", 1179 + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", 1180 + "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", 1181 1181 "dev": true, 1182 1182 "license": "MIT", 1183 1183 "dependencies": { ··· 1189 1189 } 1190 1190 }, 1191 1191 "node_modules/esbuild": { 1192 - "version": "0.27.3", 1193 - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", 1194 - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", 1192 + "version": "0.27.4", 1193 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", 1194 + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", 1195 1195 "dev": true, 1196 1196 "hasInstallScript": true, 1197 1197 "license": "MIT", ··· 1202 1202 "node": ">=18" 1203 1203 }, 1204 1204 "optionalDependencies": { 1205 - "@esbuild/aix-ppc64": "0.27.3", 1206 - "@esbuild/android-arm": "0.27.3", 1207 - "@esbuild/android-arm64": "0.27.3", 1208 - "@esbuild/android-x64": "0.27.3", 1209 - "@esbuild/darwin-arm64": "0.27.3", 1210 - "@esbuild/darwin-x64": "0.27.3", 1211 - "@esbuild/freebsd-arm64": "0.27.3", 1212 - "@esbuild/freebsd-x64": "0.27.3", 1213 - "@esbuild/linux-arm": "0.27.3", 1214 - "@esbuild/linux-arm64": "0.27.3", 1215 - "@esbuild/linux-ia32": "0.27.3", 1216 - "@esbuild/linux-loong64": "0.27.3", 1217 - "@esbuild/linux-mips64el": "0.27.3", 1218 - "@esbuild/linux-ppc64": "0.27.3", 1219 - "@esbuild/linux-riscv64": "0.27.3", 1220 - "@esbuild/linux-s390x": "0.27.3", 1221 - "@esbuild/linux-x64": "0.27.3", 1222 - "@esbuild/netbsd-arm64": "0.27.3", 1223 - "@esbuild/netbsd-x64": "0.27.3", 1224 - "@esbuild/openbsd-arm64": "0.27.3", 1225 - "@esbuild/openbsd-x64": "0.27.3", 1226 - "@esbuild/openharmony-arm64": "0.27.3", 1227 - "@esbuild/sunos-x64": "0.27.3", 1228 - "@esbuild/win32-arm64": "0.27.3", 1229 - "@esbuild/win32-ia32": "0.27.3", 1230 - "@esbuild/win32-x64": "0.27.3" 1205 + "@esbuild/aix-ppc64": "0.27.4", 1206 + "@esbuild/android-arm": "0.27.4", 1207 + "@esbuild/android-arm64": "0.27.4", 1208 + "@esbuild/android-x64": "0.27.4", 1209 + "@esbuild/darwin-arm64": "0.27.4", 1210 + "@esbuild/darwin-x64": "0.27.4", 1211 + "@esbuild/freebsd-arm64": "0.27.4", 1212 + "@esbuild/freebsd-x64": "0.27.4", 1213 + "@esbuild/linux-arm": "0.27.4", 1214 + "@esbuild/linux-arm64": "0.27.4", 1215 + "@esbuild/linux-ia32": "0.27.4", 1216 + "@esbuild/linux-loong64": "0.27.4", 1217 + "@esbuild/linux-mips64el": "0.27.4", 1218 + "@esbuild/linux-ppc64": "0.27.4", 1219 + "@esbuild/linux-riscv64": "0.27.4", 1220 + "@esbuild/linux-s390x": "0.27.4", 1221 + "@esbuild/linux-x64": "0.27.4", 1222 + "@esbuild/netbsd-arm64": "0.27.4", 1223 + "@esbuild/netbsd-x64": "0.27.4", 1224 + "@esbuild/openbsd-arm64": "0.27.4", 1225 + "@esbuild/openbsd-x64": "0.27.4", 1226 + "@esbuild/openharmony-arm64": "0.27.4", 1227 + "@esbuild/sunos-x64": "0.27.4", 1228 + "@esbuild/win32-arm64": "0.27.4", 1229 + "@esbuild/win32-ia32": "0.27.4", 1230 + "@esbuild/win32-x64": "0.27.4" 1231 1231 } 1232 1232 }, 1233 1233 "node_modules/glob": { ··· 1564 1564 } 1565 1565 }, 1566 1566 "node_modules/lru-cache": { 1567 - "version": "11.2.6", 1568 - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", 1569 - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", 1567 + "version": "11.2.7", 1568 + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", 1569 + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", 1570 1570 "dev": true, 1571 1571 "license": "BlueOak-1.0.0", 1572 1572 "engines": { ··· 1574 1574 } 1575 1575 }, 1576 1576 "node_modules/lucide": { 1577 - "version": "0.575.0", 1578 - "resolved": "https://registry.npmjs.org/lucide/-/lucide-0.575.0.tgz", 1579 - "integrity": "sha512-+xwqZpvrqPioU8bSH49zH2xARfnKyZgIjdnfbex0CrURB3q4wNFhinYN1Z9Q3lE16Q/6N9iEXnStvyS3c70RKw==", 1577 + "version": "0.577.0", 1578 + "resolved": "https://registry.npmjs.org/lucide/-/lucide-0.577.0.tgz", 1579 + "integrity": "sha512-PpC/m5eOItp/WU/GlQPFBXDOhq6HibL73KzYP37OX3LM7VmzWQF8voEj8QRWUFvy9FIKfeDQkWYoyS1D/MdWFA==", 1580 1580 "license": "ISC" 1581 1581 }, 1582 1582 "node_modules/magic-string": { ··· 1590 1590 } 1591 1591 }, 1592 1592 "node_modules/minimatch": { 1593 - "version": "10.2.2", 1594 - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", 1595 - "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", 1593 + "version": "10.2.4", 1594 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", 1595 + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", 1596 1596 "dev": true, 1597 1597 "license": "BlueOak-1.0.0", 1598 1598 "dependencies": { ··· 1694 1694 } 1695 1695 }, 1696 1696 "node_modules/tailwindcss": { 1697 - "version": "4.2.0", 1698 - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.0.tgz", 1699 - "integrity": "sha512-yYzTZ4++b7fNYxFfpnberEEKu43w44aqDMNM9MHMmcKuCH7lL8jJ4yJ7LGHv7rSwiqM0nkiobF9I6cLlpS2P7Q==", 1697 + "version": "4.2.1", 1698 + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", 1699 + "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==", 1700 1700 "dev": true, 1701 1701 "license": "MIT" 1702 1702 },
+3 -3
package.json
··· 16 16 "watch": "npm run css:watch & npm run js:watch" 17 17 }, 18 18 "devDependencies": { 19 - "@tailwindcss/cli": "^4.2.0", 19 + "@tailwindcss/cli": "^4.2.1", 20 20 "@tailwindcss/typography": "^0.5.19", 21 21 "daisyui": "^5.5.19", 22 - "esbuild": "^0.27.3", 22 + "esbuild": "^0.27.4", 23 23 "glob": "^13.0.6", 24 24 "tailwindcss": "^4.2" 25 25 }, ··· 27 27 "actor-typeahead": "^0.1.2", 28 28 "htmx-ext-json-enc": "^2.0.3", 29 29 "htmx.org": "^2.0.8", 30 - "lucide": "^0.575.0" 30 + "lucide": "^0.577.0" 31 31 } 32 32 }
+68
pkg/appview/db/queries.go
··· 1252 1252 return err 1253 1253 } 1254 1254 1255 + // IsManifestReferenced checks if a manifest digest is referenced as a child of 1256 + // any manifest list for the given user. Used to protect manifest list children 1257 + // from auto-removal (they are untagged but still needed by their parent list). 1258 + func IsManifestReferenced(db DBTX, did, digest string) (bool, error) { 1259 + var count int 1260 + err := db.QueryRow(` 1261 + SELECT COUNT(*) FROM manifest_references mr 1262 + JOIN manifests m ON mr.manifest_id = m.id 1263 + WHERE mr.digest = ? AND m.did = ? 1264 + LIMIT 1 1265 + `, digest, did).Scan(&count) 1266 + 1267 + if err != nil { 1268 + return false, err 1269 + } 1270 + 1271 + return count > 0, nil 1272 + } 1273 + 1255 1274 // IsManifestTagged checks if a manifest has any tags 1256 1275 func IsManifestTagged(db DBTX, did, repository, digest string) (bool, error) { 1257 1276 var count int ··· 1293 1312 } 1294 1313 1295 1314 return tags, nil 1315 + } 1316 + 1317 + // GetUntaggedTopLevelManifestDigests returns digests of top-level manifests that have no tags. 1318 + // Uses the same filtering logic as GetTopLevelManifests (manifest lists + orphaned single-arch). 1319 + func GetUntaggedTopLevelManifestDigests(db DBTX, did, repository string) ([]string, error) { 1320 + rows, err := db.Query(` 1321 + WITH manifest_list_children AS ( 1322 + SELECT DISTINCT mr.digest 1323 + FROM manifest_references mr 1324 + JOIN manifests m ON mr.manifest_id = m.id 1325 + WHERE m.did = ? AND m.repository = ? 1326 + ) 1327 + SELECT m.digest 1328 + FROM manifests m 1329 + LEFT JOIN tags t ON m.digest = t.digest AND m.did = t.did AND m.repository = t.repository 1330 + WHERE m.did = ? AND m.repository = ? 1331 + AND ( 1332 + m.media_type LIKE '%index%' OR m.media_type LIKE '%manifest.list%' 1333 + OR 1334 + m.digest NOT IN (SELECT digest FROM manifest_list_children WHERE digest IS NOT NULL) 1335 + ) 1336 + GROUP BY m.id 1337 + HAVING COUNT(t.tag) = 0 1338 + `, did, repository, did, repository) 1339 + if err != nil { 1340 + return nil, err 1341 + } 1342 + defer rows.Close() 1343 + 1344 + var digests []string 1345 + for rows.Next() { 1346 + var digest string 1347 + if err := rows.Scan(&digest); err != nil { 1348 + return nil, err 1349 + } 1350 + digests = append(digests, digest) 1351 + } 1352 + 1353 + if err := rows.Err(); err != nil { 1354 + return nil, err 1355 + } 1356 + 1357 + return digests, nil 1296 1358 } 1297 1359 1298 1360 // GetAttestationDetails returns attestation manifests and their layers for a manifest list. ··· 1793 1855 // UpdateManifestHoldDID rewrites hold_endpoint for all manifests belonging to a user 1794 1856 func (h *HoldDIDDB) UpdateManifestHoldDID(did, oldHoldDID, newHoldDID string) (int64, error) { 1795 1857 return UpdateManifestHoldDID(h.db, did, oldHoldDID, newHoldDID) 1858 + } 1859 + 1860 + // IsManifestReferenced checks if a digest is a child of any manifest list for the user. 1861 + // Implements storage.ManifestReferenceChecker. 1862 + func (h *HoldDIDDB) IsManifestReferenced(did, digest string) (bool, error) { 1863 + return IsManifestReferenced(h.db, did, digest) 1796 1864 } 1797 1865 1798 1866 // RepoCardSortOrder specifies how repo cards should be sorted
+67
pkg/appview/db/queries_test.go
··· 1307 1307 t.Errorf("Deleting non-existent user should not error, got: %v", err) 1308 1308 } 1309 1309 } 1310 + 1311 + func TestIsManifestReferenced(t *testing.T) { 1312 + db, err := InitDB(":memory:", LibsqlConfig{}) 1313 + if err != nil { 1314 + t.Fatalf("Failed to init database: %v", err) 1315 + } 1316 + defer db.Close() 1317 + 1318 + // Insert test user 1319 + if err := UpsertUser(db, &User{ 1320 + DID: "did:plc:test123", 1321 + Handle: "testuser.bsky.social", 1322 + PDSEndpoint: "https://test.pds.example.com", 1323 + LastSeen: time.Now(), 1324 + }); err != nil { 1325 + t.Fatalf("Failed to insert user: %v", err) 1326 + } 1327 + 1328 + // Insert a manifest list 1329 + _, err = db.Exec(` 1330 + INSERT INTO manifests (did, repository, digest, hold_endpoint, schema_version, media_type, created_at) 1331 + VALUES (?, ?, ?, ?, ?, ?, ?) 1332 + `, "did:plc:test123", "myapp", "sha256:indexabc", "did:web:hold.example.com", 2, 1333 + "application/vnd.oci.image.index.v1+json", time.Now()) 1334 + if err != nil { 1335 + t.Fatalf("Failed to insert manifest list: %v", err) 1336 + } 1337 + 1338 + var manifestID int64 1339 + db.QueryRow(`SELECT id FROM manifests WHERE digest = ?`, "sha256:indexabc").Scan(&manifestID) 1340 + 1341 + // Insert a child manifest reference 1342 + _, err = db.Exec(` 1343 + INSERT INTO manifest_references (manifest_id, digest, media_type, size, platform_architecture, platform_os, reference_index) 1344 + VALUES (?, ?, ?, ?, ?, ?, ?) 1345 + `, manifestID, "sha256:childdef", "application/vnd.oci.image.manifest.v1+json", 1000, "amd64", "linux", 0) 1346 + if err != nil { 1347 + t.Fatalf("Failed to insert manifest reference: %v", err) 1348 + } 1349 + 1350 + // Test 1: child digest should be referenced 1351 + referenced, err := IsManifestReferenced(db, "did:plc:test123", "sha256:childdef") 1352 + if err != nil { 1353 + t.Fatalf("IsManifestReferenced error: %v", err) 1354 + } 1355 + if !referenced { 1356 + t.Error("Expected sha256:childdef to be referenced as a manifest list child") 1357 + } 1358 + 1359 + // Test 2: unrelated digest should NOT be referenced 1360 + referenced, err = IsManifestReferenced(db, "did:plc:test123", "sha256:unrelated") 1361 + if err != nil { 1362 + t.Fatalf("IsManifestReferenced error: %v", err) 1363 + } 1364 + if referenced { 1365 + t.Error("Expected sha256:unrelated to NOT be referenced") 1366 + } 1367 + 1368 + // Test 3: same digest but different user should NOT be referenced 1369 + referenced, err = IsManifestReferenced(db, "did:plc:otheruser", "sha256:childdef") 1370 + if err != nil { 1371 + t.Fatalf("IsManifestReferenced error: %v", err) 1372 + } 1373 + if referenced { 1374 + t.Error("Expected sha256:childdef to NOT be referenced for different user") 1375 + } 1376 + }
+68
pkg/appview/handlers/images.go
··· 179 179 w.WriteHeader(http.StatusOK) 180 180 } 181 181 182 + // deleteUntaggedRequest is the JSON body for delete untagged manifests requests 183 + type deleteUntaggedRequest struct { 184 + Repo string `json:"repo"` 185 + } 186 + 187 + // DeleteUntaggedManifestsHandler handles bulk-deleting all untagged manifests in a repository 188 + type DeleteUntaggedManifestsHandler struct { 189 + BaseUIHandler 190 + } 191 + 192 + func (h *DeleteUntaggedManifestsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 193 + user := middleware.GetUser(r) 194 + if user == nil { 195 + http.Error(w, "Unauthorized", http.StatusUnauthorized) 196 + return 197 + } 198 + 199 + var req deleteUntaggedRequest 200 + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 201 + http.Error(w, "Invalid request body", http.StatusBadRequest) 202 + return 203 + } 204 + 205 + digests, err := db.GetUntaggedTopLevelManifestDigests(h.DB, user.DID, req.Repo) 206 + if err != nil { 207 + http.Error(w, fmt.Sprintf("Failed to query untagged manifests: %v", err), http.StatusInternalServerError) 208 + return 209 + } 210 + 211 + if len(digests) == 0 { 212 + render.JSON(w, r, map[string]int{"deleted": 0}) 213 + return 214 + } 215 + 216 + pdsClient := atproto.NewClientWithSessionProvider(user.PDSEndpoint, user.DID, h.Refresher) 217 + 218 + deleted := 0 219 + for _, digest := range digests { 220 + rkey := strings.TrimPrefix(digest, "sha256:") 221 + 222 + if err := pdsClient.DeleteRecord(r.Context(), atproto.ManifestCollection, rkey); err != nil { 223 + if handleOAuthError(r.Context(), h.Refresher, user.DID, err) { 224 + http.Error(w, "Authentication failed, please log in again", http.StatusUnauthorized) 225 + return 226 + } 227 + render.Status(r, http.StatusInternalServerError) 228 + render.JSON(w, r, map[string]any{ 229 + "error": fmt.Sprintf("Failed to delete manifest %s from PDS: %v", digest, err), 230 + "deleted": deleted, 231 + }) 232 + return 233 + } 234 + 235 + if err := db.DeleteManifest(h.DB, user.DID, req.Repo, digest); err != nil { 236 + render.Status(r, http.StatusInternalServerError) 237 + render.JSON(w, r, map[string]any{ 238 + "error": fmt.Sprintf("Failed to delete manifest %s from cache: %v", digest, err), 239 + "deleted": deleted, 240 + }) 241 + return 242 + } 243 + 244 + deleted++ 245 + } 246 + 247 + render.JSON(w, r, map[string]int{"deleted": deleted}) 248 + } 249 + 182 250 // UploadAvatarHandler handles uploading/updating a repository avatar 183 251 type UploadAvatarHandler struct { 184 252 BaseUIHandler
+40 -4
pkg/appview/handlers/settings.go
··· 132 132 PageData 133 133 Meta *PageMeta 134 134 Profile struct { 135 - Handle string 136 - DID string 137 - PDSEndpoint string 138 - DefaultHold string 135 + Handle string 136 + DID string 137 + PDSEndpoint string 138 + DefaultHold string 139 + AutoRemoveUntagged bool 139 140 } 140 141 ActiveHold *HoldDisplay 141 142 OtherHolds []HoldDisplay ··· 156 157 data.Profile.DID = user.DID 157 158 data.Profile.PDSEndpoint = user.PDSEndpoint 158 159 data.Profile.DefaultHold = profile.DefaultHold 160 + data.Profile.AutoRemoveUntagged = profile.AutoRemoveUntagged 159 161 160 162 if err := h.Templates.ExecuteTemplate(w, "settings", data); err != nil { 161 163 http.Error(w, err.Error(), http.StatusInternalServerError) ··· 379 381 }); err != nil { 380 382 slog.Warn("Failed to render alert", "error", err) 381 383 } 384 + } 385 + 386 + // UpdateAutoRemoveUntaggedHandler handles toggling the auto-remove-untagged setting 387 + type UpdateAutoRemoveUntaggedHandler struct { 388 + BaseUIHandler 389 + } 390 + 391 + func (h *UpdateAutoRemoveUntaggedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 392 + user := middleware.GetUser(r) 393 + if user == nil { 394 + http.Error(w, "Unauthorized", http.StatusUnauthorized) 395 + return 396 + } 397 + 398 + // Create ATProto client with session provider 399 + client := atproto.NewClientWithSessionProvider(user.PDSEndpoint, user.DID, h.Refresher) 400 + 401 + // Fetch existing profile 402 + profile, err := storage.GetProfile(r.Context(), client) 403 + if err != nil || profile == nil { 404 + http.Error(w, "Failed to fetch profile", http.StatusInternalServerError) 405 + return 406 + } 407 + 408 + // Toggle the setting (checkbox sends value when checked, absent when unchecked) 409 + profile.AutoRemoveUntagged = !profile.AutoRemoveUntagged 410 + profile.UpdatedAt = time.Now() 411 + 412 + if err := storage.UpdateProfile(r.Context(), client, profile); err != nil { 413 + http.Error(w, "Failed to update profile: "+err.Error(), http.StatusInternalServerError) 414 + return 415 + } 416 + 417 + w.WriteHeader(http.StatusNoContent) 382 418 } 383 419 384 420 // refreshCaptainRecord fetches a hold's captain record via XRPC and caches it locally.
+7
pkg/appview/jetstream/processor.go
··· 772 772 return fmt.Errorf("failed to unmarshal stats record: %w", err) 773 773 } 774 774 775 + // Ensure the owner user exists in the users table (repository_stats has FK to users.did) 776 + if !isDelete && statsRecord.OwnerDID != "" { 777 + if err := p.EnsureUserExists(ctx, statsRecord.OwnerDID); err != nil { 778 + return fmt.Errorf("failed to ensure stats owner user exists: %w", err) 779 + } 780 + } 781 + 775 782 if isDelete { 776 783 // Delete from in-memory cache 777 784 p.statsCache.Delete(holdDID, statsRecord.OwnerDID, statsRecord.Repository)
+59 -46
pkg/appview/middleware/registry.go
··· 170 170 // These are set by main.go during startup and copied into NamespaceResolver instances. 171 171 // After initialization, request handling uses the NamespaceResolver's instance fields. 172 172 var ( 173 - globalRefresher *oauth.Refresher 174 - globalDatabase storage.HoldDIDLookup 175 - globalAuthorizer auth.HoldAuthorizer 176 - globalWebhookDispatcher storage.PushWebhookDispatcher 173 + globalRefresher *oauth.Refresher 174 + globalDatabase storage.HoldDIDLookup 175 + globalAuthorizer auth.HoldAuthorizer 176 + globalWebhookDispatcher storage.PushWebhookDispatcher 177 + globalManifestRefChecker storage.ManifestReferenceChecker 177 178 ) 178 179 179 180 // SetGlobalRefresher sets the OAuth refresher instance during initialization ··· 186 187 // Must be called before the registry starts serving requests 187 188 func SetGlobalDatabase(database storage.HoldDIDLookup) { 188 189 globalDatabase = database 190 + } 191 + 192 + // SetGlobalManifestRefChecker sets the manifest reference checker during initialization 193 + func SetGlobalManifestRefChecker(checker storage.ManifestReferenceChecker) { 194 + globalManifestRefChecker = checker 189 195 } 190 196 191 197 // SetGlobalAuthorizer sets the authorizer instance during initialization ··· 216 222 // NamespaceResolver wraps a namespace and resolves names 217 223 type NamespaceResolver struct { 218 224 distribution.Namespace 219 - defaultHoldDID string // Default hold DID (e.g., "did:web:hold01.atcr.io") 220 - baseURL string // Base URL for error messages (e.g., "https://atcr.io") 221 - testMode bool // If true, fallback to default hold when user's hold is unreachable 222 - refresher *oauth.Refresher // OAuth session manager (copied from global on init) 223 - database storage.HoldDIDLookup // Database for hold DID lookups (copied from global on init) 224 - authorizer auth.HoldAuthorizer // Hold authorization (copied from global on init) 225 - webhookDispatcher storage.PushWebhookDispatcher // Push webhook dispatcher (copied from global on init) 226 - validationCache *validationCache // Request-level service token cache 227 - readmeFetcher *readme.Fetcher // README fetcher for repo pages 225 + defaultHoldDID string // Default hold DID (e.g., "did:web:hold01.atcr.io") 226 + baseURL string // Base URL for error messages (e.g., "https://atcr.io") 227 + testMode bool // If true, fallback to default hold when user's hold is unreachable 228 + refresher *oauth.Refresher // OAuth session manager (copied from global on init) 229 + database storage.HoldDIDLookup // Database for hold DID lookups (copied from global on init) 230 + authorizer auth.HoldAuthorizer // Hold authorization (copied from global on init) 231 + webhookDispatcher storage.PushWebhookDispatcher // Push webhook dispatcher (copied from global on init) 232 + manifestRefChecker storage.ManifestReferenceChecker // Manifest reference checker (copied from global on init) 233 + validationCache *validationCache // Request-level service token cache 234 + readmeFetcher *readme.Fetcher // README fetcher for repo pages 228 235 } 229 236 230 237 // initATProtoResolver initializes the name resolution middleware ··· 251 258 // Copy shared services from globals into the instance 252 259 // This avoids accessing globals during request handling 253 260 return &NamespaceResolver{ 254 - Namespace: ns, 255 - defaultHoldDID: defaultHoldDID, 256 - baseURL: baseURL, 257 - testMode: testMode, 258 - refresher: globalRefresher, 259 - database: globalDatabase, 260 - authorizer: globalAuthorizer, 261 - webhookDispatcher: globalWebhookDispatcher, 262 - validationCache: newValidationCache(), 263 - readmeFetcher: readme.NewFetcher(), 261 + Namespace: ns, 262 + defaultHoldDID: defaultHoldDID, 263 + baseURL: baseURL, 264 + testMode: testMode, 265 + refresher: globalRefresher, 266 + database: globalDatabase, 267 + authorizer: globalAuthorizer, 268 + webhookDispatcher: globalWebhookDispatcher, 269 + manifestRefChecker: globalManifestRefChecker, 270 + validationCache: newValidationCache(), 271 + readmeFetcher: readme.NewFetcher(), 264 272 }, nil 265 273 } 266 274 ··· 297 305 slog.Debug("Resolved identity", "component", "registry/middleware", "did", did, "pds", pdsEndpoint, "handle", handle) 298 306 299 307 // Query for hold DID - either user's hold or default hold service 300 - holdDID := nr.findHoldDID(ctx, did, pdsEndpoint) 308 + // Also returns the sailor profile so we can read preferences (e.g. AutoRemoveUntagged) 309 + holdDID, sailorProfile := nr.findHoldDIDAndProfile(ctx, did, pdsEndpoint) 301 310 if holdDID == "" { 302 311 // This is a fatal configuration error - registry cannot function without a hold service 303 312 return nil, fmt.Errorf("no hold DID configured: ensure default_hold_did is set in middleware config") ··· 476 485 // 3. The refresher already caches sessions efficiently (in-memory + DB) 477 486 // 4. Caching the repository with a stale ATProtoClient causes refresh token errors 478 487 registryCtx := &storage.RegistryContext{ 479 - DID: did, 480 - Handle: handle, 481 - HoldDID: holdDID, 482 - HoldURL: holdURL, 483 - PDSEndpoint: pdsEndpoint, 484 - Repository: repositoryName, 485 - ServiceToken: serviceToken, // Cached service token from puller's PDS 486 - ATProtoClient: atprotoClient, 487 - AuthMethod: authMethod, // Auth method from JWT token 488 - PullerDID: pullerDID, // Authenticated user making the request 489 - PullerPDSEndpoint: pullerPDSEndpoint, // Puller's PDS for service token refresh 490 - Database: nr.database, 491 - Authorizer: nr.authorizer, 492 - Refresher: nr.refresher, 493 - ReadmeFetcher: nr.readmeFetcher, 494 - WebhookDispatcher: nr.webhookDispatcher, 488 + DID: did, 489 + Handle: handle, 490 + HoldDID: holdDID, 491 + HoldURL: holdURL, 492 + PDSEndpoint: pdsEndpoint, 493 + Repository: repositoryName, 494 + ServiceToken: serviceToken, // Cached service token from puller's PDS 495 + ATProtoClient: atprotoClient, 496 + AuthMethod: authMethod, // Auth method from JWT token 497 + PullerDID: pullerDID, // Authenticated user making the request 498 + PullerPDSEndpoint: pullerPDSEndpoint, // Puller's PDS for service token refresh 499 + AutoRemoveUntagged: sailorProfile != nil && sailorProfile.AutoRemoveUntagged, 500 + Database: nr.database, 501 + Authorizer: nr.authorizer, 502 + Refresher: nr.refresher, 503 + ReadmeFetcher: nr.readmeFetcher, 504 + WebhookDispatcher: nr.webhookDispatcher, 505 + ManifestRefChecker: nr.manifestRefChecker, 495 506 } 496 507 497 508 return storage.NewRoutingRepository(repo, registryCtx), nil ··· 512 523 return nr.Namespace.BlobStatter() 513 524 } 514 525 515 - // findHoldDID determines which hold DID to use for blob storage 526 + // findHoldDIDAndProfile determines which hold DID to use for blob storage and 527 + // returns the user's sailor profile (if available) for reading preferences like 528 + // AutoRemoveUntagged without an extra PDS call. 516 529 // Priority order: 517 530 // 1. User's sailor profile defaultHold (if set) 518 531 // 2. AppView's default hold DID 519 532 // Returns a hold DID (e.g., "did:web:hold01.atcr.io"), or empty string if none configured 520 - func (nr *NamespaceResolver) findHoldDID(ctx context.Context, did, pdsEndpoint string) string { 533 + func (nr *NamespaceResolver) findHoldDIDAndProfile(ctx context.Context, did, pdsEndpoint string) (string, *atproto.SailorProfileRecord) { 521 534 // Create ATProto client (without auth - reading public records) 522 535 client := atproto.NewClient(pdsEndpoint, did, "") 523 536 ··· 533 546 // In test mode, verify it's reachable before using it 534 547 if nr.testMode { 535 548 if nr.isHoldReachable(ctx, profile.DefaultHold) { 536 - return profile.DefaultHold 549 + return profile.DefaultHold, profile 537 550 } 538 551 slog.Debug("User's defaultHold unreachable, falling back to default", "component", "registry/middleware/testmode", "default_hold", profile.DefaultHold) 539 - return nr.defaultHoldDID 552 + return nr.defaultHoldDID, profile 540 553 } 541 - return profile.DefaultHold 554 + return profile.DefaultHold, profile 542 555 } 543 556 544 557 // No profile defaultHold - use AppView default 545 - return nr.defaultHoldDID 558 + return nr.defaultHoldDID, profile 546 559 } 547 560 548 561 // resolveSuccessor checks if a hold has declared a successor and returns it.
+4 -4
pkg/appview/middleware/registry_test.go
··· 160 160 } 161 161 162 162 ctx := context.Background() 163 - holdDID := resolver.findHoldDID(ctx, "did:plc:test123", mockPDS.URL) 163 + holdDID, _ := resolver.findHoldDIDAndProfile(ctx, "did:plc:test123", mockPDS.URL) 164 164 165 165 assert.Equal(t, "did:web:default.atcr.io", holdDID, "should fall back to default hold DID") 166 166 } ··· 188 188 } 189 189 190 190 ctx := context.Background() 191 - holdDID := resolver.findHoldDID(ctx, "did:plc:test123", mockPDS.URL) 191 + holdDID, _ := resolver.findHoldDIDAndProfile(ctx, "did:plc:test123", mockPDS.URL) 192 192 193 193 assert.Equal(t, "did:web:user.hold.io", holdDID, "should use sailor profile's defaultHold") 194 194 } ··· 215 215 } 216 216 217 217 ctx := context.Background() 218 - holdDID := resolver.findHoldDID(ctx, "did:plc:test123", mockPDS.URL) 218 + holdDID, _ := resolver.findHoldDIDAndProfile(ctx, "did:plc:test123", mockPDS.URL) 219 219 220 220 // Profile should take priority over hold records and default 221 221 assert.Equal(t, "did:web:profile.hold.io", holdDID, "should prioritize sailor profile over hold records") ··· 244 244 } 245 245 246 246 ctx := context.Background() 247 - holdDID := resolver.findHoldDID(ctx, "did:plc:test123", mockPDS.URL) 247 + holdDID, _ := resolver.findHoldDIDAndProfile(ctx, "did:plc:test123", mockPDS.URL) 248 248 249 249 // In test mode with unreachable hold, should fall back to default 250 250 assert.Equal(t, "did:web:default.atcr.io", holdDID, "should fall back to default in test mode when hold unreachable")
+2 -2
pkg/appview/public/js/bundle.min.js
··· 1 - var ae=(function(){"use strict";let htmx={onLoad:null,process:null,on:null,off:null,trigger:null,ajax:null,find:null,findAll:null,closest:null,values:function(e,t){return getInputValues(e,t||"post").values},remove:null,addClass:null,removeClass:null,toggleClass:null,takeClass:null,swap:null,defineExtension:null,removeExtension:null,logAll:null,logNone:null,logger:null,config:{historyEnabled:!0,historyCacheSize:10,refreshOnHistoryMiss:!1,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:!0,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:!0,allowScriptTags:!0,inlineScriptNonce:"",inlineStyleNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:!1,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",scrollBehavior:"instant",defaultFocusScroll:!1,getCacheBusterParam:!1,globalViewTransitions:!1,methodsThatUseUrlParams:["get","delete"],selfRequestsOnly:!0,ignoreTitle:!1,scrollIntoViewOnBoost:!0,triggerSpecsCache:null,disableInheritance:!1,responseHandling:[{code:"204",swap:!1},{code:"[23]..",swap:!0},{code:"[45]..",swap:!1,error:!0}],allowNestedOobSwaps:!0,historyRestoreAsHxRequest:!0,reportValidityOfForms:!1},parseInterval:null,location,_:null,version:"2.0.8"};htmx.onLoad=onLoadHelper,htmx.process=processNode,htmx.on=addEventListenerImpl,htmx.off=removeEventListenerImpl,htmx.trigger=triggerEvent,htmx.ajax=ajaxHelper,htmx.find=find,htmx.findAll=findAll,htmx.closest=closest,htmx.remove=removeElement,htmx.addClass=addClassToElement,htmx.removeClass=removeClassFromElement,htmx.toggleClass=toggleClassOnElement,htmx.takeClass=takeClassForElement,htmx.swap=swap,htmx.defineExtension=defineExtension,htmx.removeExtension=removeExtension,htmx.logAll=logAll,htmx.logNone=logNone,htmx.parseInterval=parseInterval,htmx._=internalEval;let internalAPI={addTriggerHandler,bodyContains,canAccessLocalStorage,findThisElement,filterValues,swap,hasAttribute,getAttributeValue,getClosestAttributeValue,getClosestMatch,getExpressionVars,getHeaders,getInputValues,getInternalData,getSwapSpecification,getTriggerSpecs,getTarget,makeFragment,mergeObjects,makeSettleInfo,oobSwap,querySelectorExt,settleImmediately,shouldCancel,triggerEvent,triggerErrorEvent,withExtensions},VERBS=["get","post","put","delete","patch"],VERB_SELECTOR=VERBS.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");function parseInterval(e){if(e==null)return;let t=NaN;return e.slice(-2)=="ms"?t=parseFloat(e.slice(0,-2)):e.slice(-1)=="s"?t=parseFloat(e.slice(0,-1))*1e3:e.slice(-1)=="m"?t=parseFloat(e.slice(0,-1))*1e3*60:t=parseFloat(e),isNaN(t)?void 0:t}function getRawAttribute(e,t){return e instanceof Element&&e.getAttribute(t)}function hasAttribute(e,t){return!!e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function getAttributeValue(e,t){return getRawAttribute(e,t)||getRawAttribute(e,"data-"+t)}function parentElt(e){let t=e.parentElement;return!t&&e.parentNode instanceof ShadowRoot?e.parentNode:t}function getDocument(){return document}function getRootNode(e,t){return e.getRootNode?e.getRootNode({composed:t}):getDocument()}function getClosestMatch(e,t){for(;e&&!t(e);)e=parentElt(e);return e||null}function getAttributeValueWithDisinheritance(e,t,n){let r=getAttributeValue(t,n),o=getAttributeValue(t,"hx-disinherit");var i=getAttributeValue(t,"hx-inherit");if(e!==t){if(htmx.config.disableInheritance)return i&&(i==="*"||i.split(" ").indexOf(n)>=0)?r:null;if(o&&(o==="*"||o.split(" ").indexOf(n)>=0))return"unset"}return r}function getClosestAttributeValue(e,t){let n=null;if(getClosestMatch(e,function(r){return!!(n=getAttributeValueWithDisinheritance(e,asElement(r),t))}),n!=="unset")return n}function matches(e,t){return e instanceof Element&&e.matches(t)}function getStartTag(e){let n=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i.exec(e);return n?n[1].toLowerCase():""}function parseHTML(e){return"parseHTMLUnsafe"in Document?Document.parseHTMLUnsafe(e):new DOMParser().parseFromString(e,"text/html")}function takeChildrenFor(e,t){for(;t.childNodes.length>0;)e.append(t.childNodes[0])}function duplicateScript(e){let t=getDocument().createElement("script");return forEach(e.attributes,function(n){t.setAttribute(n.name,n.value)}),t.textContent=e.textContent,t.async=!1,htmx.config.inlineScriptNonce&&(t.nonce=htmx.config.inlineScriptNonce),t}function isJavaScriptScriptNode(e){return e.matches("script")&&(e.type==="text/javascript"||e.type==="module"||e.type==="")}function normalizeScriptTags(e){Array.from(e.querySelectorAll("script")).forEach(t=>{if(isJavaScriptScriptNode(t)){let n=duplicateScript(t),r=t.parentNode;try{r.insertBefore(n,t)}catch(o){logError(o)}finally{t.remove()}}})}function makeFragment(e){let t=e.replace(/<head(\s[^>]*)?>[\s\S]*?<\/head>/i,""),n=getStartTag(t),r;if(n==="html"){r=new DocumentFragment;let i=parseHTML(e);takeChildrenFor(r,i.body),r.title=i.title}else if(n==="body"){r=new DocumentFragment;let i=parseHTML(t);takeChildrenFor(r,i.body),r.title=i.title}else{let i=parseHTML('<body><template class="internal-htmx-wrapper">'+t+"</template></body>");r=i.querySelector("template").content,r.title=i.title;var o=r.querySelector("title");o&&o.parentNode===r&&(o.remove(),r.title=o.innerText)}return r&&(htmx.config.allowScriptTags?normalizeScriptTags(r):r.querySelectorAll("script").forEach(i=>i.remove())),r}function maybeCall(e){e&&e()}function isType(e,t){return Object.prototype.toString.call(e)==="[object "+t+"]"}function isFunction(e){return typeof e=="function"}function isRawObject(e){return isType(e,"Object")}function getInternalData(e){let t="htmx-internal-data",n=e[t];return n||(n=e[t]={}),n}function toArray(e){let t=[];if(e)for(let n=0;n<e.length;n++)t.push(e[n]);return t}function forEach(e,t){if(e)for(let n=0;n<e.length;n++)t(e[n])}function isScrolledIntoView(e){let t=e.getBoundingClientRect(),n=t.top,r=t.bottom;return n<window.innerHeight&&r>=0}function bodyContains(e){return e.getRootNode({composed:!0})===document}function splitOnWhitespace(e){return e.trim().split(/\s+/)}function mergeObjects(e,t){for(let n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e}function parseJSON(e){try{return JSON.parse(e)}catch(t){return logError(t),null}}function canAccessLocalStorage(){let e="htmx:sessionStorageTest";try{return sessionStorage.setItem(e,e),sessionStorage.removeItem(e),!0}catch{return!1}}function normalizePath(e){let t=new URL(e,"http://x");return t&&(e=t.pathname+t.search),e!="/"&&(e=e.replace(/\/+$/,"")),e}function internalEval(str){return maybeEval(getDocument().body,function(){return eval(str)})}function onLoadHelper(e){return htmx.on("htmx:load",function(n){e(n.detail.elt)})}function logAll(){htmx.logger=function(e,t,n){console&&console.log(t,e,n)}}function logNone(){htmx.logger=null}function find(e,t){return typeof e!="string"?e.querySelector(t):find(getDocument(),e)}function findAll(e,t){return typeof e!="string"?e.querySelectorAll(t):findAll(getDocument(),e)}function getWindow(){return window}function removeElement(e,t){e=resolveTarget(e),t?getWindow().setTimeout(function(){removeElement(e),e=null},t):parentElt(e).removeChild(e)}function asElement(e){return e instanceof Element?e:null}function asHtmlElement(e){return e instanceof HTMLElement?e:null}function asString(e){return typeof e=="string"?e:null}function asParentNode(e){return e instanceof Element||e instanceof Document||e instanceof DocumentFragment?e:null}function addClassToElement(e,t,n){e=asElement(resolveTarget(e)),e&&(n?getWindow().setTimeout(function(){addClassToElement(e,t),e=null},n):e.classList&&e.classList.add(t))}function removeClassFromElement(e,t,n){let r=asElement(resolveTarget(e));r&&(n?getWindow().setTimeout(function(){removeClassFromElement(r,t),r=null},n):r.classList&&(r.classList.remove(t),r.classList.length===0&&r.removeAttribute("class")))}function toggleClassOnElement(e,t){e=resolveTarget(e),e.classList.toggle(t)}function takeClassForElement(e,t){e=resolveTarget(e),forEach(e.parentElement.children,function(n){removeClassFromElement(n,t)}),addClassToElement(asElement(e),t)}function closest(e,t){return e=asElement(resolveTarget(e)),e?e.closest(t):null}function startsWith(e,t){return e.substring(0,t.length)===t}function endsWith(e,t){return e.substring(e.length-t.length)===t}function normalizeSelector(e){let t=e.trim();return startsWith(t,"<")&&endsWith(t,"/>")?t.substring(1,t.length-2):t}function querySelectorAllExt(e,t,n){if(t.indexOf("global ")===0)return querySelectorAllExt(e,t.slice(7),!0);e=resolveTarget(e);let r=[];{let s=0,a=0;for(let l=0;l<t.length;l++){let c=t[l];if(c===","&&s===0){r.push(t.substring(a,l)),a=l+1;continue}c==="<"?s++:c==="/"&&l<t.length-1&&t[l+1]===">"&&s--}a<t.length&&r.push(t.substring(a))}let o=[],i=[];for(;r.length>0;){let s=normalizeSelector(r.shift()),a;s.indexOf("closest ")===0?a=closest(asElement(e),normalizeSelector(s.slice(8))):s.indexOf("find ")===0?a=find(asParentNode(e),normalizeSelector(s.slice(5))):s==="next"||s==="nextElementSibling"?a=asElement(e).nextElementSibling:s.indexOf("next ")===0?a=scanForwardQuery(e,normalizeSelector(s.slice(5)),!!n):s==="previous"||s==="previousElementSibling"?a=asElement(e).previousElementSibling:s.indexOf("previous ")===0?a=scanBackwardsQuery(e,normalizeSelector(s.slice(9)),!!n):s==="document"?a=document:s==="window"?a=window:s==="body"?a=document.body:s==="root"?a=getRootNode(e,!!n):s==="host"?a=e.getRootNode().host:i.push(s),a&&o.push(a)}if(i.length>0){let s=i.join(","),a=asParentNode(getRootNode(e,!!n));o.push(...toArray(a.querySelectorAll(s)))}return o}var scanForwardQuery=function(e,t,n){let r=asParentNode(getRootNode(e,n)).querySelectorAll(t);for(let o=0;o<r.length;o++){let i=r[o];if(i.compareDocumentPosition(e)===Node.DOCUMENT_POSITION_PRECEDING)return i}},scanBackwardsQuery=function(e,t,n){let r=asParentNode(getRootNode(e,n)).querySelectorAll(t);for(let o=r.length-1;o>=0;o--){let i=r[o];if(i.compareDocumentPosition(e)===Node.DOCUMENT_POSITION_FOLLOWING)return i}};function querySelectorExt(e,t){return typeof e!="string"?querySelectorAllExt(e,t)[0]:querySelectorAllExt(getDocument().body,e)[0]}function resolveTarget(e,t){return typeof e=="string"?find(asParentNode(t)||document,e):e}function processEventArgs(e,t,n,r){return isFunction(t)?{target:getDocument().body,event:asString(e),listener:t,options:n}:{target:resolveTarget(e),event:asString(t),listener:n,options:r}}function addEventListenerImpl(e,t,n,r){return ready(function(){let i=processEventArgs(e,t,n,r);i.target.addEventListener(i.event,i.listener,i.options)}),isFunction(t)?t:n}function removeEventListenerImpl(e,t,n){return ready(function(){let r=processEventArgs(e,t,n);r.target.removeEventListener(r.event,r.listener)}),isFunction(t)?t:n}let DUMMY_ELT=getDocument().createElement("output");function findAttributeTargets(e,t){let n=getClosestAttributeValue(e,t);if(n){if(n==="this")return[findThisElement(e,t)];{let r=querySelectorAllExt(e,n);if(/(^|,)(\s*)inherit(\s*)($|,)/.test(n)){let i=asElement(getClosestMatch(e,function(s){return s!==e&&hasAttribute(asElement(s),t)}));i&&r.push(...findAttributeTargets(i,t))}return r.length===0?(logError('The selector "'+n+'" on '+t+" returned no matches!"),[DUMMY_ELT]):r}}}function findThisElement(e,t){return asElement(getClosestMatch(e,function(n){return getAttributeValue(asElement(n),t)!=null}))}function getTarget(e){let t=getClosestAttributeValue(e,"hx-target");return t?t==="this"?findThisElement(e,"hx-target"):querySelectorExt(e,t):getInternalData(e).boosted?getDocument().body:e}function shouldSettleAttribute(e){return htmx.config.attributesToSettle.includes(e)}function cloneAttributes(e,t){forEach(Array.from(e.attributes),function(n){!t.hasAttribute(n.name)&&shouldSettleAttribute(n.name)&&e.removeAttribute(n.name)}),forEach(t.attributes,function(n){shouldSettleAttribute(n.name)&&e.setAttribute(n.name,n.value)})}function isInlineSwap(e,t){let n=getExtensions(t);for(let r=0;r<n.length;r++){let o=n[r];try{if(o.isInlineSwap(e))return!0}catch(i){logError(i)}}return e==="outerHTML"}function oobSwap(e,t,n,r){r=r||getDocument();let o="#"+CSS.escape(getRawAttribute(t,"id")),i="outerHTML";e==="true"||(e.indexOf(":")>0?(i=e.substring(0,e.indexOf(":")),o=e.substring(e.indexOf(":")+1)):i=e),t.removeAttribute("hx-swap-oob"),t.removeAttribute("data-hx-swap-oob");let s=querySelectorAllExt(r,o,!1);return s.length?(forEach(s,function(a){let l,c=t.cloneNode(!0);l=getDocument().createDocumentFragment(),l.appendChild(c),isInlineSwap(i,a)||(l=asParentNode(c));let h={shouldSwap:!0,target:a,fragment:l};triggerEvent(a,"htmx:oobBeforeSwap",h)&&(a=h.target,h.shouldSwap&&(handlePreservedElements(l),swapWithStyle(i,a,a,l,n),restorePreservedElements()),forEach(n.elts,function(u){triggerEvent(u,"htmx:oobAfterSwap",h)}))}),t.parentNode.removeChild(t)):(t.parentNode.removeChild(t),triggerErrorEvent(getDocument().body,"htmx:oobErrorNoTarget",{content:t})),e}function restorePreservedElements(){let e=find("#--htmx-preserve-pantry--");if(e){for(let t of[...e.children]){let n=find("#"+t.id);n.parentNode.moveBefore(t,n),n.remove()}e.remove()}}function handlePreservedElements(e){forEach(findAll(e,"[hx-preserve], [data-hx-preserve]"),function(t){let n=getAttributeValue(t,"id"),r=getDocument().getElementById(n);if(r!=null)if(t.moveBefore){let o=find("#--htmx-preserve-pantry--");o==null&&(getDocument().body.insertAdjacentHTML("afterend","<div id='--htmx-preserve-pantry--'></div>"),o=find("#--htmx-preserve-pantry--")),o.moveBefore(r,null)}else t.parentNode.replaceChild(r,t)})}function handleAttributes(e,t,n){forEach(t.querySelectorAll("[id]"),function(r){let o=getRawAttribute(r,"id");if(o&&o.length>0){let i=o.replace("'","\\'"),s=r.tagName.replace(":","\\:"),a=asParentNode(e),l=a&&a.querySelector(s+"[id='"+i+"']");if(l&&l!==a){let c=r.cloneNode();cloneAttributes(r,l),n.tasks.push(function(){cloneAttributes(r,c)})}}})}function makeAjaxLoadTask(e){return function(){removeClassFromElement(e,htmx.config.addedClass),processNode(asElement(e)),processFocus(asParentNode(e)),triggerEvent(e,"htmx:load")}}function processFocus(e){let t="[autofocus]",n=asHtmlElement(matches(e,t)?e:e.querySelector(t));n?.focus()}function insertNodesBefore(e,t,n,r){for(handleAttributes(e,n,r);n.childNodes.length>0;){let o=n.firstChild;addClassToElement(asElement(o),htmx.config.addedClass),e.insertBefore(o,t),o.nodeType!==Node.TEXT_NODE&&o.nodeType!==Node.COMMENT_NODE&&r.tasks.push(makeAjaxLoadTask(o))}}function stringHash(e,t){let n=0;for(;n<e.length;)t=(t<<5)-t+e.charCodeAt(n++)|0;return t}function attributeHash(e){let t=0;for(let n=0;n<e.attributes.length;n++){let r=e.attributes[n];r.value&&(t=stringHash(r.name,t),t=stringHash(r.value,t))}return t}function deInitOnHandlers(e){let t=getInternalData(e);if(t.onHandlers){for(let n=0;n<t.onHandlers.length;n++){let r=t.onHandlers[n];removeEventListenerImpl(e,r.event,r.listener)}delete t.onHandlers}}function deInitNode(e){let t=getInternalData(e);t.timeout&&clearTimeout(t.timeout),t.listenerInfos&&forEach(t.listenerInfos,function(n){n.on&&removeEventListenerImpl(n.on,n.trigger,n.listener)}),deInitOnHandlers(e),forEach(Object.keys(t),function(n){n!=="firstInitCompleted"&&delete t[n]})}function cleanUpElement(e){triggerEvent(e,"htmx:beforeCleanupElement"),deInitNode(e),forEach(e.children,function(t){cleanUpElement(t)})}function swapOuterHTML(e,t,n){if(e.tagName==="BODY")return swapInnerHTML(e,t,n);let r,o=e.previousSibling,i=parentElt(e);if(i){for(insertNodesBefore(i,e,t,n),o==null?r=i.firstChild:r=o.nextSibling,n.elts=n.elts.filter(function(s){return s!==e});r&&r!==e;)r instanceof Element&&n.elts.push(r),r=r.nextSibling;cleanUpElement(e),e.remove()}}function swapAfterBegin(e,t,n){return insertNodesBefore(e,e.firstChild,t,n)}function swapBeforeBegin(e,t,n){return insertNodesBefore(parentElt(e),e,t,n)}function swapBeforeEnd(e,t,n){return insertNodesBefore(e,null,t,n)}function swapAfterEnd(e,t,n){return insertNodesBefore(parentElt(e),e.nextSibling,t,n)}function swapDelete(e){cleanUpElement(e);let t=parentElt(e);if(t)return t.removeChild(e)}function swapInnerHTML(e,t,n){let r=e.firstChild;if(insertNodesBefore(e,r,t,n),r){for(;r.nextSibling;)cleanUpElement(r.nextSibling),e.removeChild(r.nextSibling);cleanUpElement(r),e.removeChild(r)}}function swapWithStyle(e,t,n,r,o){switch(e){case"none":return;case"outerHTML":swapOuterHTML(n,r,o);return;case"afterbegin":swapAfterBegin(n,r,o);return;case"beforebegin":swapBeforeBegin(n,r,o);return;case"beforeend":swapBeforeEnd(n,r,o);return;case"afterend":swapAfterEnd(n,r,o);return;case"delete":swapDelete(n);return;default:var i=getExtensions(t);for(let s=0;s<i.length;s++){let a=i[s];try{let l=a.handleSwap(e,n,r,o);if(l){if(Array.isArray(l))for(let c=0;c<l.length;c++){let h=l[c];h.nodeType!==Node.TEXT_NODE&&h.nodeType!==Node.COMMENT_NODE&&o.tasks.push(makeAjaxLoadTask(h))}return}}catch(l){logError(l)}}e==="innerHTML"?swapInnerHTML(n,r,o):swapWithStyle(htmx.config.defaultSwapStyle,t,n,r,o)}}function findAndSwapOobElements(e,t,n){var r=findAll(e,"[hx-swap-oob], [data-hx-swap-oob]");return forEach(r,function(o){if(htmx.config.allowNestedOobSwaps||o.parentElement===null){let i=getAttributeValue(o,"hx-swap-oob");i!=null&&oobSwap(i,o,t,n)}else o.removeAttribute("hx-swap-oob"),o.removeAttribute("data-hx-swap-oob")}),r.length>0}function swap(e,t,n,r){r||(r={});let o=null,i=null,s=function(){maybeCall(r.beforeSwapCallback),e=resolveTarget(e);let c=r.contextElement?getRootNode(r.contextElement,!1):getDocument(),h=document.activeElement,u={};u={elt:h,start:h?h.selectionStart:null,end:h?h.selectionEnd:null};let f=makeSettleInfo(e);if(n.swapStyle==="textContent")e.textContent=t;else{let d=makeFragment(t);if(f.title=r.title||d.title,r.historyRequest&&(d=d.querySelector("[hx-history-elt],[data-hx-history-elt]")||d),r.selectOOB){let y=r.selectOOB.split(",");for(let m=0;m<y.length;m++){let x=y[m].split(":",2),b=x[0].trim();b.indexOf("#")===0&&(b=b.substring(1));let E=x[1]||"true",g=d.querySelector("#"+b);g&&oobSwap(E,g,f,c)}}if(findAndSwapOobElements(d,f,c),forEach(findAll(d,"template"),function(y){y.content&&findAndSwapOobElements(y.content,f,c)&&y.remove()}),r.select){let y=getDocument().createDocumentFragment();forEach(d.querySelectorAll(r.select),function(m){y.appendChild(m)}),d=y}handlePreservedElements(d),swapWithStyle(n.swapStyle,r.contextElement,e,d,f),restorePreservedElements()}if(u.elt&&!bodyContains(u.elt)&&getRawAttribute(u.elt,"id")){let d=document.getElementById(getRawAttribute(u.elt,"id")),y={preventScroll:n.focusScroll!==void 0?!n.focusScroll:!htmx.config.defaultFocusScroll};if(d){if(u.start&&d.setSelectionRange)try{d.setSelectionRange(u.start,u.end)}catch{}d.focus(y)}}e.classList.remove(htmx.config.swappingClass),forEach(f.elts,function(d){d.classList&&d.classList.add(htmx.config.settlingClass),triggerEvent(d,"htmx:afterSwap",r.eventInfo)}),maybeCall(r.afterSwapCallback),n.ignoreTitle||handleTitle(f.title);let v=function(){if(forEach(f.tasks,function(d){d.call()}),forEach(f.elts,function(d){d.classList&&d.classList.remove(htmx.config.settlingClass),triggerEvent(d,"htmx:afterSettle",r.eventInfo)}),r.anchor){let d=asElement(resolveTarget("#"+r.anchor));d&&d.scrollIntoView({block:"start",behavior:"auto"})}updateScrollState(f.elts,n),maybeCall(r.afterSettleCallback),maybeCall(o)};n.settleDelay>0?getWindow().setTimeout(v,n.settleDelay):v()},a=htmx.config.globalViewTransitions;n.hasOwnProperty("transition")&&(a=n.transition);let l=r.contextElement||getDocument();if(a&&triggerEvent(l,"htmx:beforeTransition",r.eventInfo)&&typeof Promise<"u"&&document.startViewTransition){let c=new Promise(function(u,f){o=u,i=f}),h=s;s=function(){document.startViewTransition(function(){return h(),c})}}try{n?.swapDelay&&n.swapDelay>0?getWindow().setTimeout(s,n.swapDelay):s()}catch(c){throw triggerErrorEvent(l,"htmx:swapError",r.eventInfo),maybeCall(i),c}}function handleTriggerHeader(e,t,n){let r=e.getResponseHeader(t);if(r.indexOf("{")===0){let o=parseJSON(r);for(let i in o)if(o.hasOwnProperty(i)){let s=o[i];isRawObject(s)?n=s.target!==void 0?s.target:n:s={value:s},triggerEvent(n,i,s)}}else{let o=r.split(",");for(let i=0;i<o.length;i++)triggerEvent(n,o[i].trim(),[])}}let WHITESPACE=/\s/,WHITESPACE_OR_COMMA=/[\s,]/,SYMBOL_START=/[_$a-zA-Z]/,SYMBOL_CONT=/[_$a-zA-Z0-9]/,STRINGISH_START=['"',"'","/"],NOT_WHITESPACE=/[^\s]/,COMBINED_SELECTOR_START=/[{(]/,COMBINED_SELECTOR_END=/[})]/;function tokenizeString(e){let t=[],n=0;for(;n<e.length;){if(SYMBOL_START.exec(e.charAt(n))){for(var r=n;SYMBOL_CONT.exec(e.charAt(n+1));)n++;t.push(e.substring(r,n+1))}else if(STRINGISH_START.indexOf(e.charAt(n))!==-1){let o=e.charAt(n);var r=n;for(n++;n<e.length&&e.charAt(n)!==o;)e.charAt(n)==="\\"&&n++,n++;t.push(e.substring(r,n+1))}else{let o=e.charAt(n);t.push(o)}n++}return t}function isPossibleRelativeReference(e,t,n){return SYMBOL_START.exec(e.charAt(0))&&e!=="true"&&e!=="false"&&e!=="this"&&e!==n&&t!=="."}function maybeGenerateConditional(e,t,n){if(t[0]==="["){t.shift();let r=1,o=" return (function("+n+"){ return (",i=null;for(;t.length>0;){let s=t[0];if(s==="]"){if(r--,r===0){i===null&&(o=o+"true"),t.shift(),o+=")})";try{let a=maybeEval(e,function(){return Function(o)()},function(){return!0});return a.source=o,a}catch(a){return triggerErrorEvent(getDocument().body,"htmx:syntax:error",{error:a,source:o}),null}}}else s==="["&&r++;isPossibleRelativeReference(s,i,n)?o+="(("+n+"."+s+") ? ("+n+"."+s+") : (window."+s+"))":o=o+s,i=t.shift()}}}function consumeUntil(e,t){let n="";for(;e.length>0&&!t.test(e[0]);)n+=e.shift();return n}function consumeCSSSelector(e){let t;return e.length>0&&COMBINED_SELECTOR_START.test(e[0])?(e.shift(),t=consumeUntil(e,COMBINED_SELECTOR_END).trim(),e.shift()):t=consumeUntil(e,WHITESPACE_OR_COMMA),t}let INPUT_SELECTOR="input, textarea, select";function parseAndCacheTrigger(e,t,n){let r=[],o=tokenizeString(t);do{consumeUntil(o,NOT_WHITESPACE);let a=o.length,l=consumeUntil(o,/[,\[\s]/);if(l!=="")if(l==="every"){let c={trigger:"every"};consumeUntil(o,NOT_WHITESPACE),c.pollInterval=parseInterval(consumeUntil(o,/[,\[\s]/)),consumeUntil(o,NOT_WHITESPACE);var i=maybeGenerateConditional(e,o,"event");i&&(c.eventFilter=i),r.push(c)}else{let c={trigger:l};var i=maybeGenerateConditional(e,o,"event");for(i&&(c.eventFilter=i),consumeUntil(o,NOT_WHITESPACE);o.length>0&&o[0]!==",";){let u=o.shift();if(u==="changed")c.changed=!0;else if(u==="once")c.once=!0;else if(u==="consume")c.consume=!0;else if(u==="delay"&&o[0]===":")o.shift(),c.delay=parseInterval(consumeUntil(o,WHITESPACE_OR_COMMA));else if(u==="from"&&o[0]===":"){if(o.shift(),COMBINED_SELECTOR_START.test(o[0]))var s=consumeCSSSelector(o);else{var s=consumeUntil(o,WHITESPACE_OR_COMMA);if(s==="closest"||s==="find"||s==="next"||s==="previous"){o.shift();let v=consumeCSSSelector(o);v.length>0&&(s+=" "+v)}}c.from=s}else u==="target"&&o[0]===":"?(o.shift(),c.target=consumeCSSSelector(o)):u==="throttle"&&o[0]===":"?(o.shift(),c.throttle=parseInterval(consumeUntil(o,WHITESPACE_OR_COMMA))):u==="queue"&&o[0]===":"?(o.shift(),c.queue=consumeUntil(o,WHITESPACE_OR_COMMA)):u==="root"&&o[0]===":"?(o.shift(),c[u]=consumeCSSSelector(o)):u==="threshold"&&o[0]===":"?(o.shift(),c[u]=consumeUntil(o,WHITESPACE_OR_COMMA)):triggerErrorEvent(e,"htmx:syntax:error",{token:o.shift()});consumeUntil(o,NOT_WHITESPACE)}r.push(c)}o.length===a&&triggerErrorEvent(e,"htmx:syntax:error",{token:o.shift()}),consumeUntil(o,NOT_WHITESPACE)}while(o[0]===","&&o.shift());return n&&(n[t]=r),r}function getTriggerSpecs(e){let t=getAttributeValue(e,"hx-trigger"),n=[];if(t){let r=htmx.config.triggerSpecsCache;n=r&&r[t]||parseAndCacheTrigger(e,t,r)}return n.length>0?n:matches(e,"form")?[{trigger:"submit"}]:matches(e,'input[type="button"], input[type="submit"]')?[{trigger:"click"}]:matches(e,INPUT_SELECTOR)?[{trigger:"change"}]:[{trigger:"click"}]}function cancelPolling(e){getInternalData(e).cancelled=!0}function processPolling(e,t,n){let r=getInternalData(e);r.timeout=getWindow().setTimeout(function(){bodyContains(e)&&r.cancelled!==!0&&(maybeFilterEvent(n,e,makeEvent("hx:poll:trigger",{triggerSpec:n,target:e}))||t(e),processPolling(e,t,n))},n.pollInterval)}function isLocalLink(e){return location.hostname===e.hostname&&getRawAttribute(e,"href")&&getRawAttribute(e,"href").indexOf("#")!==0}function eltIsDisabled(e){return closest(e,htmx.config.disableSelector)}function boostElement(e,t,n){if(e instanceof HTMLAnchorElement&&isLocalLink(e)&&(e.target===""||e.target==="_self")||e.tagName==="FORM"&&String(getRawAttribute(e,"method")).toLowerCase()!=="dialog"){t.boosted=!0;let r,o;if(e.tagName==="A")r="get",o=getRawAttribute(e,"href");else{let i=getRawAttribute(e,"method");r=i?i.toLowerCase():"get",o=getRawAttribute(e,"action"),(o==null||o==="")&&(o=location.href),r==="get"&&o.includes("?")&&(o=o.replace(/\?[^#]+/,""))}n.forEach(function(i){addEventListener(e,function(s,a){let l=asElement(s);if(eltIsDisabled(l)){cleanUpElement(l);return}issueAjaxRequest(r,o,l,a)},t,i,!0)})}}function shouldCancel(e,t){if(e.type==="submit"&&t.tagName==="FORM")return!0;if(e.type==="click"){let n=t.closest('input[type="submit"], button');if(n&&n.form&&n.type==="submit")return!0;let r=t.closest("a"),o=/^#.+/;if(r&&r.href&&!o.test(r.getAttribute("href")))return!0}return!1}function ignoreBoostedAnchorCtrlClick(e,t){return getInternalData(e).boosted&&e instanceof HTMLAnchorElement&&t.type==="click"&&(t.ctrlKey||t.metaKey)}function maybeFilterEvent(e,t,n){let r=e.eventFilter;if(r)try{return r.call(t,n)!==!0}catch(o){let i=r.source;return triggerErrorEvent(getDocument().body,"htmx:eventFilter:error",{error:o,source:i}),!0}return!1}function addEventListener(e,t,n,r,o){let i=getInternalData(e),s;r.from?s=querySelectorAllExt(e,r.from):s=[e],r.changed&&("lastValue"in i||(i.lastValue=new WeakMap),s.forEach(function(a){i.lastValue.has(r)||i.lastValue.set(r,new WeakMap),i.lastValue.get(r).set(a,a.value)})),forEach(s,function(a){let l=function(c){if(!bodyContains(e)){a.removeEventListener(r.trigger,l);return}if(ignoreBoostedAnchorCtrlClick(e,c)||((o||shouldCancel(c,a))&&c.preventDefault(),maybeFilterEvent(r,e,c)))return;let h=getInternalData(c);if(h.triggerSpec=r,h.handledFor==null&&(h.handledFor=[]),h.handledFor.indexOf(e)<0){if(h.handledFor.push(e),r.consume&&c.stopPropagation(),r.target&&c.target&&!matches(asElement(c.target),r.target))return;if(r.once){if(i.triggeredOnce)return;i.triggeredOnce=!0}if(r.changed){let u=c.target,f=u.value,v=i.lastValue.get(r);if(v.has(u)&&v.get(u)===f)return;v.set(u,f)}if(i.delayed&&clearTimeout(i.delayed),i.throttle)return;r.throttle>0?i.throttle||(triggerEvent(e,"htmx:trigger"),t(e,c),i.throttle=getWindow().setTimeout(function(){i.throttle=null},r.throttle)):r.delay>0?i.delayed=getWindow().setTimeout(function(){triggerEvent(e,"htmx:trigger"),t(e,c)},r.delay):(triggerEvent(e,"htmx:trigger"),t(e,c))}};n.listenerInfos==null&&(n.listenerInfos=[]),n.listenerInfos.push({trigger:r.trigger,listener:l,on:a}),a.addEventListener(r.trigger,l)})}let windowIsScrolling=!1,scrollHandler=null;function initScrollHandler(){scrollHandler||(scrollHandler=function(){windowIsScrolling=!0},window.addEventListener("scroll",scrollHandler),window.addEventListener("resize",scrollHandler),setInterval(function(){windowIsScrolling&&(windowIsScrolling=!1,forEach(getDocument().querySelectorAll("[hx-trigger*='revealed'],[data-hx-trigger*='revealed']"),function(e){maybeReveal(e)}))},200))}function maybeReveal(e){!hasAttribute(e,"data-hx-revealed")&&isScrolledIntoView(e)&&(e.setAttribute("data-hx-revealed","true"),getInternalData(e).initHash?triggerEvent(e,"revealed"):e.addEventListener("htmx:afterProcessNode",function(){triggerEvent(e,"revealed")},{once:!0}))}function loadImmediately(e,t,n,r){let o=function(){n.loaded||(n.loaded=!0,triggerEvent(e,"htmx:trigger"),t(e))};r>0?getWindow().setTimeout(o,r):o()}function processVerbs(e,t,n){let r=!1;return forEach(VERBS,function(o){if(hasAttribute(e,"hx-"+o)){let i=getAttributeValue(e,"hx-"+o);r=!0,t.path=i,t.verb=o,n.forEach(function(s){addTriggerHandler(e,s,t,function(a,l){let c=asElement(a);if(eltIsDisabled(c)){cleanUpElement(c);return}issueAjaxRequest(o,i,c,l)})})}}),r}function addTriggerHandler(e,t,n,r){if(t.trigger==="revealed")initScrollHandler(),addEventListener(e,r,n,t),maybeReveal(asElement(e));else if(t.trigger==="intersect"){let o={};t.root&&(o.root=querySelectorExt(e,t.root)),t.threshold&&(o.threshold=parseFloat(t.threshold)),new IntersectionObserver(function(s){for(let a=0;a<s.length;a++)if(s[a].isIntersecting){triggerEvent(e,"intersect");break}},o).observe(asElement(e)),addEventListener(asElement(e),r,n,t)}else!n.firstInitCompleted&&t.trigger==="load"?maybeFilterEvent(t,e,makeEvent("load",{elt:e}))||loadImmediately(asElement(e),r,n,t.delay):t.pollInterval>0?(n.polling=!0,processPolling(asElement(e),r,t)):addEventListener(e,r,n,t)}function shouldProcessHxOn(e){let t=asElement(e);if(!t)return!1;let n=t.attributes;for(let r=0;r<n.length;r++){let o=n[r].name;if(startsWith(o,"hx-on:")||startsWith(o,"data-hx-on:")||startsWith(o,"hx-on-")||startsWith(o,"data-hx-on-"))return!0}return!1}let HX_ON_QUERY=new XPathEvaluator().createExpression('.//*[@*[ starts-with(name(), "hx-on:") or starts-with(name(), "data-hx-on:") or starts-with(name(), "hx-on-") or starts-with(name(), "data-hx-on-") ]]');function processHXOnRoot(e,t){shouldProcessHxOn(e)&&t.push(asElement(e));let n=HX_ON_QUERY.evaluate(e),r=null;for(;r=n.iterateNext();)t.push(asElement(r))}function findHxOnWildcardElements(e){let t=[];if(e instanceof DocumentFragment)for(let n of e.childNodes)processHXOnRoot(n,t);else processHXOnRoot(e,t);return t}function findElementsToProcess(e){if(e.querySelectorAll){let n=", [hx-boost] a, [data-hx-boost] a, a[hx-boost], a[data-hx-boost]",r=[];for(let i in extensions){let s=extensions[i];if(s.getSelectors){var t=s.getSelectors();t&&r.push(t)}}return e.querySelectorAll(VERB_SELECTOR+n+", form, [type='submit'], [hx-ext], [data-hx-ext], [hx-trigger], [data-hx-trigger]"+r.flat().map(i=>", "+i).join(""))}else return[]}function maybeSetLastButtonClicked(e){let t=getTargetButton(e.target),n=getRelatedFormData(e);n&&(n.lastButtonClicked=t)}function maybeUnsetLastButtonClicked(e){let t=getRelatedFormData(e);t&&(t.lastButtonClicked=null)}function getTargetButton(e){return closest(asElement(e),"button, input[type='submit']")}function getRelatedForm(e){return e.form||closest(e,"form")}function getRelatedFormData(e){let t=getTargetButton(e.target);if(!t)return;let n=getRelatedForm(t);if(n)return getInternalData(n)}function initButtonTracking(e){e.addEventListener("click",maybeSetLastButtonClicked),e.addEventListener("focusin",maybeSetLastButtonClicked),e.addEventListener("focusout",maybeUnsetLastButtonClicked)}function addHxOnEventHandler(e,t,n){let r=getInternalData(e);Array.isArray(r.onHandlers)||(r.onHandlers=[]);let o,i=function(s){maybeEval(e,function(){eltIsDisabled(e)||(o||(o=new Function("event",n)),o.call(e,s))})};e.addEventListener(t,i),r.onHandlers.push({event:t,listener:i})}function processHxOnWildcard(e){deInitOnHandlers(e);for(let t=0;t<e.attributes.length;t++){let n=e.attributes[t].name,r=e.attributes[t].value;if(startsWith(n,"hx-on")||startsWith(n,"data-hx-on")){let o=n.indexOf("-on")+3,i=n.slice(o,o+1);if(i==="-"||i===":"){let s=n.slice(o+1);startsWith(s,":")?s="htmx"+s:startsWith(s,"-")?s="htmx:"+s.slice(1):startsWith(s,"htmx-")&&(s="htmx:"+s.slice(5)),addHxOnEventHandler(e,s,r)}}}}function initNode(e){triggerEvent(e,"htmx:beforeProcessNode");let t=getInternalData(e),n=getTriggerSpecs(e);processVerbs(e,t,n)||(getClosestAttributeValue(e,"hx-boost")==="true"?boostElement(e,t,n):hasAttribute(e,"hx-trigger")&&n.forEach(function(o){addTriggerHandler(e,o,t,function(){})})),(e.tagName==="FORM"||getRawAttribute(e,"type")==="submit"&&hasAttribute(e,"form"))&&initButtonTracking(e),t.firstInitCompleted=!0,triggerEvent(e,"htmx:afterProcessNode")}function maybeDeInitAndHash(e){if(!(e instanceof Element))return!1;let t=getInternalData(e),n=attributeHash(e);return t.initHash!==n?(deInitNode(e),t.initHash=n,!0):!1}function processNode(e){if(e=resolveTarget(e),eltIsDisabled(e)){cleanUpElement(e);return}let t=[];maybeDeInitAndHash(e)&&t.push(e),forEach(findElementsToProcess(e),function(n){if(eltIsDisabled(n)){cleanUpElement(n);return}maybeDeInitAndHash(n)&&t.push(n)}),forEach(findHxOnWildcardElements(e),processHxOnWildcard),forEach(t,initNode)}function kebabEventName(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase()}function makeEvent(e,t){return new CustomEvent(e,{bubbles:!0,cancelable:!0,composed:!0,detail:t})}function triggerErrorEvent(e,t,n){triggerEvent(e,t,mergeObjects({error:t},n))}function ignoreEventForLogging(e){return e==="htmx:afterProcessNode"}function withExtensions(e,t,n){forEach(getExtensions(e,[],n),function(r){try{t(r)}catch(o){logError(o)}})}function logError(e){console.error(e)}function triggerEvent(e,t,n){e=resolveTarget(e),n==null&&(n={}),n.elt=e;let r=makeEvent(t,n);htmx.logger&&!ignoreEventForLogging(t)&&htmx.logger(e,t,n),n.error&&(logError(n.error),triggerEvent(e,"htmx:error",{errorInfo:n}));let o=e.dispatchEvent(r),i=kebabEventName(t);if(o&&i!==t){let s=makeEvent(i,r.detail);o=o&&e.dispatchEvent(s)}return withExtensions(asElement(e),function(s){o=o&&s.onEvent(t,r)!==!1&&!r.defaultPrevented}),o}let currentPathForHistory;function setCurrentPathForHistory(e){currentPathForHistory=e,canAccessLocalStorage()&&sessionStorage.setItem("htmx-current-path-for-history",e)}setCurrentPathForHistory(location.pathname+location.search);function getHistoryElement(){return getDocument().querySelector("[hx-history-elt],[data-hx-history-elt]")||getDocument().body}function saveToHistoryCache(e,t){if(!canAccessLocalStorage())return;let n=cleanInnerHtmlForHistory(t),r=getDocument().title,o=window.scrollY;if(htmx.config.historyCacheSize<=0){sessionStorage.removeItem("htmx-history-cache");return}e=normalizePath(e);let i=parseJSON(sessionStorage.getItem("htmx-history-cache"))||[];for(let a=0;a<i.length;a++)if(i[a].url===e){i.splice(a,1);break}let s={url:e,content:n,title:r,scroll:o};for(triggerEvent(getDocument().body,"htmx:historyItemCreated",{item:s,cache:i}),i.push(s);i.length>htmx.config.historyCacheSize;)i.shift();for(;i.length>0;)try{sessionStorage.setItem("htmx-history-cache",JSON.stringify(i));break}catch(a){triggerErrorEvent(getDocument().body,"htmx:historyCacheError",{cause:a,cache:i}),i.shift()}}function getCachedHistory(e){if(!canAccessLocalStorage())return null;e=normalizePath(e);let t=parseJSON(sessionStorage.getItem("htmx-history-cache"))||[];for(let n=0;n<t.length;n++)if(t[n].url===e)return t[n];return null}function cleanInnerHtmlForHistory(e){let t=htmx.config.requestClass,n=e.cloneNode(!0);return forEach(findAll(n,"."+t),function(r){removeClassFromElement(r,t)}),forEach(findAll(n,"[data-disabled-by-htmx]"),function(r){r.removeAttribute("disabled")}),n.innerHTML}function saveCurrentPageToHistory(){let e=getHistoryElement(),t=currentPathForHistory;canAccessLocalStorage()&&(t=sessionStorage.getItem("htmx-current-path-for-history")),t=t||location.pathname+location.search,getDocument().querySelector('[hx-history="false" i],[data-hx-history="false" i]')||(triggerEvent(getDocument().body,"htmx:beforeHistorySave",{path:t,historyElt:e}),saveToHistoryCache(t,e)),htmx.config.historyEnabled&&history.replaceState({htmx:!0},getDocument().title,location.href)}function pushUrlIntoHistory(e){htmx.config.getCacheBusterParam&&(e=e.replace(/org\.htmx\.cache-buster=[^&]*&?/,""),(endsWith(e,"&")||endsWith(e,"?"))&&(e=e.slice(0,-1))),htmx.config.historyEnabled&&history.pushState({htmx:!0},"",e),setCurrentPathForHistory(e)}function replaceUrlInHistory(e){htmx.config.historyEnabled&&history.replaceState({htmx:!0},"",e),setCurrentPathForHistory(e)}function settleImmediately(e){forEach(e,function(t){t.call(void 0)})}function loadHistoryFromServer(e){let t=new XMLHttpRequest,n={swapStyle:"innerHTML",swapDelay:0,settleDelay:0},r={path:e,xhr:t,historyElt:getHistoryElement(),swapSpec:n};t.open("GET",e,!0),htmx.config.historyRestoreAsHxRequest&&t.setRequestHeader("HX-Request","true"),t.setRequestHeader("HX-History-Restore-Request","true"),t.setRequestHeader("HX-Current-URL",location.href),t.onload=function(){this.status>=200&&this.status<400?(r.response=this.response,triggerEvent(getDocument().body,"htmx:historyCacheMissLoad",r),swap(r.historyElt,r.response,n,{contextElement:r.historyElt,historyRequest:!0}),setCurrentPathForHistory(r.path),triggerEvent(getDocument().body,"htmx:historyRestore",{path:e,cacheMiss:!0,serverResponse:r.response})):triggerErrorEvent(getDocument().body,"htmx:historyCacheMissLoadError",r)},triggerEvent(getDocument().body,"htmx:historyCacheMiss",r)&&t.send()}function restoreHistory(e){saveCurrentPageToHistory(),e=e||location.pathname+location.search;let t=getCachedHistory(e);if(t){let n={swapStyle:"innerHTML",swapDelay:0,settleDelay:0,scroll:t.scroll},r={path:e,item:t,historyElt:getHistoryElement(),swapSpec:n};triggerEvent(getDocument().body,"htmx:historyCacheHit",r)&&(swap(r.historyElt,t.content,n,{contextElement:r.historyElt,title:t.title}),setCurrentPathForHistory(r.path),triggerEvent(getDocument().body,"htmx:historyRestore",r))}else htmx.config.refreshOnHistoryMiss?htmx.location.reload(!0):loadHistoryFromServer(e)}function addRequestIndicatorClasses(e){let t=findAttributeTargets(e,"hx-indicator");return t==null&&(t=[e]),forEach(t,function(n){let r=getInternalData(n);r.requestCount=(r.requestCount||0)+1,n.classList.add.call(n.classList,htmx.config.requestClass)}),t}function disableElements(e){let t=findAttributeTargets(e,"hx-disabled-elt");return t==null&&(t=[]),forEach(t,function(n){let r=getInternalData(n);r.requestCount=(r.requestCount||0)+1,n.setAttribute("disabled",""),n.setAttribute("data-disabled-by-htmx","")}),t}function removeRequestIndicators(e,t){forEach(e.concat(t),function(n){let r=getInternalData(n);r.requestCount=(r.requestCount||1)-1}),forEach(e,function(n){getInternalData(n).requestCount===0&&n.classList.remove.call(n.classList,htmx.config.requestClass)}),forEach(t,function(n){getInternalData(n).requestCount===0&&(n.removeAttribute("disabled"),n.removeAttribute("data-disabled-by-htmx"))})}function haveSeenNode(e,t){for(let n=0;n<e.length;n++)if(e[n].isSameNode(t))return!0;return!1}function shouldInclude(e){let t=e;return t.name===""||t.name==null||t.disabled||closest(t,"fieldset[disabled]")||t.type==="button"||t.type==="submit"||t.tagName==="image"||t.tagName==="reset"||t.tagName==="file"?!1:t.type==="checkbox"||t.type==="radio"?t.checked:!0}function addValueToFormData(e,t,n){e!=null&&t!=null&&(Array.isArray(t)?t.forEach(function(r){n.append(e,r)}):n.append(e,t))}function removeValueFromFormData(e,t,n){if(e!=null&&t!=null){let r=n.getAll(e);Array.isArray(t)?r=r.filter(o=>t.indexOf(o)<0):r=r.filter(o=>o!==t),n.delete(e),forEach(r,o=>n.append(e,o))}}function getValueFromInput(e){return e instanceof HTMLSelectElement&&e.multiple?toArray(e.querySelectorAll("option:checked")).map(function(t){return t.value}):e instanceof HTMLInputElement&&e.files?toArray(e.files):e.value}function processInputValue(e,t,n,r,o){if(!(r==null||haveSeenNode(e,r))){if(e.push(r),shouldInclude(r)){let i=getRawAttribute(r,"name");addValueToFormData(i,getValueFromInput(r),t),o&&validateElement(r,n)}r instanceof HTMLFormElement&&(forEach(r.elements,function(i){e.indexOf(i)>=0?removeValueFromFormData(i.name,getValueFromInput(i),t):e.push(i),o&&validateElement(i,n)}),new FormData(r).forEach(function(i,s){i instanceof File&&i.name===""||addValueToFormData(s,i,t)}))}}function validateElement(e,t){let n=e;n.willValidate&&(triggerEvent(n,"htmx:validation:validate"),n.checkValidity()||(triggerEvent(n,"htmx:validation:failed",{message:n.validationMessage,validity:n.validity})&&!t.length&&htmx.config.reportValidityOfForms&&n.reportValidity(),t.push({elt:n,message:n.validationMessage,validity:n.validity})))}function overrideFormData(e,t){for(let n of t.keys())e.delete(n);return t.forEach(function(n,r){e.append(r,n)}),e}function getInputValues(e,t){let n=[],r=new FormData,o=new FormData,i=[],s=getInternalData(e);s.lastButtonClicked&&!bodyContains(s.lastButtonClicked)&&(s.lastButtonClicked=null);let a=e instanceof HTMLFormElement&&e.noValidate!==!0||getAttributeValue(e,"hx-validate")==="true";if(s.lastButtonClicked&&(a=a&&s.lastButtonClicked.formNoValidate!==!0),t!=="get"&&processInputValue(n,o,i,getRelatedForm(e),a),processInputValue(n,r,i,e,a),s.lastButtonClicked||e.tagName==="BUTTON"||e.tagName==="INPUT"&&getRawAttribute(e,"type")==="submit"){let c=s.lastButtonClicked||e,h=getRawAttribute(c,"name");addValueToFormData(h,c.value,o)}let l=findAttributeTargets(e,"hx-include");return forEach(l,function(c){processInputValue(n,r,i,asElement(c),a),matches(c,"form")||forEach(asParentNode(c).querySelectorAll(INPUT_SELECTOR),function(h){processInputValue(n,r,i,h,a)})}),overrideFormData(r,o),{errors:i,formData:r,values:formDataProxy(r)}}function appendParam(e,t,n){e!==""&&(e+="&"),String(n)==="[object Object]"&&(n=JSON.stringify(n));let r=encodeURIComponent(n);return e+=encodeURIComponent(t)+"="+r,e}function urlEncode(e){e=formDataFromObject(e);let t="";return e.forEach(function(n,r){t=appendParam(t,r,n)}),t}function getHeaders(e,t,n){let r={"HX-Request":"true","HX-Trigger":getRawAttribute(e,"id"),"HX-Trigger-Name":getRawAttribute(e,"name"),"HX-Target":getAttributeValue(t,"id"),"HX-Current-URL":location.href};return getValuesForElement(e,"hx-headers",!1,r),n!==void 0&&(r["HX-Prompt"]=n),getInternalData(e).boosted&&(r["HX-Boosted"]="true"),r}function filterValues(e,t){let n=getClosestAttributeValue(t,"hx-params");if(n){if(n==="none")return new FormData;if(n==="*")return e;if(n.indexOf("not ")===0)return forEach(n.slice(4).split(","),function(r){r=r.trim(),e.delete(r)}),e;{let r=new FormData;return forEach(n.split(","),function(o){o=o.trim(),e.has(o)&&e.getAll(o).forEach(function(i){r.append(o,i)})}),r}}else return e}function isAnchorLink(e){return!!getRawAttribute(e,"href")&&getRawAttribute(e,"href").indexOf("#")>=0}function getSwapSpecification(e,t){let n=t||getClosestAttributeValue(e,"hx-swap"),r={swapStyle:getInternalData(e).boosted?"innerHTML":htmx.config.defaultSwapStyle,swapDelay:htmx.config.defaultSwapDelay,settleDelay:htmx.config.defaultSettleDelay};if(htmx.config.scrollIntoViewOnBoost&&getInternalData(e).boosted&&!isAnchorLink(e)&&(r.show="top"),n){let s=splitOnWhitespace(n);if(s.length>0)for(let a=0;a<s.length;a++){let l=s[a];if(l.indexOf("swap:")===0)r.swapDelay=parseInterval(l.slice(5));else if(l.indexOf("settle:")===0)r.settleDelay=parseInterval(l.slice(7));else if(l.indexOf("transition:")===0)r.transition=l.slice(11)==="true";else if(l.indexOf("ignoreTitle:")===0)r.ignoreTitle=l.slice(12)==="true";else if(l.indexOf("scroll:")===0){var o=l.slice(7).split(":");let h=o.pop();var i=o.length>0?o.join(":"):null;r.scroll=h,r.scrollTarget=i}else if(l.indexOf("show:")===0){var o=l.slice(5).split(":");let u=o.pop();var i=o.length>0?o.join(":"):null;r.show=u,r.showTarget=i}else if(l.indexOf("focus-scroll:")===0){let c=l.slice(13);r.focusScroll=c=="true"}else a==0?r.swapStyle=l:logError("Unknown modifier in hx-swap: "+l)}}return r}function usesFormData(e){return getClosestAttributeValue(e,"hx-encoding")==="multipart/form-data"||matches(e,"form")&&getRawAttribute(e,"enctype")==="multipart/form-data"}function encodeParamsForBody(e,t,n){let r=null;return withExtensions(t,function(o){r==null&&(r=o.encodeParameters(e,n,t))}),r??(usesFormData(t)?overrideFormData(new FormData,formDataFromObject(n)):urlEncode(n))}function makeSettleInfo(e){return{tasks:[],elts:[e]}}function updateScrollState(e,t){let n=e[0],r=e[e.length-1];if(t.scroll){var o=null;t.scrollTarget&&(o=asElement(querySelectorExt(n,t.scrollTarget))),t.scroll==="top"&&(n||o)&&(o=o||n,o.scrollTop=0),t.scroll==="bottom"&&(r||o)&&(o=o||r,o.scrollTop=o.scrollHeight),typeof t.scroll=="number"&&getWindow().setTimeout(function(){window.scrollTo(0,t.scroll)},0)}if(t.show){var o=null;if(t.showTarget){let s=t.showTarget;t.showTarget==="window"&&(s="body"),o=asElement(querySelectorExt(n,s))}t.show==="top"&&(n||o)&&(o=o||n,o.scrollIntoView({block:"start",behavior:htmx.config.scrollBehavior})),t.show==="bottom"&&(r||o)&&(o=o||r,o.scrollIntoView({block:"end",behavior:htmx.config.scrollBehavior}))}}function getValuesForElement(e,t,n,r,o){if(r==null&&(r={}),e==null)return r;let i=getAttributeValue(e,t);if(i){let s=i.trim(),a=n;if(s==="unset")return null;s.indexOf("javascript:")===0?(s=s.slice(11),a=!0):s.indexOf("js:")===0&&(s=s.slice(3),a=!0),s.indexOf("{")!==0&&(s="{"+s+"}");let l;a?l=maybeEval(e,function(){return o?Function("event","return ("+s+")").call(e,o):Function("return ("+s+")").call(e)},{}):l=parseJSON(s);for(let c in l)l.hasOwnProperty(c)&&r[c]==null&&(r[c]=l[c])}return getValuesForElement(asElement(parentElt(e)),t,n,r,o)}function maybeEval(e,t,n){return htmx.config.allowEval?t():(triggerErrorEvent(e,"htmx:evalDisallowedError"),n)}function getHXVarsForElement(e,t,n){return getValuesForElement(e,"hx-vars",!0,n,t)}function getHXValsForElement(e,t,n){return getValuesForElement(e,"hx-vals",!1,n,t)}function getExpressionVars(e,t){return mergeObjects(getHXVarsForElement(e,t),getHXValsForElement(e,t))}function safelySetHeaderValue(e,t,n){if(n!==null)try{e.setRequestHeader(t,n)}catch{e.setRequestHeader(t,encodeURIComponent(n)),e.setRequestHeader(t+"-URI-AutoEncoded","true")}}function getPathFromResponse(e){if(e.responseURL)try{let t=new URL(e.responseURL);return t.pathname+t.search}catch{triggerErrorEvent(getDocument().body,"htmx:badResponseUrl",{url:e.responseURL})}}function hasHeader(e,t){return t.test(e.getAllResponseHeaders())}function ajaxHelper(e,t,n){if(e=e.toLowerCase(),n){if(n instanceof Element||typeof n=="string")return issueAjaxRequest(e,t,null,null,{targetOverride:resolveTarget(n)||DUMMY_ELT,returnPromise:!0});{let r=resolveTarget(n.target);return(n.target&&!r||n.source&&!r&&!resolveTarget(n.source))&&(r=DUMMY_ELT),issueAjaxRequest(e,t,resolveTarget(n.source),n.event,{handler:n.handler,headers:n.headers,values:n.values,targetOverride:r,swapOverride:n.swap,select:n.select,returnPromise:!0,push:n.push,replace:n.replace,selectOOB:n.selectOOB})}}else return issueAjaxRequest(e,t,null,null,{returnPromise:!0})}function hierarchyForElt(e){let t=[];for(;e;)t.push(e),e=e.parentElement;return t}function verifyPath(e,t,n){let r=new URL(t,location.protocol!=="about:"?location.href:window.origin),i=(location.protocol!=="about:"?location.origin:window.origin)===r.origin;return htmx.config.selfRequestsOnly&&!i?!1:triggerEvent(e,"htmx:validateUrl",mergeObjects({url:r,sameHost:i},n))}function formDataFromObject(e){if(e instanceof FormData)return e;let t=new FormData;for(let n in e)e.hasOwnProperty(n)&&(e[n]&&typeof e[n].forEach=="function"?e[n].forEach(function(r){t.append(n,r)}):typeof e[n]=="object"&&!(e[n]instanceof Blob)?t.append(n,JSON.stringify(e[n])):t.append(n,e[n]));return t}function formDataArrayProxy(e,t,n){return new Proxy(n,{get:function(r,o){return typeof o=="number"?r[o]:o==="length"?r.length:o==="push"?function(i){r.push(i),e.append(t,i)}:typeof r[o]=="function"?function(){r[o].apply(r,arguments),e.delete(t),r.forEach(function(i){e.append(t,i)})}:r[o]&&r[o].length===1?r[o][0]:r[o]},set:function(r,o,i){return r[o]=i,e.delete(t),r.forEach(function(s){e.append(t,s)}),!0}})}function formDataProxy(e){return new Proxy(e,{get:function(t,n){if(typeof n=="symbol"){let o=Reflect.get(t,n);return typeof o=="function"?function(){return o.apply(e,arguments)}:o}if(n==="toJSON")return()=>Object.fromEntries(e);if(n in t&&typeof t[n]=="function")return function(){return e[n].apply(e,arguments)};let r=e.getAll(n);if(r.length!==0)return r.length===1?r[0]:formDataArrayProxy(t,n,r)},set:function(t,n,r){return typeof n!="string"?!1:(t.delete(n),r&&typeof r.forEach=="function"?r.forEach(function(o){t.append(n,o)}):typeof r=="object"&&!(r instanceof Blob)?t.append(n,JSON.stringify(r)):t.append(n,r),!0)},deleteProperty:function(t,n){return typeof n=="string"&&t.delete(n),!0},ownKeys:function(t){return Reflect.ownKeys(Object.fromEntries(t))},getOwnPropertyDescriptor:function(t,n){return Reflect.getOwnPropertyDescriptor(Object.fromEntries(t),n)}})}function issueAjaxRequest(e,t,n,r,o,i){let s=null,a=null;if(o=o??{},o.returnPromise&&typeof Promise<"u")var l=new Promise(function(p,w){s=p,a=w});n==null&&(n=getDocument().body);let c=o.handler||handleAjaxResponse,h=o.select||null;if(!bodyContains(n))return maybeCall(s),l;let u=o.targetOverride||asElement(getTarget(n));if(u==null||u==DUMMY_ELT)return triggerErrorEvent(n,"htmx:targetError",{target:getClosestAttributeValue(n,"hx-target")}),maybeCall(a),l;let f=getInternalData(n),v=f.lastButtonClicked;if(v){let p=getRawAttribute(v,"formaction");p!=null&&(t=p);let w=getRawAttribute(v,"formmethod");if(w!=null)if(VERBS.includes(w.toLowerCase()))e=w;else return maybeCall(s),l}let d=getClosestAttributeValue(n,"hx-confirm");if(i===void 0&&triggerEvent(n,"htmx:confirm",{target:u,elt:n,path:t,verb:e,triggeringEvent:r,etc:o,issueRequest:function(T){return issueAjaxRequest(e,t,n,r,o,!!T)},question:d})===!1)return maybeCall(s),l;let y=n,m=getClosestAttributeValue(n,"hx-sync"),x=null,b=!1;if(m){let p=m.split(":"),w=p[0].trim();if(w==="this"?y=findThisElement(n,"hx-sync"):y=asElement(querySelectorExt(n,w)),m=(p[1]||"drop").trim(),f=getInternalData(y),m==="drop"&&f.xhr&&f.abortable!==!0)return maybeCall(s),l;if(m==="abort"){if(f.xhr)return maybeCall(s),l;b=!0}else m==="replace"?triggerEvent(y,"htmx:abort"):m.indexOf("queue")===0&&(x=(m.split(" ")[1]||"last").trim())}if(f.xhr)if(f.abortable)triggerEvent(y,"htmx:abort");else{if(x==null){if(r){let p=getInternalData(r);p&&p.triggerSpec&&p.triggerSpec.queue&&(x=p.triggerSpec.queue)}x==null&&(x="last")}return f.queuedRequests==null&&(f.queuedRequests=[]),x==="first"&&f.queuedRequests.length===0?f.queuedRequests.push(function(){issueAjaxRequest(e,t,n,r,o)}):x==="all"?f.queuedRequests.push(function(){issueAjaxRequest(e,t,n,r,o)}):x==="last"&&(f.queuedRequests=[],f.queuedRequests.push(function(){issueAjaxRequest(e,t,n,r,o)})),maybeCall(s),l}let E=new XMLHttpRequest;f.xhr=E,f.abortable=b;let g=function(){f.xhr=null,f.abortable=!1,f.queuedRequests!=null&&f.queuedRequests.length>0&&f.queuedRequests.shift()()},X=getClosestAttributeValue(n,"hx-prompt");if(X){var k=prompt(X);if(k===null||!triggerEvent(n,"htmx:prompt",{prompt:k,target:u}))return maybeCall(s),g(),l}if(d&&!i&&!confirm(d))return maybeCall(s),g(),l;let H=getHeaders(n,u,k);e!=="get"&&!usesFormData(n)&&(H["Content-Type"]="application/x-www-form-urlencoded"),o.headers&&(H=mergeObjects(H,o.headers));let $=getInputValues(n,e),R=$.errors,z=$.formData;o.values&&overrideFormData(z,formDataFromObject(o.values));let oe=formDataFromObject(getExpressionVars(n,r)),P=overrideFormData(z,oe),D=filterValues(P,n);htmx.config.getCacheBusterParam&&e==="get"&&D.set("org.htmx.cache-buster",getRawAttribute(u,"id")||"true"),(t==null||t==="")&&(t=location.href);let N=getValuesForElement(n,"hx-request"),J=getInternalData(n).boosted,I=htmx.config.methodsThatUseUrlParams.indexOf(e)>=0,S={boosted:J,useUrlParams:I,formData:D,parameters:formDataProxy(D),unfilteredFormData:P,unfilteredParameters:formDataProxy(P),headers:H,elt:n,target:u,verb:e,errors:R,withCredentials:o.credentials||N.credentials||htmx.config.withCredentials,timeout:o.timeout||N.timeout||htmx.config.timeout,path:t,triggeringEvent:r};if(!triggerEvent(n,"htmx:configRequest",S))return maybeCall(s),g(),l;if(t=S.path,e=S.verb,H=S.headers,D=formDataFromObject(S.parameters),R=S.errors,I=S.useUrlParams,R&&R.length>0)return triggerEvent(n,"htmx:validation:halted",S),maybeCall(s),g(),l;let Y=t.split("#"),ie=Y[0],M=Y[1],A=t;if(I&&(A=ie,!D.keys().next().done&&(A.indexOf("?")<0?A+="?":A+="&",A+=urlEncode(D),M&&(A+="#"+M))),!verifyPath(n,A,S))return triggerErrorEvent(n,"htmx:invalidPath",S),maybeCall(a),g(),l;if(E.open(e.toUpperCase(),A,!0),E.overrideMimeType("text/html"),E.withCredentials=S.withCredentials,E.timeout=S.timeout,!N.noHeaders){for(let p in H)if(H.hasOwnProperty(p)){let w=H[p];safelySetHeaderValue(E,p,w)}}let C={xhr:E,target:u,requestConfig:S,etc:o,boosted:J,select:h,pathInfo:{requestPath:t,finalRequestPath:A,responsePath:null,anchor:M}};if(E.onload=function(){try{let p=hierarchyForElt(n);if(C.pathInfo.responsePath=getPathFromResponse(E),c(n,C),C.keepIndicators!==!0&&removeRequestIndicators(L,O),triggerEvent(n,"htmx:afterRequest",C),triggerEvent(n,"htmx:afterOnLoad",C),!bodyContains(n)){let w=null;for(;p.length>0&&w==null;){let T=p.shift();bodyContains(T)&&(w=T)}w&&(triggerEvent(w,"htmx:afterRequest",C),triggerEvent(w,"htmx:afterOnLoad",C))}maybeCall(s)}catch(p){throw triggerErrorEvent(n,"htmx:onLoadError",mergeObjects({error:p},C)),p}finally{g()}},E.onerror=function(){removeRequestIndicators(L,O),triggerErrorEvent(n,"htmx:afterRequest",C),triggerErrorEvent(n,"htmx:sendError",C),maybeCall(a),g()},E.onabort=function(){removeRequestIndicators(L,O),triggerErrorEvent(n,"htmx:afterRequest",C),triggerErrorEvent(n,"htmx:sendAbort",C),maybeCall(a),g()},E.ontimeout=function(){removeRequestIndicators(L,O),triggerErrorEvent(n,"htmx:afterRequest",C),triggerErrorEvent(n,"htmx:timeout",C),maybeCall(a),g()},!triggerEvent(n,"htmx:beforeRequest",C))return maybeCall(s),g(),l;var L=addRequestIndicatorClasses(n),O=disableElements(n);forEach(["loadstart","loadend","progress","abort"],function(p){forEach([E,E.upload],function(w){w.addEventListener(p,function(T){triggerEvent(n,"htmx:xhr:"+p,{lengthComputable:T.lengthComputable,loaded:T.loaded,total:T.total})})})}),triggerEvent(n,"htmx:beforeSend",C);let se=I?null:encodeParamsForBody(E,n,D);return E.send(se),l}function determineHistoryUpdates(e,t){let n=t.xhr,r=null,o=null;if(hasHeader(n,/HX-Push:/i)?(r=n.getResponseHeader("HX-Push"),o="push"):hasHeader(n,/HX-Push-Url:/i)?(r=n.getResponseHeader("HX-Push-Url"),o="push"):hasHeader(n,/HX-Replace-Url:/i)&&(r=n.getResponseHeader("HX-Replace-Url"),o="replace"),r)return r==="false"?{}:{type:o,path:r};let i=t.pathInfo.finalRequestPath,s=t.pathInfo.responsePath,a=t.etc.push||getClosestAttributeValue(e,"hx-push-url"),l=t.etc.replace||getClosestAttributeValue(e,"hx-replace-url"),c=getInternalData(e).boosted,h=null,u=null;return a?(h="push",u=a):l?(h="replace",u=l):c&&(h="push",u=s||i),u?u==="false"?{}:(u==="true"&&(u=s||i),t.pathInfo.anchor&&u.indexOf("#")===-1&&(u=u+"#"+t.pathInfo.anchor),{type:h,path:u}):{}}function codeMatches(e,t){var n=new RegExp(e.code);return n.test(t.toString(10))}function resolveResponseHandling(e){for(var t=0;t<htmx.config.responseHandling.length;t++){var n=htmx.config.responseHandling[t];if(codeMatches(n,e.status))return n}return{swap:!1}}function handleTitle(e){if(e){let t=find("title");t?t.textContent=e:window.document.title=e}}function resolveRetarget(e,t){if(t==="this")return e;let n=asElement(querySelectorExt(e,t));if(n==null)throw triggerErrorEvent(e,"htmx:targetError",{target:t}),new Error(`Invalid re-target ${t}`);return n}function handleAjaxResponse(e,t){let n=t.xhr,r=t.target,o=t.etc,i=t.select;if(!triggerEvent(e,"htmx:beforeOnLoad",t))return;if(hasHeader(n,/HX-Trigger:/i)&&handleTriggerHeader(n,"HX-Trigger",e),hasHeader(n,/HX-Location:/i)){let b=n.getResponseHeader("HX-Location");var s={};b.indexOf("{")===0&&(s=parseJSON(b),b=s.path,delete s.path),s.push=s.push||"true",ajaxHelper("get",b,s);return}let a=hasHeader(n,/HX-Refresh:/i)&&n.getResponseHeader("HX-Refresh")==="true";if(hasHeader(n,/HX-Redirect:/i)){t.keepIndicators=!0,htmx.location.href=n.getResponseHeader("HX-Redirect"),a&&htmx.location.reload();return}if(a){t.keepIndicators=!0,htmx.location.reload();return}let l=determineHistoryUpdates(e,t),c=resolveResponseHandling(n),h=c.swap,u=!!c.error,f=htmx.config.ignoreTitle||c.ignoreTitle,v=c.select;c.target&&(t.target=resolveRetarget(e,c.target));var d=o.swapOverride;d==null&&c.swapOverride&&(d=c.swapOverride),hasHeader(n,/HX-Retarget:/i)&&(t.target=resolveRetarget(e,n.getResponseHeader("HX-Retarget"))),hasHeader(n,/HX-Reswap:/i)&&(d=n.getResponseHeader("HX-Reswap"));var y=n.response,m=mergeObjects({shouldSwap:h,serverResponse:y,isError:u,ignoreTitle:f,selectOverride:v,swapOverride:d},t);if(!(c.event&&!triggerEvent(r,c.event,m))&&triggerEvent(r,"htmx:beforeSwap",m)){if(r=m.target,y=m.serverResponse,u=m.isError,f=m.ignoreTitle,v=m.selectOverride,d=m.swapOverride,t.target=r,t.failed=u,t.successful=!u,m.shouldSwap){n.status===286&&cancelPolling(e),withExtensions(e,function(g){y=g.transformResponse(y,n,e)}),l.type&&saveCurrentPageToHistory();var x=getSwapSpecification(e,d);x.hasOwnProperty("ignoreTitle")||(x.ignoreTitle=f),r.classList.add(htmx.config.swappingClass),i&&(v=i),hasHeader(n,/HX-Reselect:/i)&&(v=n.getResponseHeader("HX-Reselect"));let b=o.selectOOB||getClosestAttributeValue(e,"hx-select-oob"),E=getClosestAttributeValue(e,"hx-select");swap(r,y,x,{select:v==="unset"?null:v||E,selectOOB:b,eventInfo:t,anchor:t.pathInfo.anchor,contextElement:e,afterSwapCallback:function(){if(hasHeader(n,/HX-Trigger-After-Swap:/i)){let g=e;bodyContains(e)||(g=getDocument().body),handleTriggerHeader(n,"HX-Trigger-After-Swap",g)}},afterSettleCallback:function(){if(hasHeader(n,/HX-Trigger-After-Settle:/i)){let g=e;bodyContains(e)||(g=getDocument().body),handleTriggerHeader(n,"HX-Trigger-After-Settle",g)}},beforeSwapCallback:function(){l.type&&(triggerEvent(getDocument().body,"htmx:beforeHistoryUpdate",mergeObjects({history:l},t)),l.type==="push"?(pushUrlIntoHistory(l.path),triggerEvent(getDocument().body,"htmx:pushedIntoHistory",{path:l.path})):(replaceUrlInHistory(l.path),triggerEvent(getDocument().body,"htmx:replacedInHistory",{path:l.path})))}})}u&&triggerErrorEvent(e,"htmx:responseError",mergeObjects({error:"Response Status Error Code "+n.status+" from "+t.pathInfo.requestPath},t))}}let extensions={};function extensionBase(){return{init:function(e){return null},getSelectors:function(){return null},onEvent:function(e,t){return!0},transformResponse:function(e,t,n){return e},isInlineSwap:function(e){return!1},handleSwap:function(e,t,n,r){return!1},encodeParameters:function(e,t,n){return null}}}function defineExtension(e,t){t.init&&t.init(internalAPI),extensions[e]=mergeObjects(extensionBase(),t)}function removeExtension(e){delete extensions[e]}function getExtensions(e,t,n){if(t==null&&(t=[]),e==null)return t;n==null&&(n=[]);let r=getAttributeValue(e,"hx-ext");return r&&forEach(r.split(","),function(o){if(o=o.replace(/ /g,""),o.slice(0,7)=="ignore:"){n.push(o.slice(7));return}if(n.indexOf(o)<0){let i=extensions[o];i&&t.indexOf(i)<0&&t.push(i)}}),getExtensions(asElement(parentElt(e)),t,n)}var isReady=!1;getDocument().addEventListener("DOMContentLoaded",function(){isReady=!0});function ready(e){isReady||getDocument().readyState==="complete"?e():getDocument().addEventListener("DOMContentLoaded",e)}function insertIndicatorStyles(){if(htmx.config.includeIndicatorStyles!==!1){let e=htmx.config.inlineStyleNonce?` nonce="${htmx.config.inlineStyleNonce}"`:"",t=htmx.config.indicatorClass,n=htmx.config.requestClass;getDocument().head.insertAdjacentHTML("beforeend",`<style${e}>.${t}{opacity:0;visibility: hidden} .${n} .${t}, .${n}.${t}{opacity:1;visibility: visible;transition: opacity 200ms ease-in}</style>`)}}function getMetaConfig(){let e=getDocument().querySelector('meta[name="htmx-config"]');return e?parseJSON(e.content):null}function mergeMetaConfig(){let e=getMetaConfig();e&&(htmx.config=mergeObjects(htmx.config,e))}return ready(function(){mergeMetaConfig(),insertIndicatorStyles();let e=getDocument().body;processNode(e);let t=getDocument().querySelectorAll("[hx-trigger='restored'],[data-hx-trigger='restored']");e.addEventListener("htmx:abort",function(r){let o=r.detail.elt||r.target,i=getInternalData(o);i&&i.xhr&&i.xhr.abort()});let n=window.onpopstate?window.onpopstate.bind(window):null;window.onpopstate=function(r){r.state&&r.state.htmx?(restoreHistory(),forEach(t,function(o){triggerEvent(o,"htmx:restored",{document:getDocument(),triggerEvent})})):n&&n(r)},getWindow().setTimeout(function(){triggerEvent(e,"htmx:load",{}),e=null},0)}),htmx})(),F=ae;(function(){let e;F.defineExtension("json-enc",{init:function(t){e=t},onEvent:function(t,n){t==="htmx:configRequest"&&(n.detail.headers["Content-Type"]="application/json")},encodeParameters:function(t,n,r){t.overrideMimeType("text/json");let o={};n.forEach(function(s,a){Object.hasOwn(o,a)?(Array.isArray(o[a])||(o[a]=[o[a]]),o[a].push(s)):o[a]=s});let i=e.getExpressionVars(r);return Object.keys(o).forEach(function(s){o[s]=Object.hasOwn(i,s)?i[s]:o[s]}),JSON.stringify(o)}})})();var G=document.createElement("template");G.innerHTML=` 1 + var ae=(function(){"use strict";let htmx={onLoad:null,process:null,on:null,off:null,trigger:null,ajax:null,find:null,findAll:null,closest:null,values:function(e,t){return getInputValues(e,t||"post").values},remove:null,addClass:null,removeClass:null,toggleClass:null,takeClass:null,swap:null,defineExtension:null,removeExtension:null,logAll:null,logNone:null,logger:null,config:{historyEnabled:!0,historyCacheSize:10,refreshOnHistoryMiss:!1,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:!0,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:!0,allowScriptTags:!0,inlineScriptNonce:"",inlineStyleNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:!1,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",scrollBehavior:"instant",defaultFocusScroll:!1,getCacheBusterParam:!1,globalViewTransitions:!1,methodsThatUseUrlParams:["get","delete"],selfRequestsOnly:!0,ignoreTitle:!1,scrollIntoViewOnBoost:!0,triggerSpecsCache:null,disableInheritance:!1,responseHandling:[{code:"204",swap:!1},{code:"[23]..",swap:!0},{code:"[45]..",swap:!1,error:!0}],allowNestedOobSwaps:!0,historyRestoreAsHxRequest:!0,reportValidityOfForms:!1},parseInterval:null,location,_:null,version:"2.0.8"};htmx.onLoad=onLoadHelper,htmx.process=processNode,htmx.on=addEventListenerImpl,htmx.off=removeEventListenerImpl,htmx.trigger=triggerEvent,htmx.ajax=ajaxHelper,htmx.find=find,htmx.findAll=findAll,htmx.closest=closest,htmx.remove=removeElement,htmx.addClass=addClassToElement,htmx.removeClass=removeClassFromElement,htmx.toggleClass=toggleClassOnElement,htmx.takeClass=takeClassForElement,htmx.swap=swap,htmx.defineExtension=defineExtension,htmx.removeExtension=removeExtension,htmx.logAll=logAll,htmx.logNone=logNone,htmx.parseInterval=parseInterval,htmx._=internalEval;let internalAPI={addTriggerHandler,bodyContains,canAccessLocalStorage,findThisElement,filterValues,swap,hasAttribute,getAttributeValue,getClosestAttributeValue,getClosestMatch,getExpressionVars,getHeaders,getInputValues,getInternalData,getSwapSpecification,getTriggerSpecs,getTarget,makeFragment,mergeObjects,makeSettleInfo,oobSwap,querySelectorExt,settleImmediately,shouldCancel,triggerEvent,triggerErrorEvent,withExtensions},VERBS=["get","post","put","delete","patch"],VERB_SELECTOR=VERBS.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");function parseInterval(e){if(e==null)return;let t=NaN;return e.slice(-2)=="ms"?t=parseFloat(e.slice(0,-2)):e.slice(-1)=="s"?t=parseFloat(e.slice(0,-1))*1e3:e.slice(-1)=="m"?t=parseFloat(e.slice(0,-1))*1e3*60:t=parseFloat(e),isNaN(t)?void 0:t}function getRawAttribute(e,t){return e instanceof Element&&e.getAttribute(t)}function hasAttribute(e,t){return!!e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function getAttributeValue(e,t){return getRawAttribute(e,t)||getRawAttribute(e,"data-"+t)}function parentElt(e){let t=e.parentElement;return!t&&e.parentNode instanceof ShadowRoot?e.parentNode:t}function getDocument(){return document}function getRootNode(e,t){return e.getRootNode?e.getRootNode({composed:t}):getDocument()}function getClosestMatch(e,t){for(;e&&!t(e);)e=parentElt(e);return e||null}function getAttributeValueWithDisinheritance(e,t,n){let r=getAttributeValue(t,n),o=getAttributeValue(t,"hx-disinherit");var i=getAttributeValue(t,"hx-inherit");if(e!==t){if(htmx.config.disableInheritance)return i&&(i==="*"||i.split(" ").indexOf(n)>=0)?r:null;if(o&&(o==="*"||o.split(" ").indexOf(n)>=0))return"unset"}return r}function getClosestAttributeValue(e,t){let n=null;if(getClosestMatch(e,function(r){return!!(n=getAttributeValueWithDisinheritance(e,asElement(r),t))}),n!=="unset")return n}function matches(e,t){return e instanceof Element&&e.matches(t)}function getStartTag(e){let n=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i.exec(e);return n?n[1].toLowerCase():""}function parseHTML(e){return"parseHTMLUnsafe"in Document?Document.parseHTMLUnsafe(e):new DOMParser().parseFromString(e,"text/html")}function takeChildrenFor(e,t){for(;t.childNodes.length>0;)e.append(t.childNodes[0])}function duplicateScript(e){let t=getDocument().createElement("script");return forEach(e.attributes,function(n){t.setAttribute(n.name,n.value)}),t.textContent=e.textContent,t.async=!1,htmx.config.inlineScriptNonce&&(t.nonce=htmx.config.inlineScriptNonce),t}function isJavaScriptScriptNode(e){return e.matches("script")&&(e.type==="text/javascript"||e.type==="module"||e.type==="")}function normalizeScriptTags(e){Array.from(e.querySelectorAll("script")).forEach(t=>{if(isJavaScriptScriptNode(t)){let n=duplicateScript(t),r=t.parentNode;try{r.insertBefore(n,t)}catch(o){logError(o)}finally{t.remove()}}})}function makeFragment(e){let t=e.replace(/<head(\s[^>]*)?>[\s\S]*?<\/head>/i,""),n=getStartTag(t),r;if(n==="html"){r=new DocumentFragment;let i=parseHTML(e);takeChildrenFor(r,i.body),r.title=i.title}else if(n==="body"){r=new DocumentFragment;let i=parseHTML(t);takeChildrenFor(r,i.body),r.title=i.title}else{let i=parseHTML('<body><template class="internal-htmx-wrapper">'+t+"</template></body>");r=i.querySelector("template").content,r.title=i.title;var o=r.querySelector("title");o&&o.parentNode===r&&(o.remove(),r.title=o.innerText)}return r&&(htmx.config.allowScriptTags?normalizeScriptTags(r):r.querySelectorAll("script").forEach(i=>i.remove())),r}function maybeCall(e){e&&e()}function isType(e,t){return Object.prototype.toString.call(e)==="[object "+t+"]"}function isFunction(e){return typeof e=="function"}function isRawObject(e){return isType(e,"Object")}function getInternalData(e){let t="htmx-internal-data",n=e[t];return n||(n=e[t]={}),n}function toArray(e){let t=[];if(e)for(let n=0;n<e.length;n++)t.push(e[n]);return t}function forEach(e,t){if(e)for(let n=0;n<e.length;n++)t(e[n])}function isScrolledIntoView(e){let t=e.getBoundingClientRect(),n=t.top,r=t.bottom;return n<window.innerHeight&&r>=0}function bodyContains(e){return e.getRootNode({composed:!0})===document}function splitOnWhitespace(e){return e.trim().split(/\s+/)}function mergeObjects(e,t){for(let n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e}function parseJSON(e){try{return JSON.parse(e)}catch(t){return logError(t),null}}function canAccessLocalStorage(){let e="htmx:sessionStorageTest";try{return sessionStorage.setItem(e,e),sessionStorage.removeItem(e),!0}catch{return!1}}function normalizePath(e){let t=new URL(e,"http://x");return t&&(e=t.pathname+t.search),e!="/"&&(e=e.replace(/\/+$/,"")),e}function internalEval(str){return maybeEval(getDocument().body,function(){return eval(str)})}function onLoadHelper(e){return htmx.on("htmx:load",function(n){e(n.detail.elt)})}function logAll(){htmx.logger=function(e,t,n){console&&console.log(t,e,n)}}function logNone(){htmx.logger=null}function find(e,t){return typeof e!="string"?e.querySelector(t):find(getDocument(),e)}function findAll(e,t){return typeof e!="string"?e.querySelectorAll(t):findAll(getDocument(),e)}function getWindow(){return window}function removeElement(e,t){e=resolveTarget(e),t?getWindow().setTimeout(function(){removeElement(e),e=null},t):parentElt(e).removeChild(e)}function asElement(e){return e instanceof Element?e:null}function asHtmlElement(e){return e instanceof HTMLElement?e:null}function asString(e){return typeof e=="string"?e:null}function asParentNode(e){return e instanceof Element||e instanceof Document||e instanceof DocumentFragment?e:null}function addClassToElement(e,t,n){e=asElement(resolveTarget(e)),e&&(n?getWindow().setTimeout(function(){addClassToElement(e,t),e=null},n):e.classList&&e.classList.add(t))}function removeClassFromElement(e,t,n){let r=asElement(resolveTarget(e));r&&(n?getWindow().setTimeout(function(){removeClassFromElement(r,t),r=null},n):r.classList&&(r.classList.remove(t),r.classList.length===0&&r.removeAttribute("class")))}function toggleClassOnElement(e,t){e=resolveTarget(e),e.classList.toggle(t)}function takeClassForElement(e,t){e=resolveTarget(e),forEach(e.parentElement.children,function(n){removeClassFromElement(n,t)}),addClassToElement(asElement(e),t)}function closest(e,t){return e=asElement(resolveTarget(e)),e?e.closest(t):null}function startsWith(e,t){return e.substring(0,t.length)===t}function endsWith(e,t){return e.substring(e.length-t.length)===t}function normalizeSelector(e){let t=e.trim();return startsWith(t,"<")&&endsWith(t,"/>")?t.substring(1,t.length-2):t}function querySelectorAllExt(e,t,n){if(t.indexOf("global ")===0)return querySelectorAllExt(e,t.slice(7),!0);e=resolveTarget(e);let r=[];{let s=0,a=0;for(let l=0;l<t.length;l++){let c=t[l];if(c===","&&s===0){r.push(t.substring(a,l)),a=l+1;continue}c==="<"?s++:c==="/"&&l<t.length-1&&t[l+1]===">"&&s--}a<t.length&&r.push(t.substring(a))}let o=[],i=[];for(;r.length>0;){let s=normalizeSelector(r.shift()),a;s.indexOf("closest ")===0?a=closest(asElement(e),normalizeSelector(s.slice(8))):s.indexOf("find ")===0?a=find(asParentNode(e),normalizeSelector(s.slice(5))):s==="next"||s==="nextElementSibling"?a=asElement(e).nextElementSibling:s.indexOf("next ")===0?a=scanForwardQuery(e,normalizeSelector(s.slice(5)),!!n):s==="previous"||s==="previousElementSibling"?a=asElement(e).previousElementSibling:s.indexOf("previous ")===0?a=scanBackwardsQuery(e,normalizeSelector(s.slice(9)),!!n):s==="document"?a=document:s==="window"?a=window:s==="body"?a=document.body:s==="root"?a=getRootNode(e,!!n):s==="host"?a=e.getRootNode().host:i.push(s),a&&o.push(a)}if(i.length>0){let s=i.join(","),a=asParentNode(getRootNode(e,!!n));o.push(...toArray(a.querySelectorAll(s)))}return o}var scanForwardQuery=function(e,t,n){let r=asParentNode(getRootNode(e,n)).querySelectorAll(t);for(let o=0;o<r.length;o++){let i=r[o];if(i.compareDocumentPosition(e)===Node.DOCUMENT_POSITION_PRECEDING)return i}},scanBackwardsQuery=function(e,t,n){let r=asParentNode(getRootNode(e,n)).querySelectorAll(t);for(let o=r.length-1;o>=0;o--){let i=r[o];if(i.compareDocumentPosition(e)===Node.DOCUMENT_POSITION_FOLLOWING)return i}};function querySelectorExt(e,t){return typeof e!="string"?querySelectorAllExt(e,t)[0]:querySelectorAllExt(getDocument().body,e)[0]}function resolveTarget(e,t){return typeof e=="string"?find(asParentNode(t)||document,e):e}function processEventArgs(e,t,n,r){return isFunction(t)?{target:getDocument().body,event:asString(e),listener:t,options:n}:{target:resolveTarget(e),event:asString(t),listener:n,options:r}}function addEventListenerImpl(e,t,n,r){return ready(function(){let i=processEventArgs(e,t,n,r);i.target.addEventListener(i.event,i.listener,i.options)}),isFunction(t)?t:n}function removeEventListenerImpl(e,t,n){return ready(function(){let r=processEventArgs(e,t,n);r.target.removeEventListener(r.event,r.listener)}),isFunction(t)?t:n}let DUMMY_ELT=getDocument().createElement("output");function findAttributeTargets(e,t){let n=getClosestAttributeValue(e,t);if(n){if(n==="this")return[findThisElement(e,t)];{let r=querySelectorAllExt(e,n);if(/(^|,)(\s*)inherit(\s*)($|,)/.test(n)){let i=asElement(getClosestMatch(e,function(s){return s!==e&&hasAttribute(asElement(s),t)}));i&&r.push(...findAttributeTargets(i,t))}return r.length===0?(logError('The selector "'+n+'" on '+t+" returned no matches!"),[DUMMY_ELT]):r}}}function findThisElement(e,t){return asElement(getClosestMatch(e,function(n){return getAttributeValue(asElement(n),t)!=null}))}function getTarget(e){let t=getClosestAttributeValue(e,"hx-target");return t?t==="this"?findThisElement(e,"hx-target"):querySelectorExt(e,t):getInternalData(e).boosted?getDocument().body:e}function shouldSettleAttribute(e){return htmx.config.attributesToSettle.includes(e)}function cloneAttributes(e,t){forEach(Array.from(e.attributes),function(n){!t.hasAttribute(n.name)&&shouldSettleAttribute(n.name)&&e.removeAttribute(n.name)}),forEach(t.attributes,function(n){shouldSettleAttribute(n.name)&&e.setAttribute(n.name,n.value)})}function isInlineSwap(e,t){let n=getExtensions(t);for(let r=0;r<n.length;r++){let o=n[r];try{if(o.isInlineSwap(e))return!0}catch(i){logError(i)}}return e==="outerHTML"}function oobSwap(e,t,n,r){r=r||getDocument();let o="#"+CSS.escape(getRawAttribute(t,"id")),i="outerHTML";e==="true"||(e.indexOf(":")>0?(i=e.substring(0,e.indexOf(":")),o=e.substring(e.indexOf(":")+1)):i=e),t.removeAttribute("hx-swap-oob"),t.removeAttribute("data-hx-swap-oob");let s=querySelectorAllExt(r,o,!1);return s.length?(forEach(s,function(a){let l,c=t.cloneNode(!0);l=getDocument().createDocumentFragment(),l.appendChild(c),isInlineSwap(i,a)||(l=asParentNode(c));let h={shouldSwap:!0,target:a,fragment:l};triggerEvent(a,"htmx:oobBeforeSwap",h)&&(a=h.target,h.shouldSwap&&(handlePreservedElements(l),swapWithStyle(i,a,a,l,n),restorePreservedElements()),forEach(n.elts,function(u){triggerEvent(u,"htmx:oobAfterSwap",h)}))}),t.parentNode.removeChild(t)):(t.parentNode.removeChild(t),triggerErrorEvent(getDocument().body,"htmx:oobErrorNoTarget",{content:t})),e}function restorePreservedElements(){let e=find("#--htmx-preserve-pantry--");if(e){for(let t of[...e.children]){let n=find("#"+t.id);n.parentNode.moveBefore(t,n),n.remove()}e.remove()}}function handlePreservedElements(e){forEach(findAll(e,"[hx-preserve], [data-hx-preserve]"),function(t){let n=getAttributeValue(t,"id"),r=getDocument().getElementById(n);if(r!=null)if(t.moveBefore){let o=find("#--htmx-preserve-pantry--");o==null&&(getDocument().body.insertAdjacentHTML("afterend","<div id='--htmx-preserve-pantry--'></div>"),o=find("#--htmx-preserve-pantry--")),o.moveBefore(r,null)}else t.parentNode.replaceChild(r,t)})}function handleAttributes(e,t,n){forEach(t.querySelectorAll("[id]"),function(r){let o=getRawAttribute(r,"id");if(o&&o.length>0){let i=o.replace("'","\\'"),s=r.tagName.replace(":","\\:"),a=asParentNode(e),l=a&&a.querySelector(s+"[id='"+i+"']");if(l&&l!==a){let c=r.cloneNode();cloneAttributes(r,l),n.tasks.push(function(){cloneAttributes(r,c)})}}})}function makeAjaxLoadTask(e){return function(){removeClassFromElement(e,htmx.config.addedClass),processNode(asElement(e)),processFocus(asParentNode(e)),triggerEvent(e,"htmx:load")}}function processFocus(e){let t="[autofocus]",n=asHtmlElement(matches(e,t)?e:e.querySelector(t));n?.focus()}function insertNodesBefore(e,t,n,r){for(handleAttributes(e,n,r);n.childNodes.length>0;){let o=n.firstChild;addClassToElement(asElement(o),htmx.config.addedClass),e.insertBefore(o,t),o.nodeType!==Node.TEXT_NODE&&o.nodeType!==Node.COMMENT_NODE&&r.tasks.push(makeAjaxLoadTask(o))}}function stringHash(e,t){let n=0;for(;n<e.length;)t=(t<<5)-t+e.charCodeAt(n++)|0;return t}function attributeHash(e){let t=0;for(let n=0;n<e.attributes.length;n++){let r=e.attributes[n];r.value&&(t=stringHash(r.name,t),t=stringHash(r.value,t))}return t}function deInitOnHandlers(e){let t=getInternalData(e);if(t.onHandlers){for(let n=0;n<t.onHandlers.length;n++){let r=t.onHandlers[n];removeEventListenerImpl(e,r.event,r.listener)}delete t.onHandlers}}function deInitNode(e){let t=getInternalData(e);t.timeout&&clearTimeout(t.timeout),t.listenerInfos&&forEach(t.listenerInfos,function(n){n.on&&removeEventListenerImpl(n.on,n.trigger,n.listener)}),deInitOnHandlers(e),forEach(Object.keys(t),function(n){n!=="firstInitCompleted"&&delete t[n]})}function cleanUpElement(e){triggerEvent(e,"htmx:beforeCleanupElement"),deInitNode(e),forEach(e.children,function(t){cleanUpElement(t)})}function swapOuterHTML(e,t,n){if(e.tagName==="BODY")return swapInnerHTML(e,t,n);let r,o=e.previousSibling,i=parentElt(e);if(i){for(insertNodesBefore(i,e,t,n),o==null?r=i.firstChild:r=o.nextSibling,n.elts=n.elts.filter(function(s){return s!==e});r&&r!==e;)r instanceof Element&&n.elts.push(r),r=r.nextSibling;cleanUpElement(e),e.remove()}}function swapAfterBegin(e,t,n){return insertNodesBefore(e,e.firstChild,t,n)}function swapBeforeBegin(e,t,n){return insertNodesBefore(parentElt(e),e,t,n)}function swapBeforeEnd(e,t,n){return insertNodesBefore(e,null,t,n)}function swapAfterEnd(e,t,n){return insertNodesBefore(parentElt(e),e.nextSibling,t,n)}function swapDelete(e){cleanUpElement(e);let t=parentElt(e);if(t)return t.removeChild(e)}function swapInnerHTML(e,t,n){let r=e.firstChild;if(insertNodesBefore(e,r,t,n),r){for(;r.nextSibling;)cleanUpElement(r.nextSibling),e.removeChild(r.nextSibling);cleanUpElement(r),e.removeChild(r)}}function swapWithStyle(e,t,n,r,o){switch(e){case"none":return;case"outerHTML":swapOuterHTML(n,r,o);return;case"afterbegin":swapAfterBegin(n,r,o);return;case"beforebegin":swapBeforeBegin(n,r,o);return;case"beforeend":swapBeforeEnd(n,r,o);return;case"afterend":swapAfterEnd(n,r,o);return;case"delete":swapDelete(n);return;default:var i=getExtensions(t);for(let s=0;s<i.length;s++){let a=i[s];try{let l=a.handleSwap(e,n,r,o);if(l){if(Array.isArray(l))for(let c=0;c<l.length;c++){let h=l[c];h.nodeType!==Node.TEXT_NODE&&h.nodeType!==Node.COMMENT_NODE&&o.tasks.push(makeAjaxLoadTask(h))}return}}catch(l){logError(l)}}e==="innerHTML"?swapInnerHTML(n,r,o):swapWithStyle(htmx.config.defaultSwapStyle,t,n,r,o)}}function findAndSwapOobElements(e,t,n){var r=findAll(e,"[hx-swap-oob], [data-hx-swap-oob]");return forEach(r,function(o){if(htmx.config.allowNestedOobSwaps||o.parentElement===null){let i=getAttributeValue(o,"hx-swap-oob");i!=null&&oobSwap(i,o,t,n)}else o.removeAttribute("hx-swap-oob"),o.removeAttribute("data-hx-swap-oob")}),r.length>0}function swap(e,t,n,r){r||(r={});let o=null,i=null,s=function(){maybeCall(r.beforeSwapCallback),e=resolveTarget(e);let c=r.contextElement?getRootNode(r.contextElement,!1):getDocument(),h=document.activeElement,u={};u={elt:h,start:h?h.selectionStart:null,end:h?h.selectionEnd:null};let f=makeSettleInfo(e);if(n.swapStyle==="textContent")e.textContent=t;else{let d=makeFragment(t);if(f.title=r.title||d.title,r.historyRequest&&(d=d.querySelector("[hx-history-elt],[data-hx-history-elt]")||d),r.selectOOB){let y=r.selectOOB.split(",");for(let m=0;m<y.length;m++){let x=y[m].split(":",2),b=x[0].trim();b.indexOf("#")===0&&(b=b.substring(1));let E=x[1]||"true",g=d.querySelector("#"+b);g&&oobSwap(E,g,f,c)}}if(findAndSwapOobElements(d,f,c),forEach(findAll(d,"template"),function(y){y.content&&findAndSwapOobElements(y.content,f,c)&&y.remove()}),r.select){let y=getDocument().createDocumentFragment();forEach(d.querySelectorAll(r.select),function(m){y.appendChild(m)}),d=y}handlePreservedElements(d),swapWithStyle(n.swapStyle,r.contextElement,e,d,f),restorePreservedElements()}if(u.elt&&!bodyContains(u.elt)&&getRawAttribute(u.elt,"id")){let d=document.getElementById(getRawAttribute(u.elt,"id")),y={preventScroll:n.focusScroll!==void 0?!n.focusScroll:!htmx.config.defaultFocusScroll};if(d){if(u.start&&d.setSelectionRange)try{d.setSelectionRange(u.start,u.end)}catch{}d.focus(y)}}e.classList.remove(htmx.config.swappingClass),forEach(f.elts,function(d){d.classList&&d.classList.add(htmx.config.settlingClass),triggerEvent(d,"htmx:afterSwap",r.eventInfo)}),maybeCall(r.afterSwapCallback),n.ignoreTitle||handleTitle(f.title);let v=function(){if(forEach(f.tasks,function(d){d.call()}),forEach(f.elts,function(d){d.classList&&d.classList.remove(htmx.config.settlingClass),triggerEvent(d,"htmx:afterSettle",r.eventInfo)}),r.anchor){let d=asElement(resolveTarget("#"+r.anchor));d&&d.scrollIntoView({block:"start",behavior:"auto"})}updateScrollState(f.elts,n),maybeCall(r.afterSettleCallback),maybeCall(o)};n.settleDelay>0?getWindow().setTimeout(v,n.settleDelay):v()},a=htmx.config.globalViewTransitions;n.hasOwnProperty("transition")&&(a=n.transition);let l=r.contextElement||getDocument();if(a&&triggerEvent(l,"htmx:beforeTransition",r.eventInfo)&&typeof Promise<"u"&&document.startViewTransition){let c=new Promise(function(u,f){o=u,i=f}),h=s;s=function(){document.startViewTransition(function(){return h(),c})}}try{n?.swapDelay&&n.swapDelay>0?getWindow().setTimeout(s,n.swapDelay):s()}catch(c){throw triggerErrorEvent(l,"htmx:swapError",r.eventInfo),maybeCall(i),c}}function handleTriggerHeader(e,t,n){let r=e.getResponseHeader(t);if(r.indexOf("{")===0){let o=parseJSON(r);for(let i in o)if(o.hasOwnProperty(i)){let s=o[i];isRawObject(s)?n=s.target!==void 0?s.target:n:s={value:s},triggerEvent(n,i,s)}}else{let o=r.split(",");for(let i=0;i<o.length;i++)triggerEvent(n,o[i].trim(),[])}}let WHITESPACE=/\s/,WHITESPACE_OR_COMMA=/[\s,]/,SYMBOL_START=/[_$a-zA-Z]/,SYMBOL_CONT=/[_$a-zA-Z0-9]/,STRINGISH_START=['"',"'","/"],NOT_WHITESPACE=/[^\s]/,COMBINED_SELECTOR_START=/[{(]/,COMBINED_SELECTOR_END=/[})]/;function tokenizeString(e){let t=[],n=0;for(;n<e.length;){if(SYMBOL_START.exec(e.charAt(n))){for(var r=n;SYMBOL_CONT.exec(e.charAt(n+1));)n++;t.push(e.substring(r,n+1))}else if(STRINGISH_START.indexOf(e.charAt(n))!==-1){let o=e.charAt(n);var r=n;for(n++;n<e.length&&e.charAt(n)!==o;)e.charAt(n)==="\\"&&n++,n++;t.push(e.substring(r,n+1))}else{let o=e.charAt(n);t.push(o)}n++}return t}function isPossibleRelativeReference(e,t,n){return SYMBOL_START.exec(e.charAt(0))&&e!=="true"&&e!=="false"&&e!=="this"&&e!==n&&t!=="."}function maybeGenerateConditional(e,t,n){if(t[0]==="["){t.shift();let r=1,o=" return (function("+n+"){ return (",i=null;for(;t.length>0;){let s=t[0];if(s==="]"){if(r--,r===0){i===null&&(o=o+"true"),t.shift(),o+=")})";try{let a=maybeEval(e,function(){return Function(o)()},function(){return!0});return a.source=o,a}catch(a){return triggerErrorEvent(getDocument().body,"htmx:syntax:error",{error:a,source:o}),null}}}else s==="["&&r++;isPossibleRelativeReference(s,i,n)?o+="(("+n+"."+s+") ? ("+n+"."+s+") : (window."+s+"))":o=o+s,i=t.shift()}}}function consumeUntil(e,t){let n="";for(;e.length>0&&!t.test(e[0]);)n+=e.shift();return n}function consumeCSSSelector(e){let t;return e.length>0&&COMBINED_SELECTOR_START.test(e[0])?(e.shift(),t=consumeUntil(e,COMBINED_SELECTOR_END).trim(),e.shift()):t=consumeUntil(e,WHITESPACE_OR_COMMA),t}let INPUT_SELECTOR="input, textarea, select";function parseAndCacheTrigger(e,t,n){let r=[],o=tokenizeString(t);do{consumeUntil(o,NOT_WHITESPACE);let a=o.length,l=consumeUntil(o,/[,\[\s]/);if(l!=="")if(l==="every"){let c={trigger:"every"};consumeUntil(o,NOT_WHITESPACE),c.pollInterval=parseInterval(consumeUntil(o,/[,\[\s]/)),consumeUntil(o,NOT_WHITESPACE);var i=maybeGenerateConditional(e,o,"event");i&&(c.eventFilter=i),r.push(c)}else{let c={trigger:l};var i=maybeGenerateConditional(e,o,"event");for(i&&(c.eventFilter=i),consumeUntil(o,NOT_WHITESPACE);o.length>0&&o[0]!==",";){let u=o.shift();if(u==="changed")c.changed=!0;else if(u==="once")c.once=!0;else if(u==="consume")c.consume=!0;else if(u==="delay"&&o[0]===":")o.shift(),c.delay=parseInterval(consumeUntil(o,WHITESPACE_OR_COMMA));else if(u==="from"&&o[0]===":"){if(o.shift(),COMBINED_SELECTOR_START.test(o[0]))var s=consumeCSSSelector(o);else{var s=consumeUntil(o,WHITESPACE_OR_COMMA);if(s==="closest"||s==="find"||s==="next"||s==="previous"){o.shift();let v=consumeCSSSelector(o);v.length>0&&(s+=" "+v)}}c.from=s}else u==="target"&&o[0]===":"?(o.shift(),c.target=consumeCSSSelector(o)):u==="throttle"&&o[0]===":"?(o.shift(),c.throttle=parseInterval(consumeUntil(o,WHITESPACE_OR_COMMA))):u==="queue"&&o[0]===":"?(o.shift(),c.queue=consumeUntil(o,WHITESPACE_OR_COMMA)):u==="root"&&o[0]===":"?(o.shift(),c[u]=consumeCSSSelector(o)):u==="threshold"&&o[0]===":"?(o.shift(),c[u]=consumeUntil(o,WHITESPACE_OR_COMMA)):triggerErrorEvent(e,"htmx:syntax:error",{token:o.shift()});consumeUntil(o,NOT_WHITESPACE)}r.push(c)}o.length===a&&triggerErrorEvent(e,"htmx:syntax:error",{token:o.shift()}),consumeUntil(o,NOT_WHITESPACE)}while(o[0]===","&&o.shift());return n&&(n[t]=r),r}function getTriggerSpecs(e){let t=getAttributeValue(e,"hx-trigger"),n=[];if(t){let r=htmx.config.triggerSpecsCache;n=r&&r[t]||parseAndCacheTrigger(e,t,r)}return n.length>0?n:matches(e,"form")?[{trigger:"submit"}]:matches(e,'input[type="button"], input[type="submit"]')?[{trigger:"click"}]:matches(e,INPUT_SELECTOR)?[{trigger:"change"}]:[{trigger:"click"}]}function cancelPolling(e){getInternalData(e).cancelled=!0}function processPolling(e,t,n){let r=getInternalData(e);r.timeout=getWindow().setTimeout(function(){bodyContains(e)&&r.cancelled!==!0&&(maybeFilterEvent(n,e,makeEvent("hx:poll:trigger",{triggerSpec:n,target:e}))||t(e),processPolling(e,t,n))},n.pollInterval)}function isLocalLink(e){return location.hostname===e.hostname&&getRawAttribute(e,"href")&&getRawAttribute(e,"href").indexOf("#")!==0}function eltIsDisabled(e){return closest(e,htmx.config.disableSelector)}function boostElement(e,t,n){if(e instanceof HTMLAnchorElement&&isLocalLink(e)&&(e.target===""||e.target==="_self")||e.tagName==="FORM"&&String(getRawAttribute(e,"method")).toLowerCase()!=="dialog"){t.boosted=!0;let r,o;if(e.tagName==="A")r="get",o=getRawAttribute(e,"href");else{let i=getRawAttribute(e,"method");r=i?i.toLowerCase():"get",o=getRawAttribute(e,"action"),(o==null||o==="")&&(o=location.href),r==="get"&&o.includes("?")&&(o=o.replace(/\?[^#]+/,""))}n.forEach(function(i){addEventListener(e,function(s,a){let l=asElement(s);if(eltIsDisabled(l)){cleanUpElement(l);return}issueAjaxRequest(r,o,l,a)},t,i,!0)})}}function shouldCancel(e,t){if(e.type==="submit"&&t.tagName==="FORM")return!0;if(e.type==="click"){let n=t.closest('input[type="submit"], button');if(n&&n.form&&n.type==="submit")return!0;let r=t.closest("a"),o=/^#.+/;if(r&&r.href&&!o.test(r.getAttribute("href")))return!0}return!1}function ignoreBoostedAnchorCtrlClick(e,t){return getInternalData(e).boosted&&e instanceof HTMLAnchorElement&&t.type==="click"&&(t.ctrlKey||t.metaKey)}function maybeFilterEvent(e,t,n){let r=e.eventFilter;if(r)try{return r.call(t,n)!==!0}catch(o){let i=r.source;return triggerErrorEvent(getDocument().body,"htmx:eventFilter:error",{error:o,source:i}),!0}return!1}function addEventListener(e,t,n,r,o){let i=getInternalData(e),s;r.from?s=querySelectorAllExt(e,r.from):s=[e],r.changed&&("lastValue"in i||(i.lastValue=new WeakMap),s.forEach(function(a){i.lastValue.has(r)||i.lastValue.set(r,new WeakMap),i.lastValue.get(r).set(a,a.value)})),forEach(s,function(a){let l=function(c){if(!bodyContains(e)){a.removeEventListener(r.trigger,l);return}if(ignoreBoostedAnchorCtrlClick(e,c)||((o||shouldCancel(c,a))&&c.preventDefault(),maybeFilterEvent(r,e,c)))return;let h=getInternalData(c);if(h.triggerSpec=r,h.handledFor==null&&(h.handledFor=[]),h.handledFor.indexOf(e)<0){if(h.handledFor.push(e),r.consume&&c.stopPropagation(),r.target&&c.target&&!matches(asElement(c.target),r.target))return;if(r.once){if(i.triggeredOnce)return;i.triggeredOnce=!0}if(r.changed){let u=c.target,f=u.value,v=i.lastValue.get(r);if(v.has(u)&&v.get(u)===f)return;v.set(u,f)}if(i.delayed&&clearTimeout(i.delayed),i.throttle)return;r.throttle>0?i.throttle||(triggerEvent(e,"htmx:trigger"),t(e,c),i.throttle=getWindow().setTimeout(function(){i.throttle=null},r.throttle)):r.delay>0?i.delayed=getWindow().setTimeout(function(){triggerEvent(e,"htmx:trigger"),t(e,c)},r.delay):(triggerEvent(e,"htmx:trigger"),t(e,c))}};n.listenerInfos==null&&(n.listenerInfos=[]),n.listenerInfos.push({trigger:r.trigger,listener:l,on:a}),a.addEventListener(r.trigger,l)})}let windowIsScrolling=!1,scrollHandler=null;function initScrollHandler(){scrollHandler||(scrollHandler=function(){windowIsScrolling=!0},window.addEventListener("scroll",scrollHandler),window.addEventListener("resize",scrollHandler),setInterval(function(){windowIsScrolling&&(windowIsScrolling=!1,forEach(getDocument().querySelectorAll("[hx-trigger*='revealed'],[data-hx-trigger*='revealed']"),function(e){maybeReveal(e)}))},200))}function maybeReveal(e){!hasAttribute(e,"data-hx-revealed")&&isScrolledIntoView(e)&&(e.setAttribute("data-hx-revealed","true"),getInternalData(e).initHash?triggerEvent(e,"revealed"):e.addEventListener("htmx:afterProcessNode",function(){triggerEvent(e,"revealed")},{once:!0}))}function loadImmediately(e,t,n,r){let o=function(){n.loaded||(n.loaded=!0,triggerEvent(e,"htmx:trigger"),t(e))};r>0?getWindow().setTimeout(o,r):o()}function processVerbs(e,t,n){let r=!1;return forEach(VERBS,function(o){if(hasAttribute(e,"hx-"+o)){let i=getAttributeValue(e,"hx-"+o);r=!0,t.path=i,t.verb=o,n.forEach(function(s){addTriggerHandler(e,s,t,function(a,l){let c=asElement(a);if(eltIsDisabled(c)){cleanUpElement(c);return}issueAjaxRequest(o,i,c,l)})})}}),r}function addTriggerHandler(e,t,n,r){if(t.trigger==="revealed")initScrollHandler(),addEventListener(e,r,n,t),maybeReveal(asElement(e));else if(t.trigger==="intersect"){let o={};t.root&&(o.root=querySelectorExt(e,t.root)),t.threshold&&(o.threshold=parseFloat(t.threshold)),new IntersectionObserver(function(s){for(let a=0;a<s.length;a++)if(s[a].isIntersecting){triggerEvent(e,"intersect");break}},o).observe(asElement(e)),addEventListener(asElement(e),r,n,t)}else!n.firstInitCompleted&&t.trigger==="load"?maybeFilterEvent(t,e,makeEvent("load",{elt:e}))||loadImmediately(asElement(e),r,n,t.delay):t.pollInterval>0?(n.polling=!0,processPolling(asElement(e),r,t)):addEventListener(e,r,n,t)}function shouldProcessHxOn(e){let t=asElement(e);if(!t)return!1;let n=t.attributes;for(let r=0;r<n.length;r++){let o=n[r].name;if(startsWith(o,"hx-on:")||startsWith(o,"data-hx-on:")||startsWith(o,"hx-on-")||startsWith(o,"data-hx-on-"))return!0}return!1}let HX_ON_QUERY=new XPathEvaluator().createExpression('.//*[@*[ starts-with(name(), "hx-on:") or starts-with(name(), "data-hx-on:") or starts-with(name(), "hx-on-") or starts-with(name(), "data-hx-on-") ]]');function processHXOnRoot(e,t){shouldProcessHxOn(e)&&t.push(asElement(e));let n=HX_ON_QUERY.evaluate(e),r=null;for(;r=n.iterateNext();)t.push(asElement(r))}function findHxOnWildcardElements(e){let t=[];if(e instanceof DocumentFragment)for(let n of e.childNodes)processHXOnRoot(n,t);else processHXOnRoot(e,t);return t}function findElementsToProcess(e){if(e.querySelectorAll){let n=", [hx-boost] a, [data-hx-boost] a, a[hx-boost], a[data-hx-boost]",r=[];for(let i in extensions){let s=extensions[i];if(s.getSelectors){var t=s.getSelectors();t&&r.push(t)}}return e.querySelectorAll(VERB_SELECTOR+n+", form, [type='submit'], [hx-ext], [data-hx-ext], [hx-trigger], [data-hx-trigger]"+r.flat().map(i=>", "+i).join(""))}else return[]}function maybeSetLastButtonClicked(e){let t=getTargetButton(e.target),n=getRelatedFormData(e);n&&(n.lastButtonClicked=t)}function maybeUnsetLastButtonClicked(e){let t=getRelatedFormData(e);t&&(t.lastButtonClicked=null)}function getTargetButton(e){return closest(asElement(e),"button, input[type='submit']")}function getRelatedForm(e){return e.form||closest(e,"form")}function getRelatedFormData(e){let t=getTargetButton(e.target);if(!t)return;let n=getRelatedForm(t);if(n)return getInternalData(n)}function initButtonTracking(e){e.addEventListener("click",maybeSetLastButtonClicked),e.addEventListener("focusin",maybeSetLastButtonClicked),e.addEventListener("focusout",maybeUnsetLastButtonClicked)}function addHxOnEventHandler(e,t,n){let r=getInternalData(e);Array.isArray(r.onHandlers)||(r.onHandlers=[]);let o,i=function(s){maybeEval(e,function(){eltIsDisabled(e)||(o||(o=new Function("event",n)),o.call(e,s))})};e.addEventListener(t,i),r.onHandlers.push({event:t,listener:i})}function processHxOnWildcard(e){deInitOnHandlers(e);for(let t=0;t<e.attributes.length;t++){let n=e.attributes[t].name,r=e.attributes[t].value;if(startsWith(n,"hx-on")||startsWith(n,"data-hx-on")){let o=n.indexOf("-on")+3,i=n.slice(o,o+1);if(i==="-"||i===":"){let s=n.slice(o+1);startsWith(s,":")?s="htmx"+s:startsWith(s,"-")?s="htmx:"+s.slice(1):startsWith(s,"htmx-")&&(s="htmx:"+s.slice(5)),addHxOnEventHandler(e,s,r)}}}}function initNode(e){triggerEvent(e,"htmx:beforeProcessNode");let t=getInternalData(e),n=getTriggerSpecs(e);processVerbs(e,t,n)||(getClosestAttributeValue(e,"hx-boost")==="true"?boostElement(e,t,n):hasAttribute(e,"hx-trigger")&&n.forEach(function(o){addTriggerHandler(e,o,t,function(){})})),(e.tagName==="FORM"||getRawAttribute(e,"type")==="submit"&&hasAttribute(e,"form"))&&initButtonTracking(e),t.firstInitCompleted=!0,triggerEvent(e,"htmx:afterProcessNode")}function maybeDeInitAndHash(e){if(!(e instanceof Element))return!1;let t=getInternalData(e),n=attributeHash(e);return t.initHash!==n?(deInitNode(e),t.initHash=n,!0):!1}function processNode(e){if(e=resolveTarget(e),eltIsDisabled(e)){cleanUpElement(e);return}let t=[];maybeDeInitAndHash(e)&&t.push(e),forEach(findElementsToProcess(e),function(n){if(eltIsDisabled(n)){cleanUpElement(n);return}maybeDeInitAndHash(n)&&t.push(n)}),forEach(findHxOnWildcardElements(e),processHxOnWildcard),forEach(t,initNode)}function kebabEventName(e){return e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase()}function makeEvent(e,t){return new CustomEvent(e,{bubbles:!0,cancelable:!0,composed:!0,detail:t})}function triggerErrorEvent(e,t,n){triggerEvent(e,t,mergeObjects({error:t},n))}function ignoreEventForLogging(e){return e==="htmx:afterProcessNode"}function withExtensions(e,t,n){forEach(getExtensions(e,[],n),function(r){try{t(r)}catch(o){logError(o)}})}function logError(e){console.error(e)}function triggerEvent(e,t,n){e=resolveTarget(e),n==null&&(n={}),n.elt=e;let r=makeEvent(t,n);htmx.logger&&!ignoreEventForLogging(t)&&htmx.logger(e,t,n),n.error&&(logError(n.error),triggerEvent(e,"htmx:error",{errorInfo:n}));let o=e.dispatchEvent(r),i=kebabEventName(t);if(o&&i!==t){let s=makeEvent(i,r.detail);o=o&&e.dispatchEvent(s)}return withExtensions(asElement(e),function(s){o=o&&s.onEvent(t,r)!==!1&&!r.defaultPrevented}),o}let currentPathForHistory;function setCurrentPathForHistory(e){currentPathForHistory=e,canAccessLocalStorage()&&sessionStorage.setItem("htmx-current-path-for-history",e)}setCurrentPathForHistory(location.pathname+location.search);function getHistoryElement(){return getDocument().querySelector("[hx-history-elt],[data-hx-history-elt]")||getDocument().body}function saveToHistoryCache(e,t){if(!canAccessLocalStorage())return;let n=cleanInnerHtmlForHistory(t),r=getDocument().title,o=window.scrollY;if(htmx.config.historyCacheSize<=0){sessionStorage.removeItem("htmx-history-cache");return}e=normalizePath(e);let i=parseJSON(sessionStorage.getItem("htmx-history-cache"))||[];for(let a=0;a<i.length;a++)if(i[a].url===e){i.splice(a,1);break}let s={url:e,content:n,title:r,scroll:o};for(triggerEvent(getDocument().body,"htmx:historyItemCreated",{item:s,cache:i}),i.push(s);i.length>htmx.config.historyCacheSize;)i.shift();for(;i.length>0;)try{sessionStorage.setItem("htmx-history-cache",JSON.stringify(i));break}catch(a){triggerErrorEvent(getDocument().body,"htmx:historyCacheError",{cause:a,cache:i}),i.shift()}}function getCachedHistory(e){if(!canAccessLocalStorage())return null;e=normalizePath(e);let t=parseJSON(sessionStorage.getItem("htmx-history-cache"))||[];for(let n=0;n<t.length;n++)if(t[n].url===e)return t[n];return null}function cleanInnerHtmlForHistory(e){let t=htmx.config.requestClass,n=e.cloneNode(!0);return forEach(findAll(n,"."+t),function(r){removeClassFromElement(r,t)}),forEach(findAll(n,"[data-disabled-by-htmx]"),function(r){r.removeAttribute("disabled")}),n.innerHTML}function saveCurrentPageToHistory(){let e=getHistoryElement(),t=currentPathForHistory;canAccessLocalStorage()&&(t=sessionStorage.getItem("htmx-current-path-for-history")),t=t||location.pathname+location.search,getDocument().querySelector('[hx-history="false" i],[data-hx-history="false" i]')||(triggerEvent(getDocument().body,"htmx:beforeHistorySave",{path:t,historyElt:e}),saveToHistoryCache(t,e)),htmx.config.historyEnabled&&history.replaceState({htmx:!0},getDocument().title,location.href)}function pushUrlIntoHistory(e){htmx.config.getCacheBusterParam&&(e=e.replace(/org\.htmx\.cache-buster=[^&]*&?/,""),(endsWith(e,"&")||endsWith(e,"?"))&&(e=e.slice(0,-1))),htmx.config.historyEnabled&&history.pushState({htmx:!0},"",e),setCurrentPathForHistory(e)}function replaceUrlInHistory(e){htmx.config.historyEnabled&&history.replaceState({htmx:!0},"",e),setCurrentPathForHistory(e)}function settleImmediately(e){forEach(e,function(t){t.call(void 0)})}function loadHistoryFromServer(e){let t=new XMLHttpRequest,n={swapStyle:"innerHTML",swapDelay:0,settleDelay:0},r={path:e,xhr:t,historyElt:getHistoryElement(),swapSpec:n};t.open("GET",e,!0),htmx.config.historyRestoreAsHxRequest&&t.setRequestHeader("HX-Request","true"),t.setRequestHeader("HX-History-Restore-Request","true"),t.setRequestHeader("HX-Current-URL",location.href),t.onload=function(){this.status>=200&&this.status<400?(r.response=this.response,triggerEvent(getDocument().body,"htmx:historyCacheMissLoad",r),swap(r.historyElt,r.response,n,{contextElement:r.historyElt,historyRequest:!0}),setCurrentPathForHistory(r.path),triggerEvent(getDocument().body,"htmx:historyRestore",{path:e,cacheMiss:!0,serverResponse:r.response})):triggerErrorEvent(getDocument().body,"htmx:historyCacheMissLoadError",r)},triggerEvent(getDocument().body,"htmx:historyCacheMiss",r)&&t.send()}function restoreHistory(e){saveCurrentPageToHistory(),e=e||location.pathname+location.search;let t=getCachedHistory(e);if(t){let n={swapStyle:"innerHTML",swapDelay:0,settleDelay:0,scroll:t.scroll},r={path:e,item:t,historyElt:getHistoryElement(),swapSpec:n};triggerEvent(getDocument().body,"htmx:historyCacheHit",r)&&(swap(r.historyElt,t.content,n,{contextElement:r.historyElt,title:t.title}),setCurrentPathForHistory(r.path),triggerEvent(getDocument().body,"htmx:historyRestore",r))}else htmx.config.refreshOnHistoryMiss?htmx.location.reload(!0):loadHistoryFromServer(e)}function addRequestIndicatorClasses(e){let t=findAttributeTargets(e,"hx-indicator");return t==null&&(t=[e]),forEach(t,function(n){let r=getInternalData(n);r.requestCount=(r.requestCount||0)+1,n.classList.add.call(n.classList,htmx.config.requestClass)}),t}function disableElements(e){let t=findAttributeTargets(e,"hx-disabled-elt");return t==null&&(t=[]),forEach(t,function(n){let r=getInternalData(n);r.requestCount=(r.requestCount||0)+1,n.setAttribute("disabled",""),n.setAttribute("data-disabled-by-htmx","")}),t}function removeRequestIndicators(e,t){forEach(e.concat(t),function(n){let r=getInternalData(n);r.requestCount=(r.requestCount||1)-1}),forEach(e,function(n){getInternalData(n).requestCount===0&&n.classList.remove.call(n.classList,htmx.config.requestClass)}),forEach(t,function(n){getInternalData(n).requestCount===0&&(n.removeAttribute("disabled"),n.removeAttribute("data-disabled-by-htmx"))})}function haveSeenNode(e,t){for(let n=0;n<e.length;n++)if(e[n].isSameNode(t))return!0;return!1}function shouldInclude(e){let t=e;return t.name===""||t.name==null||t.disabled||closest(t,"fieldset[disabled]")||t.type==="button"||t.type==="submit"||t.tagName==="image"||t.tagName==="reset"||t.tagName==="file"?!1:t.type==="checkbox"||t.type==="radio"?t.checked:!0}function addValueToFormData(e,t,n){e!=null&&t!=null&&(Array.isArray(t)?t.forEach(function(r){n.append(e,r)}):n.append(e,t))}function removeValueFromFormData(e,t,n){if(e!=null&&t!=null){let r=n.getAll(e);Array.isArray(t)?r=r.filter(o=>t.indexOf(o)<0):r=r.filter(o=>o!==t),n.delete(e),forEach(r,o=>n.append(e,o))}}function getValueFromInput(e){return e instanceof HTMLSelectElement&&e.multiple?toArray(e.querySelectorAll("option:checked")).map(function(t){return t.value}):e instanceof HTMLInputElement&&e.files?toArray(e.files):e.value}function processInputValue(e,t,n,r,o){if(!(r==null||haveSeenNode(e,r))){if(e.push(r),shouldInclude(r)){let i=getRawAttribute(r,"name");addValueToFormData(i,getValueFromInput(r),t),o&&validateElement(r,n)}r instanceof HTMLFormElement&&(forEach(r.elements,function(i){e.indexOf(i)>=0?removeValueFromFormData(i.name,getValueFromInput(i),t):e.push(i),o&&validateElement(i,n)}),new FormData(r).forEach(function(i,s){i instanceof File&&i.name===""||addValueToFormData(s,i,t)}))}}function validateElement(e,t){let n=e;n.willValidate&&(triggerEvent(n,"htmx:validation:validate"),n.checkValidity()||(triggerEvent(n,"htmx:validation:failed",{message:n.validationMessage,validity:n.validity})&&!t.length&&htmx.config.reportValidityOfForms&&n.reportValidity(),t.push({elt:n,message:n.validationMessage,validity:n.validity})))}function overrideFormData(e,t){for(let n of t.keys())e.delete(n);return t.forEach(function(n,r){e.append(r,n)}),e}function getInputValues(e,t){let n=[],r=new FormData,o=new FormData,i=[],s=getInternalData(e);s.lastButtonClicked&&!bodyContains(s.lastButtonClicked)&&(s.lastButtonClicked=null);let a=e instanceof HTMLFormElement&&e.noValidate!==!0||getAttributeValue(e,"hx-validate")==="true";if(s.lastButtonClicked&&(a=a&&s.lastButtonClicked.formNoValidate!==!0),t!=="get"&&processInputValue(n,o,i,getRelatedForm(e),a),processInputValue(n,r,i,e,a),s.lastButtonClicked||e.tagName==="BUTTON"||e.tagName==="INPUT"&&getRawAttribute(e,"type")==="submit"){let c=s.lastButtonClicked||e,h=getRawAttribute(c,"name");addValueToFormData(h,c.value,o)}let l=findAttributeTargets(e,"hx-include");return forEach(l,function(c){processInputValue(n,r,i,asElement(c),a),matches(c,"form")||forEach(asParentNode(c).querySelectorAll(INPUT_SELECTOR),function(h){processInputValue(n,r,i,h,a)})}),overrideFormData(r,o),{errors:i,formData:r,values:formDataProxy(r)}}function appendParam(e,t,n){e!==""&&(e+="&"),String(n)==="[object Object]"&&(n=JSON.stringify(n));let r=encodeURIComponent(n);return e+=encodeURIComponent(t)+"="+r,e}function urlEncode(e){e=formDataFromObject(e);let t="";return e.forEach(function(n,r){t=appendParam(t,r,n)}),t}function getHeaders(e,t,n){let r={"HX-Request":"true","HX-Trigger":getRawAttribute(e,"id"),"HX-Trigger-Name":getRawAttribute(e,"name"),"HX-Target":getAttributeValue(t,"id"),"HX-Current-URL":location.href};return getValuesForElement(e,"hx-headers",!1,r),n!==void 0&&(r["HX-Prompt"]=n),getInternalData(e).boosted&&(r["HX-Boosted"]="true"),r}function filterValues(e,t){let n=getClosestAttributeValue(t,"hx-params");if(n){if(n==="none")return new FormData;if(n==="*")return e;if(n.indexOf("not ")===0)return forEach(n.slice(4).split(","),function(r){r=r.trim(),e.delete(r)}),e;{let r=new FormData;return forEach(n.split(","),function(o){o=o.trim(),e.has(o)&&e.getAll(o).forEach(function(i){r.append(o,i)})}),r}}else return e}function isAnchorLink(e){return!!getRawAttribute(e,"href")&&getRawAttribute(e,"href").indexOf("#")>=0}function getSwapSpecification(e,t){let n=t||getClosestAttributeValue(e,"hx-swap"),r={swapStyle:getInternalData(e).boosted?"innerHTML":htmx.config.defaultSwapStyle,swapDelay:htmx.config.defaultSwapDelay,settleDelay:htmx.config.defaultSettleDelay};if(htmx.config.scrollIntoViewOnBoost&&getInternalData(e).boosted&&!isAnchorLink(e)&&(r.show="top"),n){let s=splitOnWhitespace(n);if(s.length>0)for(let a=0;a<s.length;a++){let l=s[a];if(l.indexOf("swap:")===0)r.swapDelay=parseInterval(l.slice(5));else if(l.indexOf("settle:")===0)r.settleDelay=parseInterval(l.slice(7));else if(l.indexOf("transition:")===0)r.transition=l.slice(11)==="true";else if(l.indexOf("ignoreTitle:")===0)r.ignoreTitle=l.slice(12)==="true";else if(l.indexOf("scroll:")===0){var o=l.slice(7).split(":");let h=o.pop();var i=o.length>0?o.join(":"):null;r.scroll=h,r.scrollTarget=i}else if(l.indexOf("show:")===0){var o=l.slice(5).split(":");let u=o.pop();var i=o.length>0?o.join(":"):null;r.show=u,r.showTarget=i}else if(l.indexOf("focus-scroll:")===0){let c=l.slice(13);r.focusScroll=c=="true"}else a==0?r.swapStyle=l:logError("Unknown modifier in hx-swap: "+l)}}return r}function usesFormData(e){return getClosestAttributeValue(e,"hx-encoding")==="multipart/form-data"||matches(e,"form")&&getRawAttribute(e,"enctype")==="multipart/form-data"}function encodeParamsForBody(e,t,n){let r=null;return withExtensions(t,function(o){r==null&&(r=o.encodeParameters(e,n,t))}),r??(usesFormData(t)?overrideFormData(new FormData,formDataFromObject(n)):urlEncode(n))}function makeSettleInfo(e){return{tasks:[],elts:[e]}}function updateScrollState(e,t){let n=e[0],r=e[e.length-1];if(t.scroll){var o=null;t.scrollTarget&&(o=asElement(querySelectorExt(n,t.scrollTarget))),t.scroll==="top"&&(n||o)&&(o=o||n,o.scrollTop=0),t.scroll==="bottom"&&(r||o)&&(o=o||r,o.scrollTop=o.scrollHeight),typeof t.scroll=="number"&&getWindow().setTimeout(function(){window.scrollTo(0,t.scroll)},0)}if(t.show){var o=null;if(t.showTarget){let s=t.showTarget;t.showTarget==="window"&&(s="body"),o=asElement(querySelectorExt(n,s))}t.show==="top"&&(n||o)&&(o=o||n,o.scrollIntoView({block:"start",behavior:htmx.config.scrollBehavior})),t.show==="bottom"&&(r||o)&&(o=o||r,o.scrollIntoView({block:"end",behavior:htmx.config.scrollBehavior}))}}function getValuesForElement(e,t,n,r,o){if(r==null&&(r={}),e==null)return r;let i=getAttributeValue(e,t);if(i){let s=i.trim(),a=n;if(s==="unset")return null;s.indexOf("javascript:")===0?(s=s.slice(11),a=!0):s.indexOf("js:")===0&&(s=s.slice(3),a=!0),s.indexOf("{")!==0&&(s="{"+s+"}");let l;a?l=maybeEval(e,function(){return o?Function("event","return ("+s+")").call(e,o):Function("return ("+s+")").call(e)},{}):l=parseJSON(s);for(let c in l)l.hasOwnProperty(c)&&r[c]==null&&(r[c]=l[c])}return getValuesForElement(asElement(parentElt(e)),t,n,r,o)}function maybeEval(e,t,n){return htmx.config.allowEval?t():(triggerErrorEvent(e,"htmx:evalDisallowedError"),n)}function getHXVarsForElement(e,t,n){return getValuesForElement(e,"hx-vars",!0,n,t)}function getHXValsForElement(e,t,n){return getValuesForElement(e,"hx-vals",!1,n,t)}function getExpressionVars(e,t){return mergeObjects(getHXVarsForElement(e,t),getHXValsForElement(e,t))}function safelySetHeaderValue(e,t,n){if(n!==null)try{e.setRequestHeader(t,n)}catch{e.setRequestHeader(t,encodeURIComponent(n)),e.setRequestHeader(t+"-URI-AutoEncoded","true")}}function getPathFromResponse(e){if(e.responseURL)try{let t=new URL(e.responseURL);return t.pathname+t.search}catch{triggerErrorEvent(getDocument().body,"htmx:badResponseUrl",{url:e.responseURL})}}function hasHeader(e,t){return t.test(e.getAllResponseHeaders())}function ajaxHelper(e,t,n){if(e=e.toLowerCase(),n){if(n instanceof Element||typeof n=="string")return issueAjaxRequest(e,t,null,null,{targetOverride:resolveTarget(n)||DUMMY_ELT,returnPromise:!0});{let r=resolveTarget(n.target);return(n.target&&!r||n.source&&!r&&!resolveTarget(n.source))&&(r=DUMMY_ELT),issueAjaxRequest(e,t,resolveTarget(n.source),n.event,{handler:n.handler,headers:n.headers,values:n.values,targetOverride:r,swapOverride:n.swap,select:n.select,returnPromise:!0,push:n.push,replace:n.replace,selectOOB:n.selectOOB})}}else return issueAjaxRequest(e,t,null,null,{returnPromise:!0})}function hierarchyForElt(e){let t=[];for(;e;)t.push(e),e=e.parentElement;return t}function verifyPath(e,t,n){let r=new URL(t,location.protocol!=="about:"?location.href:window.origin),i=(location.protocol!=="about:"?location.origin:window.origin)===r.origin;return htmx.config.selfRequestsOnly&&!i?!1:triggerEvent(e,"htmx:validateUrl",mergeObjects({url:r,sameHost:i},n))}function formDataFromObject(e){if(e instanceof FormData)return e;let t=new FormData;for(let n in e)e.hasOwnProperty(n)&&(e[n]&&typeof e[n].forEach=="function"?e[n].forEach(function(r){t.append(n,r)}):typeof e[n]=="object"&&!(e[n]instanceof Blob)?t.append(n,JSON.stringify(e[n])):t.append(n,e[n]));return t}function formDataArrayProxy(e,t,n){return new Proxy(n,{get:function(r,o){return typeof o=="number"?r[o]:o==="length"?r.length:o==="push"?function(i){r.push(i),e.append(t,i)}:typeof r[o]=="function"?function(){r[o].apply(r,arguments),e.delete(t),r.forEach(function(i){e.append(t,i)})}:r[o]&&r[o].length===1?r[o][0]:r[o]},set:function(r,o,i){return r[o]=i,e.delete(t),r.forEach(function(s){e.append(t,s)}),!0}})}function formDataProxy(e){return new Proxy(e,{get:function(t,n){if(typeof n=="symbol"){let o=Reflect.get(t,n);return typeof o=="function"?function(){return o.apply(e,arguments)}:o}if(n==="toJSON")return()=>Object.fromEntries(e);if(n in t&&typeof t[n]=="function")return function(){return e[n].apply(e,arguments)};let r=e.getAll(n);if(r.length!==0)return r.length===1?r[0]:formDataArrayProxy(t,n,r)},set:function(t,n,r){return typeof n!="string"?!1:(t.delete(n),r&&typeof r.forEach=="function"?r.forEach(function(o){t.append(n,o)}):typeof r=="object"&&!(r instanceof Blob)?t.append(n,JSON.stringify(r)):t.append(n,r),!0)},deleteProperty:function(t,n){return typeof n=="string"&&t.delete(n),!0},ownKeys:function(t){return Reflect.ownKeys(Object.fromEntries(t))},getOwnPropertyDescriptor:function(t,n){return Reflect.getOwnPropertyDescriptor(Object.fromEntries(t),n)}})}function issueAjaxRequest(e,t,n,r,o,i){let s=null,a=null;if(o=o??{},o.returnPromise&&typeof Promise<"u")var l=new Promise(function(p,w){s=p,a=w});n==null&&(n=getDocument().body);let c=o.handler||handleAjaxResponse,h=o.select||null;if(!bodyContains(n))return maybeCall(s),l;let u=o.targetOverride||asElement(getTarget(n));if(u==null||u==DUMMY_ELT)return triggerErrorEvent(n,"htmx:targetError",{target:getClosestAttributeValue(n,"hx-target")}),maybeCall(a),l;let f=getInternalData(n),v=f.lastButtonClicked;if(v){let p=getRawAttribute(v,"formaction");p!=null&&(t=p);let w=getRawAttribute(v,"formmethod");if(w!=null)if(VERBS.includes(w.toLowerCase()))e=w;else return maybeCall(s),l}let d=getClosestAttributeValue(n,"hx-confirm");if(i===void 0&&triggerEvent(n,"htmx:confirm",{target:u,elt:n,path:t,verb:e,triggeringEvent:r,etc:o,issueRequest:function(T){return issueAjaxRequest(e,t,n,r,o,!!T)},question:d})===!1)return maybeCall(s),l;let y=n,m=getClosestAttributeValue(n,"hx-sync"),x=null,b=!1;if(m){let p=m.split(":"),w=p[0].trim();if(w==="this"?y=findThisElement(n,"hx-sync"):y=asElement(querySelectorExt(n,w)),m=(p[1]||"drop").trim(),f=getInternalData(y),m==="drop"&&f.xhr&&f.abortable!==!0)return maybeCall(s),l;if(m==="abort"){if(f.xhr)return maybeCall(s),l;b=!0}else m==="replace"?triggerEvent(y,"htmx:abort"):m.indexOf("queue")===0&&(x=(m.split(" ")[1]||"last").trim())}if(f.xhr)if(f.abortable)triggerEvent(y,"htmx:abort");else{if(x==null){if(r){let p=getInternalData(r);p&&p.triggerSpec&&p.triggerSpec.queue&&(x=p.triggerSpec.queue)}x==null&&(x="last")}return f.queuedRequests==null&&(f.queuedRequests=[]),x==="first"&&f.queuedRequests.length===0?f.queuedRequests.push(function(){issueAjaxRequest(e,t,n,r,o)}):x==="all"?f.queuedRequests.push(function(){issueAjaxRequest(e,t,n,r,o)}):x==="last"&&(f.queuedRequests=[],f.queuedRequests.push(function(){issueAjaxRequest(e,t,n,r,o)})),maybeCall(s),l}let E=new XMLHttpRequest;f.xhr=E,f.abortable=b;let g=function(){f.xhr=null,f.abortable=!1,f.queuedRequests!=null&&f.queuedRequests.length>0&&f.queuedRequests.shift()()},X=getClosestAttributeValue(n,"hx-prompt");if(X){var k=prompt(X);if(k===null||!triggerEvent(n,"htmx:prompt",{prompt:k,target:u}))return maybeCall(s),g(),l}if(d&&!i&&!confirm(d))return maybeCall(s),g(),l;let H=getHeaders(n,u,k);e!=="get"&&!usesFormData(n)&&(H["Content-Type"]="application/x-www-form-urlencoded"),o.headers&&(H=mergeObjects(H,o.headers));let $=getInputValues(n,e),R=$.errors,J=$.formData;o.values&&overrideFormData(J,formDataFromObject(o.values));let oe=formDataFromObject(getExpressionVars(n,r)),P=overrideFormData(J,oe),D=filterValues(P,n);htmx.config.getCacheBusterParam&&e==="get"&&D.set("org.htmx.cache-buster",getRawAttribute(u,"id")||"true"),(t==null||t==="")&&(t=location.href);let N=getValuesForElement(n,"hx-request"),z=getInternalData(n).boosted,L=htmx.config.methodsThatUseUrlParams.indexOf(e)>=0,S={boosted:z,useUrlParams:L,formData:D,parameters:formDataProxy(D),unfilteredFormData:P,unfilteredParameters:formDataProxy(P),headers:H,elt:n,target:u,verb:e,errors:R,withCredentials:o.credentials||N.credentials||htmx.config.withCredentials,timeout:o.timeout||N.timeout||htmx.config.timeout,path:t,triggeringEvent:r};if(!triggerEvent(n,"htmx:configRequest",S))return maybeCall(s),g(),l;if(t=S.path,e=S.verb,H=S.headers,D=formDataFromObject(S.parameters),R=S.errors,L=S.useUrlParams,R&&R.length>0)return triggerEvent(n,"htmx:validation:halted",S),maybeCall(s),g(),l;let Y=t.split("#"),ie=Y[0],M=Y[1],A=t;if(L&&(A=ie,!D.keys().next().done&&(A.indexOf("?")<0?A+="?":A+="&",A+=urlEncode(D),M&&(A+="#"+M))),!verifyPath(n,A,S))return triggerErrorEvent(n,"htmx:invalidPath",S),maybeCall(a),g(),l;if(E.open(e.toUpperCase(),A,!0),E.overrideMimeType("text/html"),E.withCredentials=S.withCredentials,E.timeout=S.timeout,!N.noHeaders){for(let p in H)if(H.hasOwnProperty(p)){let w=H[p];safelySetHeaderValue(E,p,w)}}let C={xhr:E,target:u,requestConfig:S,etc:o,boosted:z,select:h,pathInfo:{requestPath:t,finalRequestPath:A,responsePath:null,anchor:M}};if(E.onload=function(){try{let p=hierarchyForElt(n);if(C.pathInfo.responsePath=getPathFromResponse(E),c(n,C),C.keepIndicators!==!0&&removeRequestIndicators(O,F),triggerEvent(n,"htmx:afterRequest",C),triggerEvent(n,"htmx:afterOnLoad",C),!bodyContains(n)){let w=null;for(;p.length>0&&w==null;){let T=p.shift();bodyContains(T)&&(w=T)}w&&(triggerEvent(w,"htmx:afterRequest",C),triggerEvent(w,"htmx:afterOnLoad",C))}maybeCall(s)}catch(p){throw triggerErrorEvent(n,"htmx:onLoadError",mergeObjects({error:p},C)),p}finally{g()}},E.onerror=function(){removeRequestIndicators(O,F),triggerErrorEvent(n,"htmx:afterRequest",C),triggerErrorEvent(n,"htmx:sendError",C),maybeCall(a),g()},E.onabort=function(){removeRequestIndicators(O,F),triggerErrorEvent(n,"htmx:afterRequest",C),triggerErrorEvent(n,"htmx:sendAbort",C),maybeCall(a),g()},E.ontimeout=function(){removeRequestIndicators(O,F),triggerErrorEvent(n,"htmx:afterRequest",C),triggerErrorEvent(n,"htmx:timeout",C),maybeCall(a),g()},!triggerEvent(n,"htmx:beforeRequest",C))return maybeCall(s),g(),l;var O=addRequestIndicatorClasses(n),F=disableElements(n);forEach(["loadstart","loadend","progress","abort"],function(p){forEach([E,E.upload],function(w){w.addEventListener(p,function(T){triggerEvent(n,"htmx:xhr:"+p,{lengthComputable:T.lengthComputable,loaded:T.loaded,total:T.total})})})}),triggerEvent(n,"htmx:beforeSend",C);let se=L?null:encodeParamsForBody(E,n,D);return E.send(se),l}function determineHistoryUpdates(e,t){let n=t.xhr,r=null,o=null;if(hasHeader(n,/HX-Push:/i)?(r=n.getResponseHeader("HX-Push"),o="push"):hasHeader(n,/HX-Push-Url:/i)?(r=n.getResponseHeader("HX-Push-Url"),o="push"):hasHeader(n,/HX-Replace-Url:/i)&&(r=n.getResponseHeader("HX-Replace-Url"),o="replace"),r)return r==="false"?{}:{type:o,path:r};let i=t.pathInfo.finalRequestPath,s=t.pathInfo.responsePath,a=t.etc.push||getClosestAttributeValue(e,"hx-push-url"),l=t.etc.replace||getClosestAttributeValue(e,"hx-replace-url"),c=getInternalData(e).boosted,h=null,u=null;return a?(h="push",u=a):l?(h="replace",u=l):c&&(h="push",u=s||i),u?u==="false"?{}:(u==="true"&&(u=s||i),t.pathInfo.anchor&&u.indexOf("#")===-1&&(u=u+"#"+t.pathInfo.anchor),{type:h,path:u}):{}}function codeMatches(e,t){var n=new RegExp(e.code);return n.test(t.toString(10))}function resolveResponseHandling(e){for(var t=0;t<htmx.config.responseHandling.length;t++){var n=htmx.config.responseHandling[t];if(codeMatches(n,e.status))return n}return{swap:!1}}function handleTitle(e){if(e){let t=find("title");t?t.textContent=e:window.document.title=e}}function resolveRetarget(e,t){if(t==="this")return e;let n=asElement(querySelectorExt(e,t));if(n==null)throw triggerErrorEvent(e,"htmx:targetError",{target:t}),new Error(`Invalid re-target ${t}`);return n}function handleAjaxResponse(e,t){let n=t.xhr,r=t.target,o=t.etc,i=t.select;if(!triggerEvent(e,"htmx:beforeOnLoad",t))return;if(hasHeader(n,/HX-Trigger:/i)&&handleTriggerHeader(n,"HX-Trigger",e),hasHeader(n,/HX-Location:/i)){let b=n.getResponseHeader("HX-Location");var s={};b.indexOf("{")===0&&(s=parseJSON(b),b=s.path,delete s.path),s.push=s.push||"true",ajaxHelper("get",b,s);return}let a=hasHeader(n,/HX-Refresh:/i)&&n.getResponseHeader("HX-Refresh")==="true";if(hasHeader(n,/HX-Redirect:/i)){t.keepIndicators=!0,htmx.location.href=n.getResponseHeader("HX-Redirect"),a&&htmx.location.reload();return}if(a){t.keepIndicators=!0,htmx.location.reload();return}let l=determineHistoryUpdates(e,t),c=resolveResponseHandling(n),h=c.swap,u=!!c.error,f=htmx.config.ignoreTitle||c.ignoreTitle,v=c.select;c.target&&(t.target=resolveRetarget(e,c.target));var d=o.swapOverride;d==null&&c.swapOverride&&(d=c.swapOverride),hasHeader(n,/HX-Retarget:/i)&&(t.target=resolveRetarget(e,n.getResponseHeader("HX-Retarget"))),hasHeader(n,/HX-Reswap:/i)&&(d=n.getResponseHeader("HX-Reswap"));var y=n.response,m=mergeObjects({shouldSwap:h,serverResponse:y,isError:u,ignoreTitle:f,selectOverride:v,swapOverride:d},t);if(!(c.event&&!triggerEvent(r,c.event,m))&&triggerEvent(r,"htmx:beforeSwap",m)){if(r=m.target,y=m.serverResponse,u=m.isError,f=m.ignoreTitle,v=m.selectOverride,d=m.swapOverride,t.target=r,t.failed=u,t.successful=!u,m.shouldSwap){n.status===286&&cancelPolling(e),withExtensions(e,function(g){y=g.transformResponse(y,n,e)}),l.type&&saveCurrentPageToHistory();var x=getSwapSpecification(e,d);x.hasOwnProperty("ignoreTitle")||(x.ignoreTitle=f),r.classList.add(htmx.config.swappingClass),i&&(v=i),hasHeader(n,/HX-Reselect:/i)&&(v=n.getResponseHeader("HX-Reselect"));let b=o.selectOOB||getClosestAttributeValue(e,"hx-select-oob"),E=getClosestAttributeValue(e,"hx-select");swap(r,y,x,{select:v==="unset"?null:v||E,selectOOB:b,eventInfo:t,anchor:t.pathInfo.anchor,contextElement:e,afterSwapCallback:function(){if(hasHeader(n,/HX-Trigger-After-Swap:/i)){let g=e;bodyContains(e)||(g=getDocument().body),handleTriggerHeader(n,"HX-Trigger-After-Swap",g)}},afterSettleCallback:function(){if(hasHeader(n,/HX-Trigger-After-Settle:/i)){let g=e;bodyContains(e)||(g=getDocument().body),handleTriggerHeader(n,"HX-Trigger-After-Settle",g)}},beforeSwapCallback:function(){l.type&&(triggerEvent(getDocument().body,"htmx:beforeHistoryUpdate",mergeObjects({history:l},t)),l.type==="push"?(pushUrlIntoHistory(l.path),triggerEvent(getDocument().body,"htmx:pushedIntoHistory",{path:l.path})):(replaceUrlInHistory(l.path),triggerEvent(getDocument().body,"htmx:replacedInHistory",{path:l.path})))}})}u&&triggerErrorEvent(e,"htmx:responseError",mergeObjects({error:"Response Status Error Code "+n.status+" from "+t.pathInfo.requestPath},t))}}let extensions={};function extensionBase(){return{init:function(e){return null},getSelectors:function(){return null},onEvent:function(e,t){return!0},transformResponse:function(e,t,n){return e},isInlineSwap:function(e){return!1},handleSwap:function(e,t,n,r){return!1},encodeParameters:function(e,t,n){return null}}}function defineExtension(e,t){t.init&&t.init(internalAPI),extensions[e]=mergeObjects(extensionBase(),t)}function removeExtension(e){delete extensions[e]}function getExtensions(e,t,n){if(t==null&&(t=[]),e==null)return t;n==null&&(n=[]);let r=getAttributeValue(e,"hx-ext");return r&&forEach(r.split(","),function(o){if(o=o.replace(/ /g,""),o.slice(0,7)=="ignore:"){n.push(o.slice(7));return}if(n.indexOf(o)<0){let i=extensions[o];i&&t.indexOf(i)<0&&t.push(i)}}),getExtensions(asElement(parentElt(e)),t,n)}var isReady=!1;getDocument().addEventListener("DOMContentLoaded",function(){isReady=!0});function ready(e){isReady||getDocument().readyState==="complete"?e():getDocument().addEventListener("DOMContentLoaded",e)}function insertIndicatorStyles(){if(htmx.config.includeIndicatorStyles!==!1){let e=htmx.config.inlineStyleNonce?` nonce="${htmx.config.inlineStyleNonce}"`:"",t=htmx.config.indicatorClass,n=htmx.config.requestClass;getDocument().head.insertAdjacentHTML("beforeend",`<style${e}>.${t}{opacity:0;visibility: hidden} .${n} .${t}, .${n}.${t}{opacity:1;visibility: visible;transition: opacity 200ms ease-in}</style>`)}}function getMetaConfig(){let e=getDocument().querySelector('meta[name="htmx-config"]');return e?parseJSON(e.content):null}function mergeMetaConfig(){let e=getMetaConfig();e&&(htmx.config=mergeObjects(htmx.config,e))}return ready(function(){mergeMetaConfig(),insertIndicatorStyles();let e=getDocument().body;processNode(e);let t=getDocument().querySelectorAll("[hx-trigger='restored'],[data-hx-trigger='restored']");e.addEventListener("htmx:abort",function(r){let o=r.detail.elt||r.target,i=getInternalData(o);i&&i.xhr&&i.xhr.abort()});let n=window.onpopstate?window.onpopstate.bind(window):null;window.onpopstate=function(r){r.state&&r.state.htmx?(restoreHistory(),forEach(t,function(o){triggerEvent(o,"htmx:restored",{document:getDocument(),triggerEvent})})):n&&n(r)},getWindow().setTimeout(function(){triggerEvent(e,"htmx:load",{}),e=null},0)}),htmx})(),q=ae;(function(){let e;q.defineExtension("json-enc",{init:function(t){e=t},onEvent:function(t,n){t==="htmx:configRequest"&&(n.detail.headers["Content-Type"]="application/json")},encodeParameters:function(t,n,r){t.overrideMimeType("text/json");let o={};n.forEach(function(s,a){Object.hasOwn(o,a)?(Array.isArray(o[a])||(o[a]=[o[a]]),o[a].push(s)):o[a]=s});let i=e.getExpressionVars(r);return Object.keys(o).forEach(function(s){o[s]=Object.hasOwn(i,s)?i[s]:o[s]}),JSON.stringify(o)}})})();var G=document.createElement("template");G.innerHTML=` 2 2 <slot></slot> 3 3 4 4 <ul class="menu" part="menu"></ul> ··· 92 92 <span class="handle" part="handle"></span> 93 93 </button> 94 94 </li> 95 - `;function K(e){return e.cloneNode(!0)}var B=class extends HTMLElement{static tag="actor-typeahead";static define(t=this.tag){this.tag=t;let n=customElements.getName(this);if(n&&n!==t)return console.warn(`${this.name} already defined as <${n}>!`);let r=customElements.get(t);if(r&&r!==this)return console.warn(`<${t}> already defined as ${r.name}!`);customElements.define(t,this)}static{let t=new URL(import.meta.url).searchParams.get("tag")||this.tag;t!=="none"&&this.define(t)}#n=this.attachShadow({mode:"closed"});#r=[];#e=-1;#o=!1;constructor(){super(),this.#n.append(K(G).content),this.#t(),this.addEventListener("input",this),this.addEventListener("focusout",this),this.addEventListener("keydown",this),this.#n.addEventListener("pointerdown",this),this.#n.addEventListener("pointerup",this),this.#n.addEventListener("click",this)}get#i(){let t=Number.parseInt(this.getAttribute("rows")??"");return Number.isNaN(t)?5:t}handleEvent(t){switch(t.type){case"input":this.#a(t);break;case"keydown":this.#s(t);break;case"focusout":this.#l(t);break;case"pointerdown":this.#c(t);break;case"pointerup":this.#u(t);break}}#s(t){switch(t.key){case"ArrowDown":t.preventDefault(),this.#e=Math.min(this.#e+1,this.#i-1),this.#t();break;case"PageDown":t.preventDefault(),this.#e=this.#i-1,this.#t();break;case"ArrowUp":t.preventDefault(),this.#e=Math.max(this.#e-1,0),this.#t();break;case"PageUp":t.preventDefault(),this.#e=0,this.#t();break;case"Escape":t.preventDefault(),this.#r=[],this.#e=-1,this.#t();break;case"Enter":t.preventDefault(),this.#n.querySelectorAll("button")[this.#e]?.dispatchEvent(new PointerEvent("pointerup",{bubbles:!0}));break}}async#a(t){let n=t.target?.value;if(!n){this.#r=[],this.#t();return}let r=this.getAttribute("host")??"https://public.api.bsky.app",o=new URL("xrpc/app.bsky.actor.searchActorsTypeahead",r);o.searchParams.set("q",n),o.searchParams.set("limit",`${this.#i}`);let s=await(await fetch(o)).json();this.#r=s.actors,this.#e=-1,this.#t()}async#l(t){this.#o||(this.#r=[],this.#e=-1,this.#t())}#t(){let t=document.createDocumentFragment(),n=-1;for(let r of this.#r){let o=K(Q).content,i=o.querySelector("button");i&&(i.dataset.handle=r.handle,++n===this.#e&&(i.dataset.active="true"));let s=o.querySelector("img");s&&r.avatar&&(s.src=r.avatar);let a=o.querySelector(".handle");a&&(a.textContent=r.handle),t.append(o)}this.#n.querySelector(".menu")?.replaceChildren(...t.children)}#c(t){this.#o=!0}#u(t){this.#o=!1,this.querySelector("input")?.focus();let n=t.target?.closest("button"),r=this.querySelector("input");!r||!n||(r.value=n.dataset.handle||"",this.#r=[],this.#t())}};function ee(){return localStorage.getItem("theme")||"system"}function le(e){return e==="dark"||e==="light"?e:window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}function _(){let e=ee(),n=le(e)==="dark";document.documentElement.classList.toggle("dark",n),document.documentElement.setAttribute("data-theme",n?"dark":"light"),ce(e)}function te(e){localStorage.setItem("theme",e),_(),ue()}function ce(e){let t={system:"sun-moon",light:"sun",dark:"moon"};document.querySelectorAll("[data-theme-icon] use").forEach(n=>{n.setAttribute("href",`/icons.svg#${t[e]||"sun-moon"}`)}),document.querySelectorAll(".theme-option").forEach(n=>{let r=n.dataset.value===e,o=n.querySelector(".theme-check");o&&(o.style.visibility=r?"visible":"hidden")})}function ue(){document.querySelectorAll("[data-theme-toggle]").forEach(e=>{let t=e.closest("details");t&&t.removeAttribute("open")})}window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>{ee()==="system"&&_()});function fe(){let e=document.querySelector(".nav-search-wrapper"),t=document.getElementById("nav-search-input");!e||!t||(e.classList.toggle("expanded"),e.classList.contains("expanded")&&t.focus())}function V(){let e=document.querySelector(".nav-search-wrapper");e&&e.classList.remove("expanded")}document.addEventListener("DOMContentLoaded",()=>{let e=document.querySelector(".nav-search-wrapper"),t=document.getElementById("nav-search-input");!e||!t||(document.addEventListener("keydown",n=>{if(n.key==="Escape"&&e.classList.contains("expanded")&&V(),n.key==="/"&&!e.classList.contains("expanded")){let r=n.target.tagName;if(r==="INPUT"||r==="TEXTAREA"||n.target.isContentEditable)return;n.preventDefault(),e.classList.add("expanded"),t.focus()}}),document.addEventListener("click",n=>{e.classList.contains("expanded")&&!e.contains(n.target)&&V()}))});function ne(e,t){!t&&typeof event<"u"&&(t=event.target.closest("button")),navigator.clipboard.writeText(e).then(()=>{if(!t)return;let n=t.innerHTML;t.innerHTML='<svg class="icon size-4" aria-hidden="true"><use href="/icons.svg#check"></use></svg> Copied!',setTimeout(()=>{t.innerHTML=n},2e3)}).catch(n=>{console.error("Failed to copy:",n)})}document.addEventListener("DOMContentLoaded",()=>{document.addEventListener("click",e=>{let t=e.target.closest("button[data-cmd]");if(t){ne(t.getAttribute("data-cmd"),t);return}if(e.target.closest("a, button, input, .cmd"))return;let n=e.target.closest("[data-href]");n&&(window.location=n.getAttribute("data-href"))})});function de(e){let t=Math.floor((new Date-new Date(e))/1e3),n={year:31536e3,month:2592e3,week:604800,day:86400,hour:3600,minute:60,second:1};for(let[r,o]of Object.entries(n)){let i=Math.floor(t/o);if(i>=1)return i===1?`1 ${r} ago`:`${i} ${r}s ago`}return"just now"}function j(){document.querySelectorAll("time[datetime]").forEach(e=>{let t=e.getAttribute("datetime");if(t&&!e.dataset.noUpdate){let n=de(t);e.textContent!==n&&(e.textContent=n)}})}document.addEventListener("DOMContentLoaded",()=>{j(),_(),document.querySelectorAll("[data-theme-menu]").forEach(e=>{e.querySelectorAll(".theme-option").forEach(t=>{t.addEventListener("click",()=>{te(t.dataset.value)})})}),document.addEventListener("click",e=>{let t=e.target.closest("details.dropdown");document.querySelectorAll("details.dropdown[open]").forEach(n=>{n!==t&&n.removeAttribute("open")})})});document.addEventListener("htmx:afterSwap",j);setInterval(j,6e4);function he(){let e=document.getElementById("show-offline-toggle"),t=document.querySelector(".manifests-list");!e||!t||(localStorage.setItem("showOfflineManifests",e.checked),e.checked?t.classList.add("show-offline"):t.classList.remove("show-offline"))}document.addEventListener("DOMContentLoaded",()=>{let e=document.getElementById("show-offline-toggle");if(!e)return;let t=localStorage.getItem("showOfflineManifests")==="true";e.checked=t;let n=document.querySelector(".manifests-list");n&&(t?n.classList.add("show-offline"):n.classList.remove("show-offline"))});async function me(e,t,n){try{let r=await fetch("/api/manifests",{method:"DELETE",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({repo:e,digest:t,confirm:!1})});if(r.status===409){let o=await r.json();ge(e,t,n,o.tags)}else if(r.ok)re(n);else{let o=await r.text();alert(`Failed to delete manifest: ${o}`)}}catch(r){console.error("Error deleting manifest:",r),alert(`Error deleting manifest: ${r.message}`)}}function ge(e,t,n,r){let o=document.getElementById("manifest-delete-modal"),i=document.getElementById("manifest-delete-tags"),s=document.getElementById("confirm-manifest-delete-btn");i.innerHTML="",r.forEach(a=>{let l=document.createElement("li");l.textContent=a,i.appendChild(l)}),s.onclick=()=>pe(e,t,n),o.style.display="flex"}function W(){let e=document.getElementById("manifest-delete-modal");e.style.display="none"}async function pe(e,t,n){let r=document.getElementById("confirm-manifest-delete-btn"),o=r.textContent;try{r.disabled=!0,r.textContent="Deleting...";let i=await fetch("/api/manifests",{method:"DELETE",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({repo:e,digest:t,confirm:!0})});if(i.ok)W(),re(n),location.reload();else{let s=await i.text();alert(`Failed to delete manifest: ${s}`),r.disabled=!1,r.textContent=o}}catch(i){console.error("Error deleting manifest:",i),alert(`Error deleting manifest: ${i.message}`),r.disabled=!1,r.textContent=o}}function re(e){let t=document.getElementById(`manifest-${e}`);t&&t.remove()}document.addEventListener("DOMContentLoaded",()=>{let e=document.getElementById("manifest-delete-modal");e&&e.addEventListener("click",t=>{t.target===e&&W()})});async function Ee(e,t){let n=document.getElementById("vuln-detail-modal"),r=document.getElementById("vuln-modal-body");if(!(!n||!r)){r.innerHTML='<div class="flex justify-center py-8"><span class="loading loading-spinner loading-lg"></span></div>',n.showModal();try{let o=await fetch(`/api/vuln-details?digest=${encodeURIComponent(e)}&holdEndpoint=${encodeURIComponent(t)}`);r.innerHTML=await o.text()}catch{r.innerHTML='<p class="text-error">Failed to load vulnerability details</p>'}}}var U=class{constructor(t){this.input=t,this.typeahead=t.closest("actor-typeahead"),this.dropdown=null,this.currentFocus=-1,this.typeaheadClosed=!1,this.init()}init(){this.createDropdown(),this.input.addEventListener("focus",()=>this.handleFocus()),this.input.addEventListener("input",()=>this.handleInput()),this.input.addEventListener("keydown",t=>this.handleKeydown(t)),document.addEventListener("click",t=>{!this.input.contains(t.target)&&!this.dropdown.contains(t.target)&&this.hideDropdown()})}createDropdown(){this.dropdown=document.createElement("div"),this.dropdown.className="recent-accounts-dropdown",this.dropdown.style.display="none",this.typeahead?this.typeahead.insertAdjacentElement("afterend",this.dropdown):this.input.insertAdjacentElement("afterend",this.dropdown)}handleFocus(){this.input.value.trim().length<1&&this.showRecentAccounts()}handleInput(){this.input.value.trim().length>=1&&this.hideDropdown(),this.typeaheadClosed=!1}showRecentAccounts(){let t=this.getRecentAccounts();if(t.length===0){this.hideDropdown();return}this.dropdown.innerHTML="",this.currentFocus=-1;let n=document.createElement("div");n.className="recent-accounts-header",n.textContent="Recent accounts",this.dropdown.appendChild(n),t.forEach((r,o)=>{let i=document.createElement("div");i.className="recent-accounts-item",i.dataset.index=o,i.dataset.handle=r,i.textContent=r,i.addEventListener("click",()=>this.selectItem(r)),this.dropdown.appendChild(i)}),this.dropdown.style.display="block"}selectItem(t){this.input.value=t,this.hideDropdown(),this.input.focus()}hideDropdown(){this.dropdown.style.display="none",this.currentFocus=-1}handleKeydown(t){if(t.key==="Enter"&&this.input.value.trim().length>=2&&(this.typeaheadClosed=!0),t.key==="Tab"&&this.input.value.trim().length>=2&&!this.typeaheadClosed){t.preventDefault();let o=t.shiftKey?"ArrowUp":"ArrowDown",i=new KeyboardEvent("keydown",{key:o,bubbles:!0,cancelable:!0});this.typeahead.dispatchEvent(i);return}if(this.dropdown.style.display==="none")return;let n=this.dropdown.querySelectorAll(".recent-accounts-item");t.key==="ArrowDown"?(t.preventDefault(),this.currentFocus++,this.currentFocus>=n.length&&(this.currentFocus=0),this.updateFocus(n)):t.key==="ArrowUp"?(t.preventDefault(),this.currentFocus--,this.currentFocus<0&&(this.currentFocus=n.length-1),this.updateFocus(n)):t.key==="Enter"&&this.currentFocus>-1&&n[this.currentFocus]?(t.preventDefault(),this.selectItem(n[this.currentFocus].dataset.handle)):t.key==="Escape"&&this.hideDropdown()}updateFocus(t){t.forEach((n,r)=>{n.classList.toggle("focused",r===this.currentFocus)})}getRecentAccounts(){try{let t=localStorage.getItem("atcr_recent_handles");return t?JSON.parse(t):[]}catch{return[]}}saveRecentAccount(t){if(t)try{let n=this.getRecentAccounts();n=n.filter(r=>r!==t),n.unshift(t),n=n.slice(0,5),localStorage.setItem("atcr_recent_handles",JSON.stringify(n))}catch(n){console.error("Failed to save recent account:",n)}}};document.addEventListener("DOMContentLoaded",()=>{let e=document.getElementById("login-form"),t=document.getElementById("handle");e&&t&&new U(t)});document.addEventListener("DOMContentLoaded",()=>{let e=document.cookie.split("; ").find(n=>n.startsWith("atcr_login_handle="));if(!e)return;let t=decodeURIComponent(e.split("=")[1]);if(t){try{let n="atcr_recent_handles",r=JSON.parse(localStorage.getItem(n)||"[]");r=r.filter(o=>o!==t),r.unshift(t),r=r.slice(0,5),localStorage.setItem(n,JSON.stringify(r))}catch(n){console.error("Failed to save recent account:",n)}document.cookie="atcr_login_handle=; path=/; max-age=0"}});function Z(){let e=document.getElementById("featured-carousel"),t=document.getElementById("carousel-prev"),n=document.getElementById("carousel-next");if(!e)return;let r=Array.from(e.querySelectorAll(".carousel-item"));if(r.length===0)return;let o=null,i=5e3,s=0,a=0,l=0;function c(){let b=r[0];if(!b)return;let E=getComputedStyle(e),g=parseFloat(E.gap)||24;s=b.offsetWidth+g,a=e.offsetWidth,l=e.scrollWidth}requestAnimationFrame(()=>{requestAnimationFrame(()=>{c(),m()})});let h;window.addEventListener("resize",()=>{clearTimeout(h),h=setTimeout(c,150)});function u(){return s||c(),s}function f(){return(!a||!s)&&c(),Math.round(a/s)||1}function v(){return(!l||!a)&&c(),l-a}function d(){let b=u(),E=v(),g=e.scrollLeft;g>=E-10?e.scrollTo({left:0,behavior:"smooth"}):e.scrollTo({left:g+b,behavior:"smooth"})}function y(){let b=u(),E=v(),g=e.scrollLeft;g<=10?e.scrollTo({left:E,behavior:"smooth"}):e.scrollTo({left:g-b,behavior:"smooth"})}t&&t.addEventListener("click",()=>{x(),y(),m()}),n&&n.addEventListener("click",()=>{x(),d(),m()});function m(){o||r.length<=f()||(o=setInterval(d,i))}function x(){o&&(clearInterval(o),o=null)}e.addEventListener("mouseenter",x),e.addEventListener("mouseleave",m)}document.addEventListener("DOMContentLoaded",()=>{"requestIdleCallback"in window?requestIdleCallback(Z,{timeout:2e3}):setTimeout(Z,100)});function q(e,t){let n=document.getElementById("toast-container");n||(n=document.createElement("div"),n.id="toast-container",n.className="toast toast-end toast-bottom z-50",document.body.appendChild(n));let r=t==="error"?"alert-error":"alert-success",o=document.createElement("div");o.className=`alert ${r} shadow-lg transition-opacity duration-300`,o.innerHTML=`<span>${e}</span>`,n.appendChild(o),setTimeout(()=>{o.style.opacity="0",setTimeout(()=>o.remove(),300)},3e3)}async function ye(e){try{let t=await fetch(`/api/webhooks/${e}/test`,{method:"POST",credentials:"include"}),n=await t.text();n.includes('class="success"')||t.ok&&!n.includes('class="error"')?q("Test webhook delivered successfully!","success"):q("Test delivery failed \u2014 check the webhook URL","error")}catch{q("Failed to reach server","error")}}window.setTheme=te;window.toggleSearch=fe;window.closeSearch=V;window.copyToClipboard=ne;window.toggleOfflineManifests=he;window.deleteManifest=me;window.closeManifestDeleteModal=W;window.openVulnDetails=Ee;window.showToast=q;window.testWebhook=ye;window.htmx=F; 95 + `;function K(e){return e.cloneNode(!0)}var B=class extends HTMLElement{static tag="actor-typeahead";static define(t=this.tag){this.tag=t;let n=customElements.getName(this);if(n&&n!==t)return console.warn(`${this.name} already defined as <${n}>!`);let r=customElements.get(t);if(r&&r!==this)return console.warn(`<${t}> already defined as ${r.name}!`);customElements.define(t,this)}static{let t=new URL(import.meta.url).searchParams.get("tag")||this.tag;t!=="none"&&this.define(t)}#n=this.attachShadow({mode:"closed"});#r=[];#e=-1;#o=!1;constructor(){super(),this.#n.append(K(G).content),this.#t(),this.addEventListener("input",this),this.addEventListener("focusout",this),this.addEventListener("keydown",this),this.#n.addEventListener("pointerdown",this),this.#n.addEventListener("pointerup",this),this.#n.addEventListener("click",this)}get#i(){let t=Number.parseInt(this.getAttribute("rows")??"");return Number.isNaN(t)?5:t}handleEvent(t){switch(t.type){case"input":this.#a(t);break;case"keydown":this.#s(t);break;case"focusout":this.#l(t);break;case"pointerdown":this.#c(t);break;case"pointerup":this.#u(t);break}}#s(t){switch(t.key){case"ArrowDown":t.preventDefault(),this.#e=Math.min(this.#e+1,this.#i-1),this.#t();break;case"PageDown":t.preventDefault(),this.#e=this.#i-1,this.#t();break;case"ArrowUp":t.preventDefault(),this.#e=Math.max(this.#e-1,0),this.#t();break;case"PageUp":t.preventDefault(),this.#e=0,this.#t();break;case"Escape":t.preventDefault(),this.#r=[],this.#e=-1,this.#t();break;case"Enter":t.preventDefault(),this.#n.querySelectorAll("button")[this.#e]?.dispatchEvent(new PointerEvent("pointerup",{bubbles:!0}));break}}async#a(t){let n=t.target?.value;if(!n){this.#r=[],this.#t();return}let r=this.getAttribute("host")??"https://public.api.bsky.app",o=new URL("xrpc/app.bsky.actor.searchActorsTypeahead",r);o.searchParams.set("q",n),o.searchParams.set("limit",`${this.#i}`);let s=await(await fetch(o)).json();this.#r=s.actors,this.#e=-1,this.#t()}async#l(t){this.#o||(this.#r=[],this.#e=-1,this.#t())}#t(){let t=document.createDocumentFragment(),n=-1;for(let r of this.#r){let o=K(Q).content,i=o.querySelector("button");i&&(i.dataset.handle=r.handle,++n===this.#e&&(i.dataset.active="true"));let s=o.querySelector("img");s&&r.avatar&&(s.src=r.avatar);let a=o.querySelector(".handle");a&&(a.textContent=r.handle),t.append(o)}this.#n.querySelector(".menu")?.replaceChildren(...t.children)}#c(t){this.#o=!0}#u(t){this.#o=!1,this.querySelector("input")?.focus();let n=t.target?.closest("button"),r=this.querySelector("input");!r||!n||(r.value=n.dataset.handle||"",this.#r=[],this.#t())}};function ee(){return localStorage.getItem("theme")||"system"}function le(e){return e==="dark"||e==="light"?e:window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}function _(){let e=ee(),n=le(e)==="dark";document.documentElement.classList.toggle("dark",n),document.documentElement.setAttribute("data-theme",n?"dark":"light"),ce(e)}function te(e){localStorage.setItem("theme",e),_(),ue()}function ce(e){let t={system:"sun-moon",light:"sun",dark:"moon"};document.querySelectorAll("[data-theme-icon] use").forEach(n=>{n.setAttribute("href",`/icons.svg#${t[e]||"sun-moon"}`)}),document.querySelectorAll(".theme-option").forEach(n=>{let r=n.dataset.value===e,o=n.querySelector(".theme-check");o&&(o.style.visibility=r?"visible":"hidden")})}function ue(){document.querySelectorAll("[data-theme-toggle]").forEach(e=>{let t=e.closest("details");t&&t.removeAttribute("open")})}window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>{ee()==="system"&&_()});function fe(){let e=document.querySelector(".nav-search-wrapper"),t=document.getElementById("nav-search-input");!e||!t||(e.classList.toggle("expanded"),e.classList.contains("expanded")&&t.focus())}function V(){let e=document.querySelector(".nav-search-wrapper");e&&e.classList.remove("expanded")}document.addEventListener("DOMContentLoaded",()=>{let e=document.querySelector(".nav-search-wrapper"),t=document.getElementById("nav-search-input");!e||!t||(document.addEventListener("keydown",n=>{if(n.key==="Escape"&&e.classList.contains("expanded")&&V(),n.key==="/"&&!e.classList.contains("expanded")){let r=n.target.tagName;if(r==="INPUT"||r==="TEXTAREA"||n.target.isContentEditable)return;n.preventDefault(),e.classList.add("expanded"),t.focus()}}),document.addEventListener("click",n=>{e.classList.contains("expanded")&&!e.contains(n.target)&&V()}))});function ne(e,t){!t&&typeof event<"u"&&(t=event.target.closest("button")),navigator.clipboard.writeText(e).then(()=>{if(!t)return;let n=t.innerHTML;t.innerHTML='<svg class="icon size-4" aria-hidden="true"><use href="/icons.svg#check"></use></svg> Copied!',setTimeout(()=>{t.innerHTML=n},2e3)}).catch(n=>{console.error("Failed to copy:",n)})}document.addEventListener("DOMContentLoaded",()=>{document.addEventListener("click",e=>{let t=e.target.closest("button[data-cmd]");if(t){ne(t.getAttribute("data-cmd"),t);return}if(e.target.closest("a, button, input, .cmd"))return;let n=e.target.closest("[data-href]");n&&(window.location=n.getAttribute("data-href"))})});function de(e){let t=Math.floor((new Date-new Date(e))/1e3),n={year:31536e3,month:2592e3,week:604800,day:86400,hour:3600,minute:60,second:1};for(let[r,o]of Object.entries(n)){let i=Math.floor(t/o);if(i>=1)return i===1?`1 ${r} ago`:`${i} ${r}s ago`}return"just now"}function j(){document.querySelectorAll("time[datetime]").forEach(e=>{let t=e.getAttribute("datetime");if(t&&!e.dataset.noUpdate){let n=de(t);e.textContent!==n&&(e.textContent=n)}})}document.addEventListener("DOMContentLoaded",()=>{j(),_(),document.querySelectorAll("[data-theme-menu]").forEach(e=>{e.querySelectorAll(".theme-option").forEach(t=>{t.addEventListener("click",()=>{te(t.dataset.value)})})}),document.addEventListener("click",e=>{let t=e.target.closest("details.dropdown");document.querySelectorAll("details.dropdown[open]").forEach(n=>{n!==t&&n.removeAttribute("open")})})});document.addEventListener("htmx:afterSwap",j);setInterval(j,6e4);function he(){let e=document.getElementById("show-offline-toggle"),t=document.querySelector(".manifests-list");!e||!t||(localStorage.setItem("showOfflineManifests",e.checked),e.checked?t.classList.add("show-offline"):t.classList.remove("show-offline"))}document.addEventListener("DOMContentLoaded",()=>{let e=document.getElementById("show-offline-toggle");if(!e)return;let t=localStorage.getItem("showOfflineManifests")==="true";e.checked=t;let n=document.querySelector(".manifests-list");n&&(t?n.classList.add("show-offline"):n.classList.remove("show-offline"))});async function me(e,t,n){try{let r=await fetch("/api/manifests",{method:"DELETE",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({repo:e,digest:t,confirm:!1})});if(r.status===409){let o=await r.json();ge(e,t,n,o.tags)}else if(r.ok)re(n);else{let o=await r.text();alert(`Failed to delete manifest: ${o}`)}}catch(r){console.error("Error deleting manifest:",r),alert(`Error deleting manifest: ${r.message}`)}}function ge(e,t,n,r){let o=document.getElementById("manifest-delete-modal"),i=document.getElementById("manifest-delete-tags"),s=document.getElementById("confirm-manifest-delete-btn");i.innerHTML="",r.forEach(a=>{let l=document.createElement("li");l.textContent=a,i.appendChild(l)}),s.onclick=()=>pe(e,t,n),o.style.display="flex"}function W(){let e=document.getElementById("manifest-delete-modal");e.style.display="none"}async function pe(e,t,n){let r=document.getElementById("confirm-manifest-delete-btn"),o=r.textContent;try{r.disabled=!0,r.textContent="Deleting...";let i=await fetch("/api/manifests",{method:"DELETE",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({repo:e,digest:t,confirm:!0})});if(i.ok)W(),re(n),location.reload();else{let s=await i.text();alert(`Failed to delete manifest: ${s}`),r.disabled=!1,r.textContent=o}}catch(i){console.error("Error deleting manifest:",i),alert(`Error deleting manifest: ${i.message}`),r.disabled=!1,r.textContent=o}}async function Ee(e){let t=document.getElementById("confirm-untagged-delete-btn"),n=t.textContent;try{t.disabled=!0,t.textContent="Deleting...";let r=await fetch("/api/manifests/untagged",{method:"DELETE",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({repo:e})}),o=await r.json();r.ok?(document.getElementById("untagged-delete-modal").close(),I(`Deleted ${o.deleted} untagged manifest(s)`,"success"),o.deleted>0&&location.reload(),t.disabled=!1,t.textContent=n):(alert(`Failed to delete untagged manifests: ${o.error||"Unknown error"}`),t.disabled=!1,t.textContent=n)}catch(r){console.error("Error deleting untagged manifests:",r),alert(`Error: ${r.message}`),t.disabled=!1,t.textContent=n}}function re(e){let t=document.getElementById(`manifest-${e}`);t&&t.remove()}document.addEventListener("DOMContentLoaded",()=>{let e=document.getElementById("manifest-delete-modal");e&&e.addEventListener("click",t=>{t.target===e&&W()})});async function ye(e,t){let n=document.getElementById("vuln-detail-modal"),r=document.getElementById("vuln-modal-body");if(!(!n||!r)){r.innerHTML='<div class="flex justify-center py-8"><span class="loading loading-spinner loading-lg"></span></div>',n.showModal();try{let o=await fetch(`/api/vuln-details?digest=${encodeURIComponent(e)}&holdEndpoint=${encodeURIComponent(t)}`);r.innerHTML=await o.text()}catch{r.innerHTML='<p class="text-error">Failed to load vulnerability details</p>'}}}var U=class{constructor(t){this.input=t,this.typeahead=t.closest("actor-typeahead"),this.dropdown=null,this.currentFocus=-1,this.typeaheadClosed=!1,this.init()}init(){this.createDropdown(),this.input.addEventListener("focus",()=>this.handleFocus()),this.input.addEventListener("input",()=>this.handleInput()),this.input.addEventListener("keydown",t=>this.handleKeydown(t)),document.addEventListener("click",t=>{!this.input.contains(t.target)&&!this.dropdown.contains(t.target)&&this.hideDropdown()})}createDropdown(){this.dropdown=document.createElement("div"),this.dropdown.className="recent-accounts-dropdown",this.dropdown.style.display="none",this.typeahead?this.typeahead.insertAdjacentElement("afterend",this.dropdown):this.input.insertAdjacentElement("afterend",this.dropdown)}handleFocus(){this.input.value.trim().length<1&&this.showRecentAccounts()}handleInput(){this.input.value.trim().length>=1&&this.hideDropdown(),this.typeaheadClosed=!1}showRecentAccounts(){let t=this.getRecentAccounts();if(t.length===0){this.hideDropdown();return}this.dropdown.innerHTML="",this.currentFocus=-1;let n=document.createElement("div");n.className="recent-accounts-header",n.textContent="Recent accounts",this.dropdown.appendChild(n),t.forEach((r,o)=>{let i=document.createElement("div");i.className="recent-accounts-item",i.dataset.index=o,i.dataset.handle=r,i.textContent=r,i.addEventListener("click",()=>this.selectItem(r)),this.dropdown.appendChild(i)}),this.dropdown.style.display="block"}selectItem(t){this.input.value=t,this.hideDropdown(),this.input.focus()}hideDropdown(){this.dropdown.style.display="none",this.currentFocus=-1}handleKeydown(t){if(t.key==="Enter"&&this.input.value.trim().length>=2&&(this.typeaheadClosed=!0),t.key==="Tab"&&this.input.value.trim().length>=2&&!this.typeaheadClosed){t.preventDefault();let o=t.shiftKey?"ArrowUp":"ArrowDown",i=new KeyboardEvent("keydown",{key:o,bubbles:!0,cancelable:!0});this.typeahead.dispatchEvent(i);return}if(this.dropdown.style.display==="none")return;let n=this.dropdown.querySelectorAll(".recent-accounts-item");t.key==="ArrowDown"?(t.preventDefault(),this.currentFocus++,this.currentFocus>=n.length&&(this.currentFocus=0),this.updateFocus(n)):t.key==="ArrowUp"?(t.preventDefault(),this.currentFocus--,this.currentFocus<0&&(this.currentFocus=n.length-1),this.updateFocus(n)):t.key==="Enter"&&this.currentFocus>-1&&n[this.currentFocus]?(t.preventDefault(),this.selectItem(n[this.currentFocus].dataset.handle)):t.key==="Escape"&&this.hideDropdown()}updateFocus(t){t.forEach((n,r)=>{n.classList.toggle("focused",r===this.currentFocus)})}getRecentAccounts(){try{let t=localStorage.getItem("atcr_recent_handles");return t?JSON.parse(t):[]}catch{return[]}}saveRecentAccount(t){if(t)try{let n=this.getRecentAccounts();n=n.filter(r=>r!==t),n.unshift(t),n=n.slice(0,5),localStorage.setItem("atcr_recent_handles",JSON.stringify(n))}catch(n){console.error("Failed to save recent account:",n)}}};document.addEventListener("DOMContentLoaded",()=>{let e=document.getElementById("login-form"),t=document.getElementById("handle");e&&t&&new U(t)});document.addEventListener("DOMContentLoaded",()=>{let e=document.cookie.split("; ").find(n=>n.startsWith("atcr_login_handle="));if(!e)return;let t=decodeURIComponent(e.split("=")[1]);if(t){try{let n="atcr_recent_handles",r=JSON.parse(localStorage.getItem(n)||"[]");r=r.filter(o=>o!==t),r.unshift(t),r=r.slice(0,5),localStorage.setItem(n,JSON.stringify(r))}catch(n){console.error("Failed to save recent account:",n)}document.cookie="atcr_login_handle=; path=/; max-age=0"}});function Z(){let e=document.getElementById("featured-carousel"),t=document.getElementById("carousel-prev"),n=document.getElementById("carousel-next");if(!e)return;let r=Array.from(e.querySelectorAll(".carousel-item"));if(r.length===0)return;let o=null,i=5e3,s=0,a=0,l=0;function c(){let b=r[0];if(!b)return;let E=getComputedStyle(e),g=parseFloat(E.gap)||24;s=b.offsetWidth+g,a=e.offsetWidth,l=e.scrollWidth}requestAnimationFrame(()=>{requestAnimationFrame(()=>{c(),m()})});let h;window.addEventListener("resize",()=>{clearTimeout(h),h=setTimeout(c,150)});function u(){return s||c(),s}function f(){return(!a||!s)&&c(),Math.round(a/s)||1}function v(){return(!l||!a)&&c(),l-a}function d(){let b=u(),E=v(),g=e.scrollLeft;g>=E-10?e.scrollTo({left:0,behavior:"smooth"}):e.scrollTo({left:g+b,behavior:"smooth"})}function y(){let b=u(),E=v(),g=e.scrollLeft;g<=10?e.scrollTo({left:E,behavior:"smooth"}):e.scrollTo({left:g-b,behavior:"smooth"})}t&&t.addEventListener("click",()=>{x(),y(),m()}),n&&n.addEventListener("click",()=>{x(),d(),m()});function m(){o||r.length<=f()||(o=setInterval(d,i))}function x(){o&&(clearInterval(o),o=null)}e.addEventListener("mouseenter",x),e.addEventListener("mouseleave",m)}document.addEventListener("DOMContentLoaded",()=>{"requestIdleCallback"in window?requestIdleCallback(Z,{timeout:2e3}):setTimeout(Z,100)});function I(e,t){let n=document.getElementById("toast-container");n||(n=document.createElement("div"),n.id="toast-container",n.className="toast toast-end toast-bottom z-50",document.body.appendChild(n));let r=t==="error"?"alert-error":"alert-success",o=document.createElement("div");o.className=`alert ${r} shadow-lg transition-opacity duration-300`,o.innerHTML=`<span>${e}</span>`,n.appendChild(o),setTimeout(()=>{o.style.opacity="0",setTimeout(()=>o.remove(),300)},3e3)}async function ve(e){try{let t=await fetch(`/api/webhooks/${e}/test`,{method:"POST",credentials:"include"}),n=await t.text();n.includes('class="success"')||t.ok&&!n.includes('class="error"')?I("Test webhook delivered successfully!","success"):I("Test delivery failed \u2014 check the webhook URL","error")}catch{I("Failed to reach server","error")}}window.setTheme=te;window.toggleSearch=fe;window.closeSearch=V;window.copyToClipboard=ne;window.toggleOfflineManifests=he;window.deleteManifest=me;window.deleteUntaggedManifests=Ee;window.closeManifestDeleteModal=W;window.openVulnDetails=ye;window.showToast=I;window.testWebhook=ve;window.htmx=q;
+2
pkg/appview/routes/routes.go
··· 158 158 r.Get("/settings", (&uihandlers.SettingsHandler{BaseUIHandler: base}).ServeHTTP) 159 159 r.Get("/api/storage", (&uihandlers.StorageHandler{BaseUIHandler: base}).ServeHTTP) 160 160 r.Post("/api/profile/default-hold", (&uihandlers.UpdateDefaultHoldHandler{BaseUIHandler: base}).ServeHTTP) 161 + r.Post("/api/profile/auto-remove-untagged", (&uihandlers.UpdateAutoRemoveUntaggedHandler{BaseUIHandler: base}).ServeHTTP) 161 162 162 163 // Subscription management 163 164 r.Get("/settings/subscription/checkout", (&uihandlers.SubscriptionCheckoutHandler{BaseUIHandler: base}).ServeHTTP) ··· 165 166 166 167 r.Delete("/api/tags", (&uihandlers.DeleteTagHandler{BaseUIHandler: base}).ServeHTTP) 167 168 r.Delete("/api/manifests", (&uihandlers.DeleteManifestHandler{BaseUIHandler: base}).ServeHTTP) 169 + r.Delete("/api/manifests/untagged", (&uihandlers.DeleteUntaggedManifestsHandler{BaseUIHandler: base}).ServeHTTP) 168 170 r.Post("/api/avatar", (&uihandlers.UploadAvatarHandler{BaseUIHandler: base}).ServeHTTP) 169 171 170 172 // Webhook management
+2 -1
pkg/appview/server.go
··· 231 231 // Set global refresher for middleware 232 232 middleware.SetGlobalRefresher(s.Refresher) 233 233 234 - // Set global database for hold DID lookups 234 + // Set global database for hold DID lookups and manifest reference checks 235 235 holdDIDDB := db.NewHoldDIDDB(s.Database) 236 236 middleware.SetGlobalDatabase(holdDIDDB) 237 + middleware.SetGlobalManifestRefChecker(holdDIDDB) 237 238 238 239 // Create RemoteHoldAuthorizer for hold authorization with caching 239 240 s.HoldAuthorizer = auth.NewRemoteHoldAuthorizer(s.Database, testMode)
+40
pkg/appview/src/js/app.js
··· 352 352 } 353 353 } 354 354 355 + // Delete all untagged manifests in a repository 356 + async function deleteUntaggedManifests(repository) { 357 + const confirmBtn = document.getElementById('confirm-untagged-delete-btn'); 358 + const originalText = confirmBtn.textContent; 359 + 360 + try { 361 + confirmBtn.disabled = true; 362 + confirmBtn.textContent = 'Deleting...'; 363 + 364 + const response = await fetch('/api/manifests/untagged', { 365 + method: 'DELETE', 366 + credentials: 'include', 367 + headers: { 'Content-Type': 'application/json' }, 368 + body: JSON.stringify({ repo: repository }), 369 + }); 370 + 371 + const data = await response.json(); 372 + 373 + if (response.ok) { 374 + document.getElementById('untagged-delete-modal').close(); 375 + showToast(`Deleted ${data.deleted} untagged manifest(s)`, 'success'); 376 + if (data.deleted > 0) { 377 + location.reload(); 378 + } 379 + confirmBtn.disabled = false; 380 + confirmBtn.textContent = originalText; 381 + } else { 382 + alert(`Failed to delete untagged manifests: ${data.error || 'Unknown error'}`); 383 + confirmBtn.disabled = false; 384 + confirmBtn.textContent = originalText; 385 + } 386 + } catch (err) { 387 + console.error('Error deleting untagged manifests:', err); 388 + alert(`Error: ${err.message}`); 389 + confirmBtn.disabled = false; 390 + confirmBtn.textContent = originalText; 391 + } 392 + } 393 + 355 394 // Remove a manifest element from the DOM 356 395 function removeManifestElement(sanitizedId) { 357 396 const element = document.getElementById(`manifest-${sanitizedId}`); ··· 758 797 window.copyToClipboard = copyToClipboard; 759 798 window.toggleOfflineManifests = toggleOfflineManifests; 760 799 window.deleteManifest = deleteManifest; 800 + window.deleteUntaggedManifests = deleteUntaggedManifests; 761 801 window.closeManifestDeleteModal = closeManifestDeleteModal; 762 802 window.openVulnDetails = openVulnDetails; 763 803 window.showToast = showToast;
+16 -5
pkg/appview/storage/context.go
··· 15 15 DispatchForPush(ctx context.Context, event PushWebhookEvent) 16 16 } 17 17 18 + // ManifestReferenceChecker checks if a manifest digest is referenced as a child 19 + // of a manifest list (multi-arch image). Used to protect manifest list children 20 + // from auto-removal when their parent list is still tagged. 21 + type ManifestReferenceChecker interface { 22 + IsManifestReferenced(did, digest string) (bool, error) 23 + } 24 + 18 25 // PushWebhookEvent contains the data needed to dispatch a push webhook. 19 26 type PushWebhookEvent struct { 20 27 OwnerDID string ··· 53 60 PullerDID string // Puller's DID - who is making the request (from JWT Subject) 54 61 PullerPDSEndpoint string // Puller's PDS endpoint URL 55 62 63 + // Per-request user preferences 64 + AutoRemoveUntagged bool // Whether to auto-delete untagged manifests on tag overwrite 65 + 56 66 // Shared services (same for all requests) 57 - Database HoldDIDLookup // Database for hold DID lookups 58 - Authorizer auth.HoldAuthorizer // Hold access authorization 59 - Refresher *oauth.Refresher // OAuth session manager 60 - ReadmeFetcher *readme.Fetcher // README fetcher for repo pages 61 - WebhookDispatcher PushWebhookDispatcher // Push webhook dispatcher (nil if not configured) 67 + Database HoldDIDLookup // Database for hold DID lookups 68 + Authorizer auth.HoldAuthorizer // Hold access authorization 69 + Refresher *oauth.Refresher // OAuth session manager 70 + ReadmeFetcher *readme.Fetcher // README fetcher for repo pages 71 + WebhookDispatcher PushWebhookDispatcher // Push webhook dispatcher (nil if not configured) 72 + ManifestRefChecker ManifestReferenceChecker // Checks if digest is a manifest list child (nil-safe) 62 73 }
+91 -1
pkg/appview/storage/manifest_store.go
··· 210 210 211 211 // Also handle tag if specified 212 212 var tag string 213 + var oldDigest string // Track previous digest for auto-remove-untagged cleanup 213 214 for _, option := range options { 214 215 if tagOpt, ok := option.(distribution.WithTagOption); ok { 215 216 tag = tagOpt.Tag 216 - tagRecord := atproto.NewTagRecord(s.ctx.ATProtoClient.DID(), s.ctx.Repository, tag, dgst.String()) 217 217 tagRKey := atproto.RepositoryTagToRKey(s.ctx.Repository, tag) 218 + 219 + // Before overwriting, capture the old digest so we can clean it up 220 + if s.ctx.AutoRemoveUntagged { 221 + if oldRec, getErr := s.ctx.ATProtoClient.GetRecord(ctx, atproto.TagCollection, tagRKey); getErr == nil { 222 + var oldTag atproto.TagRecord 223 + if json.Unmarshal(oldRec.Value, &oldTag) == nil { 224 + if d, dErr := oldTag.GetManifestDigest(); dErr == nil { 225 + oldDigest = d 226 + } 227 + } 228 + } 229 + } 230 + 231 + tagRecord := atproto.NewTagRecord(s.ctx.ATProtoClient.DID(), s.ctx.Repository, tag, dgst.String()) 218 232 _, err = s.ctx.ATProtoClient.PutRecord(ctx, atproto.TagCollection, tagRKey, tagRecord) 219 233 if err != nil { 220 234 return "", fmt.Errorf("failed to store tag in ATProto: %w", err) 221 235 } 222 236 } 237 + } 238 + 239 + // Auto-remove untagged manifests: if the tag pointed to a different digest before, 240 + // check if the old manifest is now untagged and clean it up 241 + if s.ctx.AutoRemoveUntagged && oldDigest != "" && oldDigest != dgst.String() { 242 + go s.cleanupUntaggedManifest(oldDigest) 223 243 } 224 244 225 245 // Notify hold about manifest push (for layer tracking, Bluesky posts, and stats) ··· 697 717 // Unknown or unsupported type - reject 698 718 return "" 699 719 } 720 + 721 + // cleanupUntaggedManifest checks if a manifest digest is now untagged and deletes it. 722 + // Runs asynchronously after a tag overwrite. Protects manifest list children. 723 + func (s *ManifestStore) cleanupUntaggedManifest(oldDigest string) { 724 + defer func() { 725 + if r := recover(); r != nil { 726 + slog.Error("Panic in cleanupUntaggedManifest", "panic", r) 727 + } 728 + }() 729 + 730 + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 731 + defer cancel() 732 + 733 + // Re-check: list all tags for this repository and see if any still point to the old digest 734 + records, err := s.ctx.ATProtoClient.ListRecords(ctx, atproto.TagCollection, 100) 735 + if err != nil { 736 + slog.Warn("Auto-remove: failed to list tags", "error", err) 737 + return 738 + } 739 + 740 + for _, rec := range records { 741 + var tagRecord atproto.TagRecord 742 + if json.Unmarshal(rec.Value, &tagRecord) != nil { 743 + continue 744 + } 745 + if tagRecord.Repository != s.ctx.Repository { 746 + continue 747 + } 748 + d, dErr := tagRecord.GetManifestDigest() 749 + if dErr == nil && d == oldDigest { 750 + // Still tagged by another tag — do not delete 751 + slog.Debug("Auto-remove: manifest still tagged, skipping", 752 + "digest", oldDigest, "tag", tagRecord.Tag) 753 + return 754 + } 755 + } 756 + 757 + // Check if this digest is a child of a manifest list (multi-arch) 758 + if s.ctx.ManifestRefChecker != nil { 759 + referenced, err := s.ctx.ManifestRefChecker.IsManifestReferenced(s.ctx.DID, oldDigest) 760 + if err != nil { 761 + slog.Warn("Auto-remove: failed to check manifest references", "digest", oldDigest, "error", err) 762 + return 763 + } 764 + if referenced { 765 + slog.Debug("Auto-remove: manifest is a manifest list child, skipping", 766 + "digest", oldDigest) 767 + return 768 + } 769 + } 770 + 771 + // Safe to delete — manifest is untagged and not referenced by any manifest list 772 + dgst, err := digest.Parse(oldDigest) 773 + if err != nil { 774 + slog.Warn("Auto-remove: invalid digest", "digest", oldDigest, "error", err) 775 + return 776 + } 777 + 778 + rkey := digestToRKey(dgst) 779 + if err := s.ctx.ATProtoClient.DeleteRecord(ctx, atproto.ManifestCollection, rkey); err != nil { 780 + slog.Warn("Auto-remove: failed to delete untagged manifest", "digest", oldDigest, "error", err) 781 + return 782 + } 783 + 784 + slog.Info("Auto-removed untagged manifest", 785 + "component", "manifest-store", 786 + "digest", oldDigest, 787 + "repository", s.ctx.Repository, 788 + "did", s.ctx.DID) 789 + }
+30 -4
pkg/appview/templates/pages/repository.html
··· 200 200 <div class="card bg-base-100 shadow-sm p-6 space-y-4"> 201 201 <div class="flex flex-wrap justify-between items-center gap-4"> 202 202 <h2 class="text-xl font-semibold">Manifests</h2> 203 - <label class="flex items-center gap-2 text-sm cursor-pointer"> 204 - <input type="checkbox" class="checkbox checkbox-sm" id="show-offline-toggle" onchange="toggleOfflineManifests()"> 205 - <span>Show offline images</span> 206 - </label> 203 + <div class="flex items-center gap-4"> 204 + {{ if $.IsOwner }} 205 + <button class="btn btn-ghost btn-sm text-error" 206 + onclick="document.getElementById('untagged-delete-modal').showModal()" 207 + aria-label="Delete all untagged manifests"> 208 + {{ icon "trash-2" "size-4" }} Delete untagged 209 + </button> 210 + {{ end }} 211 + <label class="flex items-center gap-2 text-sm cursor-pointer"> 212 + <input type="checkbox" class="checkbox checkbox-sm" id="show-offline-toggle" onchange="toggleOfflineManifests()"> 213 + <span>Show offline images</span> 214 + </label> 215 + </div> 207 216 </div> 208 217 {{ if .Manifests }} 209 218 <div class="space-y-4 manifests-list"> ··· 311 320 <form method="dialog" class="modal-backdrop"> 312 321 <button onclick="closeManifestDeleteModal()">close</button> 313 322 </form> 323 + </dialog> 324 + 325 + <!-- Untagged Manifests Delete Confirmation Modal --> 326 + <dialog id="untagged-delete-modal" class="modal"> 327 + <div class="modal-box"> 328 + <h3 class="text-lg font-bold">Delete Untagged Manifests</h3> 329 + <p class="py-2">This will delete all untagged manifests in this repository.</p> 330 + <p class="font-bold py-2 text-error">This action cannot be undone.</p> 331 + <div class="modal-action"> 332 + <form method="dialog"><button class="btn">Cancel</button></form> 333 + <button class="btn btn-error" id="confirm-untagged-delete-btn" 334 + onclick="deleteUntaggedManifests('{{ .Repository.Name }}')"> 335 + Delete All Untagged 336 + </button> 337 + </div> 338 + </div> 339 + <form method="dialog" class="modal-backdrop"><button>close</button></form> 314 340 </dialog> 315 341 316 342 <!-- Vulnerability Details Modal -->
+19
pkg/appview/templates/pages/settings.html
··· 80 80 No holds configured. Push an image to get started. 81 81 </div> 82 82 {{ end }} 83 + 84 + <!-- Storage Preferences --> 85 + <section class="card bg-base-200 shadow-sm p-6 space-y-4"> 86 + <h2 class="text-xl font-semibold">Storage Preferences</h2> 87 + <label class="flex items-start gap-3 cursor-pointer"> 88 + <input type="checkbox" class="toggle toggle-primary mt-0.5" 89 + hx-post="/api/profile/auto-remove-untagged" 90 + hx-trigger="change" 91 + hx-swap="none" 92 + {{ if .Profile.AutoRemoveUntagged }}checked{{ end }}> 93 + <div> 94 + <span class="font-medium">Automatically remove untagged manifests</span> 95 + <p class="text-sm text-base-content/60 mt-1"> 96 + When a tag is overwritten, the old manifest and its layers are cleaned up. 97 + Multi-arch child manifests are preserved. 98 + </p> 99 + </div> 100 + </label> 101 + </section> 83 102 </div> 84 103 85 104 <!-- DEVICES TAB -->
+5
pkg/atproto/lexicon.go
··· 335 335 // If null/empty, user has opted out of defaults 336 336 DefaultHold string `json:"defaultHold,omitempty"` 337 337 338 + // AutoRemoveUntagged controls whether untagged manifests are automatically 339 + // cleaned up. When true, manifests that lose all tags (e.g., after a tag 340 + // overwrite) are deleted from PDS, and their layers are cleaned up by hold GC. 341 + AutoRemoveUntagged bool `json:"autoRemoveUntagged,omitempty"` 342 + 338 343 // CreatedAt timestamp 339 344 CreatedAt time.Time `json:"createdAt"` 340 345
+1
pkg/atproto/relays.go
··· 35 35 {Name: "Feeds Blue", URL: "https://relay.feeds.blue"}, 36 36 {Name: "Waow", URL: "https://relay.waow.tech"}, 37 37 {Name: "Zlay", URL: "https://zlay.waow.tech"}, 38 + {Name: "Bassh", URL: "https://relay.bas.sh"}, 38 39 } 39 40 40 41 // RelayHTTPError indicates the relay responded with a non-200 status code.
+168 -8
pkg/hold/gc/gc.go
··· 578 578 result.usersChecked++ 579 579 gc.setProgress("manifests", fmt.Sprintf("Fetching manifests (%d/%d users)", result.usersChecked, totalUsers), gc.operationType) 580 580 581 - manifests, err := gc.fetchUserManifests(ctx, did) 581 + // Resolve PDS endpoint (needed for both manifests and optional profile/tag fetches) 582 + pdsEndpoint, err := atproto.ResolveDIDToPDS(ctx, did) 583 + if err != nil { 584 + gc.logger.Warn("Failed to resolve PDS for user, treating their records as referenced", 585 + "did", did, "error", err) 586 + continue 587 + } 588 + 589 + manifests, err := gc.fetchUserManifestsFromEndpoint(ctx, did, pdsEndpoint, gc.pds.DID()) 582 590 if err != nil { 583 591 gc.logger.Warn("Failed to fetch manifests for user, treating their records as referenced", 584 592 "did", did, "error", err) 585 593 continue 594 + } 595 + 596 + // If user opted into auto-remove-untagged, filter out untagged manifests 597 + // so their layers are treated as orphaned and cleaned up 598 + if profile, _ := gc.fetchUserProfile(ctx, pdsEndpoint, did); profile != nil && profile.AutoRemoveUntagged { 599 + taggedDigests, tagErr := gc.fetchUserTags(ctx, pdsEndpoint, did) 600 + if tagErr != nil { 601 + gc.logger.Warn("Failed to fetch tags for auto-remove, keeping all manifests referenced", 602 + "did", did, "error", tagErr) 603 + } else { 604 + before := len(manifests) 605 + manifests = gc.filterUntaggedManifests(manifests, taggedDigests) 606 + if filtered := before - len(manifests); filtered > 0 { 607 + gc.logger.Info("Filtered untagged manifests for auto-remove user", 608 + "did", did, "filtered", filtered, "remaining", len(manifests)) 609 + } 610 + } 586 611 } 587 612 588 613 fetchedUsers[did] = true ··· 835 860 return dids, nil 836 861 } 837 862 838 - // fetchUserManifests fetches all io.atcr.manifest records for a user from their PDS, 839 - // filtered to manifests that reference this hold. 840 - func (gc *GarbageCollector) fetchUserManifests(ctx context.Context, userDID string) ([]*manifestInfo, error) { 841 - pdsEndpoint, err := atproto.ResolveDIDToPDS(ctx, userDID) 863 + // fetchUserProfile fetches the sailor profile from a user's PDS. 864 + // Returns nil without error if the profile doesn't exist. 865 + func (gc *GarbageCollector) fetchUserProfile(ctx context.Context, pdsEndpoint, userDID string) (*atproto.SailorProfileRecord, error) { 866 + client := &http.Client{Timeout: 10 * time.Second} 867 + 868 + reqURL := fmt.Sprintf("%s/xrpc/com.atproto.repo.getRecord?repo=%s&collection=%s&rkey=self", 869 + pdsEndpoint, userDID, atproto.SailorProfileCollection) 870 + 871 + req, err := http.NewRequestWithContext(ctx, "GET", reqURL, nil) 872 + if err != nil { 873 + return nil, fmt.Errorf("create request: %w", err) 874 + } 875 + 876 + resp, err := client.Do(req) 842 877 if err != nil { 843 - return nil, fmt.Errorf("resolve PDS for %s: %w", userDID, err) 878 + return nil, fmt.Errorf("http request: %w", err) 879 + } 880 + defer resp.Body.Close() 881 + 882 + if resp.StatusCode == http.StatusBadRequest || resp.StatusCode == http.StatusNotFound { 883 + return nil, nil // No profile 884 + } 885 + if resp.StatusCode != http.StatusOK { 886 + return nil, fmt.Errorf("getRecord returned status %d", resp.StatusCode) 887 + } 888 + 889 + var envelope struct { 890 + Value json.RawMessage `json:"value"` 891 + } 892 + if err := json.NewDecoder(resp.Body).Decode(&envelope); err != nil { 893 + return nil, fmt.Errorf("decode response: %w", err) 894 + } 895 + 896 + var profile atproto.SailorProfileRecord 897 + if err := json.Unmarshal(envelope.Value, &profile); err != nil { 898 + return nil, fmt.Errorf("unmarshal profile: %w", err) 899 + } 900 + 901 + return &profile, nil 902 + } 903 + 904 + // fetchUserTags fetches all tag records from a user's PDS. 905 + // Returns a map of digest → true for all tagged manifest digests. 906 + func (gc *GarbageCollector) fetchUserTags(ctx context.Context, pdsEndpoint, userDID string) (map[string]bool, error) { 907 + tagged := make(map[string]bool) 908 + cursor := "" 909 + client := &http.Client{Timeout: 30 * time.Second} 910 + 911 + for { 912 + reqURL := fmt.Sprintf("%s/xrpc/com.atproto.repo.listRecords?repo=%s&collection=%s&limit=100", 913 + pdsEndpoint, userDID, atproto.TagCollection) 914 + if cursor != "" { 915 + reqURL += "&cursor=" + cursor 916 + } 917 + 918 + req, err := http.NewRequestWithContext(ctx, "GET", reqURL, nil) 919 + if err != nil { 920 + return nil, fmt.Errorf("create request: %w", err) 921 + } 922 + 923 + resp, err := client.Do(req) 924 + if err != nil { 925 + return nil, fmt.Errorf("http request: %w", err) 926 + } 927 + 928 + if resp.StatusCode != http.StatusOK { 929 + resp.Body.Close() 930 + return nil, fmt.Errorf("listRecords returned status %d", resp.StatusCode) 931 + } 932 + 933 + var listResult struct { 934 + Records []struct { 935 + Value json.RawMessage `json:"value"` 936 + } `json:"records"` 937 + Cursor string `json:"cursor,omitempty"` 938 + } 939 + 940 + if err := json.NewDecoder(resp.Body).Decode(&listResult); err != nil { 941 + resp.Body.Close() 942 + return nil, fmt.Errorf("decode response: %w", err) 943 + } 944 + resp.Body.Close() 945 + 946 + for _, rec := range listResult.Records { 947 + var tag atproto.TagRecord 948 + if err := json.Unmarshal(rec.Value, &tag); err != nil { 949 + continue 950 + } 951 + if d, err := tag.GetManifestDigest(); err == nil { 952 + tagged[d] = true 953 + } 954 + } 955 + 956 + if listResult.Cursor == "" { 957 + break 958 + } 959 + cursor = listResult.Cursor 844 960 } 845 - return gc.fetchUserManifestsFromEndpoint(ctx, userDID, pdsEndpoint, gc.pds.DID()) 961 + 962 + return tagged, nil 963 + } 964 + 965 + // filterUntaggedManifests removes untagged manifests from the list, preserving 966 + // manifest list children that are referenced by tagged manifest lists. 967 + func (gc *GarbageCollector) filterUntaggedManifests(manifests []*manifestInfo, taggedDigests map[string]bool) []*manifestInfo { 968 + // Build a set of digests that are children of tagged manifest lists 969 + childDigests := make(map[string]bool) 970 + for _, m := range manifests { 971 + // Check if this manifest is a manifest list AND is tagged 972 + mDigest := extractDigestFromManifestURI(m.URI) 973 + if mDigest == "" || !taggedDigests[mDigest] { 974 + continue 975 + } 976 + // This is a tagged manifest list — protect its children 977 + for _, ref := range m.Record.Manifests { 978 + childDigests[ref.Digest] = true 979 + } 980 + } 981 + 982 + // Filter: keep manifests that are tagged, or are children of tagged manifest lists 983 + var kept []*manifestInfo 984 + for _, m := range manifests { 985 + mDigest := extractDigestFromManifestURI(m.URI) 986 + if mDigest == "" { 987 + kept = append(kept, m) 988 + continue 989 + } 990 + if taggedDigests[mDigest] || childDigests[mDigest] { 991 + kept = append(kept, m) 992 + } 993 + } 994 + return kept 995 + } 996 + 997 + // extractDigestFromManifestURI extracts the digest from a manifest AT-URI. 998 + // URI format: at://did:plc:xxx/io.atcr.manifest/{encoded_digest} 999 + // Returns the full digest (e.g., "sha256:abc123...") or empty string. 1000 + func extractDigestFromManifestURI(uri string) string { 1001 + parts := parseATURI(uri) 1002 + if parts == nil || parts.Collection != atproto.ManifestCollection { 1003 + return "" 1004 + } 1005 + // The rkey is the digest without the "sha256:" prefix 1006 + return "sha256:" + parts.Rkey 846 1007 } 847 1008 848 1009 // fetchUserManifestsFromEndpoint fetches manifests from a specific PDS endpoint. 849 - // Separated from fetchUserManifests for testability (avoids DID resolution). 850 1010 func (gc *GarbageCollector) fetchUserManifestsFromEndpoint(ctx context.Context, userDID, pdsEndpoint, holdDID string) ([]*manifestInfo, error) { 851 1011 var manifests []*manifestInfo 852 1012 cursor := ""
+300
pkg/hold/gc/gc_test.go
··· 451 451 } 452 452 } 453 453 454 + func TestExtractDigestFromManifestURI(t *testing.T) { 455 + tests := []struct { 456 + name string 457 + uri string 458 + expected string 459 + }{ 460 + { 461 + name: "valid manifest URI", 462 + uri: "at://did:plc:user1/io.atcr.manifest/abc123def456", 463 + expected: "sha256:abc123def456", 464 + }, 465 + { 466 + name: "wrong collection", 467 + uri: "at://did:plc:user1/io.atcr.tag/abc123def456", 468 + expected: "", 469 + }, 470 + { 471 + name: "invalid URI", 472 + uri: "not-an-at-uri", 473 + expected: "", 474 + }, 475 + { 476 + name: "empty URI", 477 + uri: "", 478 + expected: "", 479 + }, 480 + } 481 + 482 + for _, tt := range tests { 483 + t.Run(tt.name, func(t *testing.T) { 484 + got := extractDigestFromManifestURI(tt.uri) 485 + if got != tt.expected { 486 + t.Errorf("extractDigestFromManifestURI(%q) = %q, want %q", tt.uri, got, tt.expected) 487 + } 488 + }) 489 + } 490 + } 491 + 492 + func TestFilterUntaggedManifests(t *testing.T) { 493 + gc := &GarbageCollector{logger: newTestLogger()} 494 + 495 + t.Run("keeps tagged manifests, removes untagged", func(t *testing.T) { 496 + manifests := []*manifestInfo{ 497 + {URI: "at://did:plc:u1/io.atcr.manifest/aaa111", Record: &atproto.ManifestRecord{}}, 498 + {URI: "at://did:plc:u1/io.atcr.manifest/bbb222", Record: &atproto.ManifestRecord{}}, 499 + {URI: "at://did:plc:u1/io.atcr.manifest/ccc333", Record: &atproto.ManifestRecord{}}, 500 + } 501 + tagged := map[string]bool{ 502 + "sha256:aaa111": true, 503 + "sha256:ccc333": true, 504 + } 505 + 506 + result := gc.filterUntaggedManifests(manifests, tagged) 507 + if len(result) != 2 { 508 + t.Fatalf("expected 2 kept, got %d", len(result)) 509 + } 510 + if result[0].URI != manifests[0].URI || result[1].URI != manifests[2].URI { 511 + t.Errorf("unexpected manifests kept: %v, %v", result[0].URI, result[1].URI) 512 + } 513 + }) 514 + 515 + t.Run("preserves manifest list children", func(t *testing.T) { 516 + // Manifest list (tagged) references child (untagged) 517 + manifests := []*manifestInfo{ 518 + { 519 + URI: "at://did:plc:u1/io.atcr.manifest/index111", 520 + Record: &atproto.ManifestRecord{ 521 + Manifests: []atproto.ManifestReference{ 522 + {Digest: "sha256:child222", MediaType: "application/vnd.oci.image.manifest.v1+json"}, 523 + {Digest: "sha256:child333", MediaType: "application/vnd.oci.image.manifest.v1+json"}, 524 + }, 525 + }, 526 + }, 527 + {URI: "at://did:plc:u1/io.atcr.manifest/child222", Record: &atproto.ManifestRecord{}}, 528 + {URI: "at://did:plc:u1/io.atcr.manifest/child333", Record: &atproto.ManifestRecord{}}, 529 + {URI: "at://did:plc:u1/io.atcr.manifest/orphan444", Record: &atproto.ManifestRecord{}}, 530 + } 531 + tagged := map[string]bool{ 532 + "sha256:index111": true, // Only the index is tagged 533 + } 534 + 535 + result := gc.filterUntaggedManifests(manifests, tagged) 536 + if len(result) != 3 { 537 + t.Fatalf("expected 3 kept (index + 2 children), got %d", len(result)) 538 + } 539 + // orphan444 should be filtered out 540 + for _, m := range result { 541 + if extractDigestFromManifestURI(m.URI) == "sha256:orphan444" { 542 + t.Error("orphan manifest should have been filtered out") 543 + } 544 + } 545 + }) 546 + 547 + t.Run("removes everything when nothing is tagged", func(t *testing.T) { 548 + manifests := []*manifestInfo{ 549 + {URI: "at://did:plc:u1/io.atcr.manifest/aaa111", Record: &atproto.ManifestRecord{}}, 550 + {URI: "at://did:plc:u1/io.atcr.manifest/bbb222", Record: &atproto.ManifestRecord{}}, 551 + } 552 + tagged := map[string]bool{} 553 + 554 + result := gc.filterUntaggedManifests(manifests, tagged) 555 + if len(result) != 0 { 556 + t.Fatalf("expected 0 kept, got %d", len(result)) 557 + } 558 + }) 559 + 560 + t.Run("keeps everything when all are tagged", func(t *testing.T) { 561 + manifests := []*manifestInfo{ 562 + {URI: "at://did:plc:u1/io.atcr.manifest/aaa111", Record: &atproto.ManifestRecord{}}, 563 + {URI: "at://did:plc:u1/io.atcr.manifest/bbb222", Record: &atproto.ManifestRecord{}}, 564 + } 565 + tagged := map[string]bool{ 566 + "sha256:aaa111": true, 567 + "sha256:bbb222": true, 568 + } 569 + 570 + result := gc.filterUntaggedManifests(manifests, tagged) 571 + if len(result) != 2 { 572 + t.Fatalf("expected 2 kept, got %d", len(result)) 573 + } 574 + }) 575 + } 576 + 577 + func TestFetchUserProfile(t *testing.T) { 578 + gc := &GarbageCollector{logger: newTestLogger()} 579 + 580 + t.Run("returns profile with autoRemoveUntagged", func(t *testing.T) { 581 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 582 + w.Header().Set("Content-Type", "application/json") 583 + json.NewEncoder(w).Encode(map[string]any{ 584 + "value": map[string]any{ 585 + "$type": "io.atcr.sailor.profile", 586 + "defaultHold": "did:web:hold.example.com", 587 + "autoRemoveUntagged": true, 588 + "createdAt": "2025-01-01T00:00:00Z", 589 + }, 590 + }) 591 + })) 592 + defer server.Close() 593 + 594 + profile, err := gc.fetchUserProfile(t.Context(), server.URL, "did:plc:test") 595 + if err != nil { 596 + t.Fatalf("unexpected error: %v", err) 597 + } 598 + if profile == nil { 599 + t.Fatal("expected non-nil profile") 600 + } 601 + if !profile.AutoRemoveUntagged { 602 + t.Error("expected AutoRemoveUntagged to be true") 603 + } 604 + if profile.DefaultHold != "did:web:hold.example.com" { 605 + t.Errorf("expected DefaultHold %q, got %q", "did:web:hold.example.com", profile.DefaultHold) 606 + } 607 + }) 608 + 609 + t.Run("returns nil for 404", func(t *testing.T) { 610 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 611 + w.WriteHeader(http.StatusNotFound) 612 + })) 613 + defer server.Close() 614 + 615 + profile, err := gc.fetchUserProfile(t.Context(), server.URL, "did:plc:test") 616 + if err != nil { 617 + t.Fatalf("unexpected error: %v", err) 618 + } 619 + if profile != nil { 620 + t.Error("expected nil profile for 404") 621 + } 622 + }) 623 + 624 + t.Run("returns error for server failure", func(t *testing.T) { 625 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 626 + w.WriteHeader(http.StatusInternalServerError) 627 + })) 628 + defer server.Close() 629 + 630 + _, err := gc.fetchUserProfile(t.Context(), server.URL, "did:plc:test") 631 + if err == nil { 632 + t.Fatal("expected error for 500 response") 633 + } 634 + }) 635 + } 636 + 637 + func TestFetchUserTags(t *testing.T) { 638 + gc := &GarbageCollector{logger: newTestLogger()} 639 + 640 + t.Run("returns tagged digests", func(t *testing.T) { 641 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 642 + w.Header().Set("Content-Type", "application/json") 643 + json.NewEncoder(w).Encode(map[string]any{ 644 + "records": []map[string]any{ 645 + { 646 + "uri": "at://did:plc:user1/io.atcr.tag/myapp_latest", 647 + "cid": "bafyrei1", 648 + "value": map[string]any{ 649 + "$type": "io.atcr.tag", 650 + "repository": "myapp", 651 + "tag": "latest", 652 + "manifest": "at://did:plc:user1/io.atcr.manifest/abc123", 653 + }, 654 + }, 655 + { 656 + "uri": "at://did:plc:user1/io.atcr.tag/myapp_v1", 657 + "cid": "bafyrei2", 658 + "value": map[string]any{ 659 + "$type": "io.atcr.tag", 660 + "repository": "myapp", 661 + "tag": "v1", 662 + "manifestDigest": "sha256:def456", 663 + }, 664 + }, 665 + }, 666 + }) 667 + })) 668 + defer server.Close() 669 + 670 + tagged, err := gc.fetchUserTags(t.Context(), server.URL, "did:plc:user1") 671 + if err != nil { 672 + t.Fatalf("unexpected error: %v", err) 673 + } 674 + if len(tagged) != 2 { 675 + t.Fatalf("expected 2 tagged digests, got %d", len(tagged)) 676 + } 677 + if !tagged["sha256:abc123"] { 678 + t.Error("expected sha256:abc123 to be tagged") 679 + } 680 + if !tagged["sha256:def456"] { 681 + t.Error("expected sha256:def456 to be tagged") 682 + } 683 + }) 684 + 685 + t.Run("handles pagination", func(t *testing.T) { 686 + callCount := 0 687 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 688 + callCount++ 689 + w.Header().Set("Content-Type", "application/json") 690 + if callCount == 1 { 691 + json.NewEncoder(w).Encode(map[string]any{ 692 + "records": []map[string]any{ 693 + { 694 + "uri": "at://did:plc:user1/io.atcr.tag/myapp_latest", 695 + "cid": "bafyrei1", 696 + "value": map[string]any{ 697 + "$type": "io.atcr.tag", 698 + "repository": "myapp", 699 + "tag": "latest", 700 + "manifestDigest": "sha256:aaa111", 701 + }, 702 + }, 703 + }, 704 + "cursor": "page2", 705 + }) 706 + } else { 707 + json.NewEncoder(w).Encode(map[string]any{ 708 + "records": []map[string]any{ 709 + { 710 + "uri": "at://did:plc:user1/io.atcr.tag/myapp_v2", 711 + "cid": "bafyrei2", 712 + "value": map[string]any{ 713 + "$type": "io.atcr.tag", 714 + "repository": "myapp", 715 + "tag": "v2", 716 + "manifestDigest": "sha256:bbb222", 717 + }, 718 + }, 719 + }, 720 + }) 721 + } 722 + })) 723 + defer server.Close() 724 + 725 + tagged, err := gc.fetchUserTags(t.Context(), server.URL, "did:plc:user1") 726 + if err != nil { 727 + t.Fatalf("unexpected error: %v", err) 728 + } 729 + if callCount != 2 { 730 + t.Errorf("expected 2 HTTP calls for pagination, got %d", callCount) 731 + } 732 + if len(tagged) != 2 { 733 + t.Fatalf("expected 2 tagged digests, got %d", len(tagged)) 734 + } 735 + }) 736 + 737 + t.Run("returns empty for no tags", func(t *testing.T) { 738 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 739 + w.Header().Set("Content-Type", "application/json") 740 + json.NewEncoder(w).Encode(map[string]any{"records": []any{}}) 741 + })) 742 + defer server.Close() 743 + 744 + tagged, err := gc.fetchUserTags(t.Context(), server.URL, "did:plc:user1") 745 + if err != nil { 746 + t.Fatalf("unexpected error: %v", err) 747 + } 748 + if len(tagged) != 0 { 749 + t.Errorf("expected 0 tagged digests, got %d", len(tagged)) 750 + } 751 + }) 752 + } 753 + 454 754 func newTestLogger() *slog.Logger { 455 755 return slog.Default().With("component", "gc-test") 456 756 }
+6 -2
themes/seamark/embed.go
··· 13 13 //go:embed public 14 14 var publicFS embed.FS 15 15 16 + //go:embed templates 17 + var templatesFS embed.FS 18 + 16 19 //go:embed theme.css 17 20 var themeCSS string 18 21 ··· 29 32 } 30 33 31 34 return &appview.BrandingOverrides{ 32 - PublicFS: prefixFS{sub: pubSub}, 33 - ExtraCSS: themeCSS, 35 + PublicFS: prefixFS{sub: pubSub}, 36 + TemplatesFS: templatesFS, 37 + ExtraCSS: themeCSS, 34 38 } 35 39 } 36 40
themes/seamark/public/apple-touch-icon.png

This is a binary file and will not be displayed.

themes/seamark/public/favicon-96x96.png

This is a binary file and will not be displayed.

themes/seamark/public/favicon.ico

This is a binary file and will not be displayed.

+1 -51
themes/seamark/public/favicon.svg
··· 1 - <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 - <!-- Created with Inkscape (http://www.inkscape.org/) --> 3 - 4 - <svg 5 - version="1.1" 6 - id="svg1" 7 - width="1090" 8 - height="1090" 9 - viewBox="0 0 1090 1090" 10 - xmlns:xlink="http://www.w3.org/1999/xlink" 11 - xmlns="http://www.w3.org/2000/svg" 12 - xmlns:svg="http://www.w3.org/2000/svg"> 13 - <defs 14 - id="defs1" /> 15 - <g 16 - id="g1"> 17 - <path 18 - id="path23" 19 - style="display:inline;fill:#274b70;fill-opacity:1" 20 - d="M -0.01317212,1090 H 1090 l 0.015,-185.73378 c 0,0 -12.771,11.11089 -20.3496,9.12889 -4.3399,-0.83912 -8.4264,-2.62038 -12.4391,-4.42115 -8.2227,-3.83234 -15.9956,-8.55376 -24.2327,-12.35744 -12.1338,-5.88311 -24.4765,-11.48724 -37.46146,-15.23384 -24.04271,-7.03785 -49.48404,-9.44127 -74.39516,-6.64199 -21.46625,2.40336 -42.12428,9.23019 -62.20106,16.93758 -14.49619,5.55198 -28.78226,11.664 -43.4538,16.75159 -8.35092,2.88186 -519.89836,-4.03589 -535.96117,6.81185 -12.16816,-5.81244 -56.21202,-23.50947 -68.89637,-28.15501 -20.04627,-7.7693 -41.27822,-12.93472 -62.83331,-13.52787 -22.97961,-0.73438 -46.005,3.80583 -67.270783,12.44855 -12.635563,5.02475 -24.794956,11.1329 -37.185172,16.71586 -1.900211,0.8456 -3.989605,1.74968 -5.981072,2.57383 -5.138432,2.143 -10.388702,4.0021 -15.690331,5.69636 -1.152227,0.39204 -2.306258,0.77931 -3.469222,1.13801 -16.8199417,6.89612 -21.52823655,1.93562 -21.52823655,1.93562 z" /> 21 - <path 22 - id="path22" 23 - style="display:inline;fill:#61bbe1;fill-opacity:1" 24 - d="m 1090,926.95153 c -11.2114,26.3292 -50.8297,0.127 -60.3771,-4.59857 -12.4675,-6.04873 -24.8936,-12.37138 -38.17896,-16.46458 -19.03984,-5.98112 -39.16494,-8.4242 -59.08682,-7.39699 -10.29661,0.53472 -20.49911,2.30579 -30.47082,4.89414 -21.54762,5.52277 -41.98308,14.45497 -62.6372,22.55588 -8.62098,3.34518 -17.3029,6.57622 -26.1953,9.13144 -197.59116,111.70745 -346.02714,76.73205 -561.3711,-2.59824 -8.70256,-3.18696 -16.96793,-7.40804 -25.24965,-11.51468 -6.1587,-3.07626 -12.41209,-5.96242 -18.78301,-8.57403 -17.44031,-7.23104 -35.85399,-12.54162 -54.75191,-13.87663 -18.01617,-1.32455 -36.27571,1.03188 -53.3963,6.78028 -15.29882,5.02733 -29.671525,12.37439 -44.264446,19.09326 -5.527211,2.52184 -11.26033,5.02996 -17.057938,7.06927 -13.183102,2.84061 -34.243289,10.71153 -41.53281788,-0.58829 -1.17550402,95.48431 -0.10901225,61.28612 -2.2055e-4,159.80111 391.05404243,0.012 702.29186243,-0.01 1090,0 0.033,-36.1703 0.01,-163.71777 0.01,-163.71777 z" /> 25 - <path 26 - style="display:inline;fill:#218dc2" 27 - d="m -0.00162752,1090 0.0526526,-27.8286 c 0,0 8.40437732,1.7278 15.97151792,2.6512 33.41462,0.4097 67.31305,-18.9114 91.502107,-29.2702 42.58278,-16.7741 85.89024,-1.2978 117.63973,19.0385 47.09881,18.3937 90.87125,13.4381 132.26905,-11.8054 38.02332,-20.184 90.84811,-18.1331 125.2167,7.0418 43.66694,21.0765 94.45211,18.8173 136.0225,-5.3561 36.32638,-21.2688 85.698,-23.0792 121.33,0.7834 47.48382,27.5274 109.39196,24.1962 150.25816,-3.5602 40.37789,-20.5509 86.85688,-16.917 120.05141,5.6917 26.8896,12.678 56.3726,19.6659 83.0381,16.9163 0.01,8.4157 -0.023,25.7079 -0.023,25.7079 z m -0.06958216,-68.3613 0.02118531,-49.38008 C 29.381094,992.44117 85.126119,954.9885 112.62946,944.16913 c 37.52595,-13.38769 69.89895,-3.34366 111.10946,18.56913 21.87489,9.64713 42.18198,19.08184 55.90444,2.48181 4.83367,-5.84728 8.98261,-11.55836 9.64558,-5.60254 0.97166,8.72892 8.86558,25.52919 9.40053,27.45865 15.93711,16.97012 40.00103,19.47452 -13.41377,28.31252 -43.07485,1.5627 -79.23128,-34.82054 -129.36305,-36.75735 -42.32945,-1.92979 -74.486692,21.47215 -111.200168,31.39655 -14.06499,3.0773 -36.4032106,4.6821 -44.78369168,12.2874 z M 1090,966.75059 c 0.6213,2.2125 0.096,53.09981 0.096,53.09981 -37.5217,7.8169 -100.05213,-33.74134 -145.77011,-39.52717 -45.89414,-0.18586 -34.95376,3.05224 -80.71314,21.05997 -22.49136,10.7156 -66.08351,15.3854 -88.28248,7.3603 13.90226,-17.31704 24.01869,-30.93465 24.01869,-30.93465 43.21199,-1.76049 95.4792,-40.10599 140.87288,-39.58947 46.08879,0.091 63.62466,19.34873 94.44276,29.75887 50.7517,18.24454 55.3355,-1.22766 55.3355,-1.22766 z" 28 - id="path19" /> 29 - <path 30 - id="path17" 31 - style="display:inline;fill:#274b70;fill-opacity:1;stroke:#0f1e30;stroke-width:0" 32 - d="m 546.88086,307.24023 c -16.25149,0.22404 -32.29505,8.53489 -41.47839,22.00999 -6.11046,8.80977 -8.84246,19.73569 -8.27158,30.39314 l 0.006,44.10273 c -6.53042,0.0258 -13.06725,-0.085 -19.59316,0.13473 -10.06021,1.0502 -18.72833,9.8651 -19.48853,19.97117 -0.28915,4.67912 -0.0151,9.36983 -0.11467,14.05387 -0.0182,7.57124 0.085,15.14576 0.0642,22.71471 -0.59209,4.96824 -1.52336,9.88991 -2.40421,14.81234 -28.84204,126.17406 -57.68407,252.34811 -86.5261,378.52217 -7.39964,0.01 -14.82289,0.65866 -22.02965,2.38833 -19.0789,4.35412 -36.8719,14.78777 -49.40118,29.89879 -11.50346,13.82858 -18.51543,31.51251 -18.8269,49.55682 -0.17848,6.78628 -0.1065,13.57846 0.062,20.36364 0.7539,14.95505 5.88537,29.62215 14.46849,41.87946 7.14342,10.27248 16.61869,19.01988 27.73317,24.82958 6.57232,3.2652 14.34528,4.1644 21.43142,2.1489 5.37582,-1.4716 10.1551,-4.4325 15.06509,-6.9737 7.64527,-4.1151 15.34626,-8.3856 23.81815,-10.5515 1.50639,-0.4123 3.21632,-0.8292 4.81412,-1.1888 0.83855,-0.1801 1.96113,-0.4271 2.91476,-0.6072 3.86309,-0.7703 7.97186,-1.384 11.97662,-1.8571 6.71548,-0.7586 13.45217,-1.4014 20.20823,-1.6411 2.6321,-0.07 5.43867,0.061 7.94727,0.3242 2.73679,0.2833 5.49754,0.7503 8.10516,1.3374 3.96612,0.8894 7.84568,2.1308 11.64875,3.5591 0.74664,0.2883 1.7671,0.6836 2.61788,1.0185 9.12665,3.7142 17.91846,8.1848 26.95617,12.103 11.80678,5.194 24.08797,9.4496 36.80079,11.7704 10.14335,1.9097 20.48623,2.5615 30.79664,2.4353 6.8993,0.098 13.82287,0.3937 20.69938,-0.3504 13.79531,-1.2981 27.30912,-5.1527 39.8335,-11.0535 11.07992,-5.0151 21.70146,-10.9248 33.21204,-14.9489 3.49458,-1.2178 7.09987,-2.2522 10.63375,-3.0789 2.26759,-0.5319 4.31819,-0.9453 6.61939,-1.3509 8.66695,-1.5203 17.47663,-2.0074 26.26131,-2.2005 2.28766,-0.015 4.75741,0.073 6.92266,0.3024 9.55998,1.0753 18.85868,3.7706 27.86133,7.0918 1.39686,0.527 3.00255,1.141 4.46827,1.7344 3.03372,1.2215 5.99547,2.5027 8.89227,3.8323 5.47093,2.4963 11.0455,5.3158 16.45758,8.1839 2.94418,1.7125 6.18504,2.9322 9.5677,3.3964 6.47607,0.9567 13.20626,-0.529 18.81961,-3.854 4.24605,-2.4485 8.15298,-5.4463 11.90386,-8.5891 13.20276,-11.3051 23.11206,-26.63756 27.15884,-43.6049 1.89016,-7.78677 2.43703,-15.84137 2.20904,-23.8338 0.0533,-8.82633 0.12149,-17.77819 -2.0432,-26.39839 -4.20691,-18.34832 -15.1256,-34.95719 -29.95652,-46.48705 -15.31265,-12.03638 -34.55411,-18.84375 -53.99187,-19.52067 -1.63184,-0.0655 -3.26452,-0.10786 -4.89769,-0.12281 -2.6944,-10.95755 -5.01744,-21.9618 -7.54773,-32.96412 -26.97758,-119.45401 -53.95516,-238.90803 -80.93274,-358.36205 0.37669,-5.97323 0.055,-11.96025 0.15108,-17.93984 -0.0215,-7.33657 0.0878,-14.68165 -0.25963,-22.01062 -1.18679,-8.96759 -8.58959,-16.53035 -17.3563,-18.4236 -3.83894,-0.82157 -7.78811,-0.36734 -11.67929,-0.48047 -3.43828,0.004 -6.87656,0.007 -10.31484,0.0108 0,0 -1.3085,-61.14045 -2.00196,-61.375 -4.31867,-14.335 -15.6879,-26.23893 -29.66529,-31.51063 -6.4577,-2.50081 -13.40358,-3.70563 -20.32494,-3.6007 z" /> 33 - <path 34 - id="path16" 35 - style="display:inline;fill:#f9e008;fill-opacity:1;stroke:#0f1e30;stroke-width:0" 36 - d="m 549.27734,329.25391 c -15.92457,-0.65336 -29.40908,10.71067 -30.11914,25.38281 -0.0103,0.32413 -0.0142,0.6484 -0.0117,0.97266 h -0.01 v 48.70898 h 57.73633 v -48.70898 h -0.0156 c -0.1224,-14.14529 -12.24206,-25.72681 -27.58008,-26.35547 z m -69.09375,96.48828 c -0.13313,0 -0.24023,0.1071 -0.24023,0.24023 v 31.37696 c 0,0.13312 0.1071,0.24023 0.24023,0.24023 h 136.05469 c 0.13313,0 0.24024,-0.10711 0.24024,-0.24023 v -31.37696 c 0,-0.13313 -0.10711,-0.24023 -0.24024,-0.24023 z m -2.62695,52.32617 -85.60547,374.51172 c 0.0242,-0.10566 194.25696,0.18573 291.36524,0.91992 11.45831,-0.002 20.72461,-0.083 20.69531,-0.21289 l -84.7207,-375.15234 c -0.0245,-0.1086 -141.73438,-0.0664 -141.73438,-0.0664 z M 368.41602,875.9375 c -37.49631,0 -67.6836,27.652 -67.6836,62 V 951.5 c 0,34.348 27.84432,52.0317 32.62489,52.5284 3.52213,0.3659 5.1766,-0.9263 7.49788,-2.0723 19.79102,-9.77106 27.51938,-18.73043 77.34759,-21.70852 49.30636,-2.17161 69.18528,34.67532 129.15625,32.40122 55.96201,3.7193 62.98649,-31.31252 131.14335,-32.71329 32.99055,-2.7514 73.77237,20.39069 81.18591,24.27319 1.62665,0.8519 3.7142,0.1598 4.33515,-0.084 2.94408,-1.1543 31.68359,-18.277 31.68359,-52.625 v -13.5625 c 0,-34.348 -30.18729,-62 -67.68359,-62 z" /> 37 - <path 38 - id="rect1" 39 - style="display:inline;fill:#edc506;fill-opacity:1;stroke:#0f1e30;stroke-width:0" 40 - d="m 554.46094,339.76562 c -12.43766,0 -18.57617,9.82407 -18.57617,22.26172 -0.096,6.55599 0.0253,13.48685 0.0625,20.04297 l 0.0625,22.24805 h 40.86328 v -43.96484 c -0.69011,-11.78122 -10.39708,-20.5879 -22.39063,-20.5879 z m 20.28515,86.04493 v 31.77539 h 41.57227 c 0.0682,-0.0241 0.12242,-0.0783 0.14648,-0.14649 v -31.53711 c -0.0123,-0.0349 -0.0327,-0.0665 -0.0586,-0.0918 z m -0.23047,52.25586 65.05469,375.16211 c 0.12066,6.3e-4 0.24079,10e-4 0.36133,0.002 l 60.87305,0.17383 h 0.082 c 1.99029,-0.0323 3.13533,-0.0714 3.125,-0.11719 l -84.7207,-375.15234 h -0.002 l -0.002,-0.002 h -0.0137 c -0.0466,-0.003 -2.85909,-0.003 -3.0918,-0.006 l -39.17773,-0.0605 c -0.82434,-3.1e-4 -1.65212,2.8e-4 -2.48829,0 z m 68.49024,397.87109 c -0.30147,0.0156 -0.6021,0.033 -0.90234,0.0527 24.35837,2.97069 43.10546,23.61842 43.10546,48.80469 v 39.42578 c 0,5.42101 -0.86859,10.63255 -2.4746,15.50196 32.15427,-0.61129 69.85368,20.76837 76.95312,24.48637 1.62665,0.8519 3.71499,0.1598 4.33594,-0.084 2.1791,-0.8544 18.46765,-10.48342 26.79687,-29.34378 0.17803,-0.40966 0.3619,-0.80956 0.53321,-1.22852 0.43216,-1.04394 0.81832,-2.13589 1.19726,-3.23437 0.22188,-0.64262 0.44798,-1.2809 0.65234,-1.94336 0.43016,-1.41189 0.81813,-2.86004 1.14649,-4.35742 0.0453,-0.1957 0.095,-0.38674 0.13867,-0.58399 0.77367,-3.71825 1.21875,-7.69247 1.21875,-11.93359 v -13.5625 c 0,-4.55407 -0.53106,-8.98996 -1.53906,-13.25977 -1.10972,-4.46696 -2.682,-8.74713 -4.66406,-12.78711 -10.7061,-21.26405 -34.152,-35.95312 -61.48047,-35.95312 z" /> 41 - <path 42 - id="rect17" 43 - style="display:inline;fill:#ffffff;stroke:#0f1e30;stroke-width:0" 44 - d="m 497.65234,507.57617 c -4.04177,-0.13328 -6.04125,2.16956 -7.48828,8.49805 L 425.46094,814.3457 c -0.8144,4.23538 -2.2246,10.49817 7.78515,11.6836 7.02508,0.69367 9.37804,-2.5307 10.51368,-9.25782 l 64.72461,-296.60351 c 1.45516,-8.75244 -0.5719,-9.68254 -6.05469,-11.70508 -1.85628,-0.52724 -3.43009,-0.84229 -4.77735,-0.88672 z m 48.67578,385.41211 -186.23437,0.0879 c -25.6219,-0.52968 -37.98443,24.9384 -41.18945,39.86328 -0.14877,9.59973 1.3897,17.02404 8.49804,17.25977 13.00856,-0.13018 8.62447,-17.92766 14.44727,-24.94141 4.94695,-5.95876 9.88516,-10.21097 19.39453,-12.11719 l 184.64258,-1.32617 c 10.15974,-0.0818 13.70554,-3.90915 13.74414,-10.60742 -0.051,-6.20127 -3.42521,-8.26815 -13.30274,-8.21875 z" /> 45 - <path 46 - id="rect24" 47 - style="fill:#f9e008;fill-opacity:1;stroke:#0f1e30;stroke-width:0" 48 - d="m 240.01367,-547.80469 c 0,5.79888 4.66792,10.46875 10.4668,10.46875 h 24.74023 c 5.79888,0 10.46875,-4.66987 10.46875,-10.46875 0,-5.79887 -4.66987,-10.46679 -10.46875,-10.46679 h -24.74023 c -5.79888,0 -10.4668,4.66792 -10.4668,10.46679 z m 33.99414,-81.32031 c -0.0467,2.67523 0.9312,5.3694 2.94531,7.45508 l 17.18555,17.79687 c 4.02824,4.17137 10.62942,4.28605 14.80078,0.25782 4.17137,-4.02824 4.28605,-10.62942 0.25782,-14.80079 l -17.18555,-17.79687 c -4.02824,-4.17136 -10.62942,-4.28605 -14.80078,-0.25781 -2.08568,2.01412 -3.15643,4.67047 -3.20313,7.3457 z m 0.74414,162.32617 c 0.0467,2.67523 1.11745,5.33159 3.20313,7.34571 4.17136,4.02823 10.77254,3.91354 14.80078,-0.25782 l 17.1875,-17.79687 c 4.02824,-4.17136 3.9116,-10.77255 -0.25977,-14.80078 -4.17136,-4.02824 -10.77059,-3.91355 -14.79882,0.25781 l -17.1875,17.79687 c -2.01412,2.08568 -2.99201,4.77985 -2.94532,7.45508 z m 77.31836,-169.35742 c 0,5.79888 4.66988,10.4668 10.46875,10.4668 5.79888,0 10.4668,-4.66792 10.4668,-10.4668 v -24.74023 c 0,-5.79888 -4.66792,-10.4668 -10.4668,-10.4668 -5.79887,0 -10.46875,4.66792 -10.46875,10.4668 z m 1.03516,200.41016 c 0,5.79887 4.66987,10.46679 10.46875,10.46679 5.79887,0 10.4668,-4.66792 10.4668,-10.46679 v -24.74219 c 0,-5.79888 -4.66793,-10.4668 -10.4668,-10.4668 -5.79888,0 -10.46875,4.66792 -10.46875,10.4668 z" 49 - transform="rotate(90)" /> 50 - </g> 51 - </svg> 1 + <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="48" height="48" viewBox="0 0 48 48" id="svg1" xml:space="preserve" xmlns:svg="http://www.w3.org/2000/svg"><defs id="defs1"></defs><g id="layer1" style="display:inline"><path id="rect24" style="fill:#f0802c;fill-opacity:1;stroke:none;stroke-width:1;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke" d="m 40,12 c -1.662,0 -3,1.338 -3,3 h 11 c 0,-1.662 -1.338,-3 -3,-3 z"></path><path id="path18" style="display:inline;fill:#e1e5e6;fill-opacity:1;stroke:none;stroke-width:1;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke" d="m 34,5 c -3.313708,0 -6,2.2385763 -6,5 V 25 L 0,40 H 28.25 C 37.479385,40 40,33.284271 40,25 V 10 C 40,7.2385763 37.313708,5 34,5 Z"></path><ellipse style="display:inline;fill:#586066;fill-opacity:1;stroke-width:1.302;stroke-linejoin:round;paint-order:fill markers stroke" id="path14" cx="35" cy="10.877969" rx="1.1974249" ry="1.1220315"></ellipse><path id="path15" style="display:inline;fill:#586066;fill-opacity:1;stroke:none;stroke-width:1.1547;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke" d="M 12,31 1,39 h 18 c 1.104569,0 2,-1.193907 2,-2.666667 0,-1.472759 -0.895431,-2.666666 -2,-2.666666 -0.495037,1.78e-4 -0.972434,0.24514 -1.339844,0.6875 z"></path><a id="a27"><path id="path26" style="fill:#a4adb4;fill-opacity:1;stroke:none;stroke-width:1.06667;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke" d="M 28,20 C 19.615996,22.218354 10,32 3.4999999,37 H 22.5 C 30,37 32,30 32,25 32,22 30,20 28,20 Z"></path></a></g></svg>
+34
themes/seamark/public/static/seamark_seagull.svg
··· 1 + <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 + <!-- Created with Inkscape (http://www.inkscape.org/) --> 3 + 4 + <svg 5 + width="48" 6 + height="48" 7 + viewBox="0 0 48 48" 8 + version="1.1" 9 + id="svg1" 10 + xml:space="preserve" 11 + xmlns="http://www.w3.org/2000/svg" 12 + xmlns:svg="http://www.w3.org/2000/svg"><defs 13 + id="defs1" /><g 14 + id="layer1" 15 + style="display:inline"><path 16 + id="rect24" 17 + style="fill:#f0802c;fill-opacity:1;stroke:none;stroke-width:1;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke" 18 + d="m 40,12 c -1.662,0 -3,1.338 -3,3 h 11 c 0,-1.662 -1.338,-3 -3,-3 z" /><path 19 + id="path18" 20 + style="display:inline;fill:#e1e5e6;fill-opacity:1;stroke:none;stroke-width:1;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke" 21 + d="m 34,5 c -3.313708,0 -6,2.2385763 -6,5 V 25 L 0,40 H 28.25 C 37.479385,40 40,33.284271 40,25 V 10 C 40,7.2385763 37.313708,5 34,5 Z" /><ellipse 22 + style="display:inline;fill:#586066;fill-opacity:1;stroke-width:1.302;stroke-linejoin:round;paint-order:fill markers stroke" 23 + id="path14" 24 + cx="35" 25 + cy="10.877969" 26 + rx="1.1974249" 27 + ry="1.1220315" /><path 28 + id="path15" 29 + style="display:inline;fill:#586066;fill-opacity:1;stroke:none;stroke-width:1.1547;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke" 30 + d="M 12,31 1,39 h 18 c 1.104569,0 2,-1.193907 2,-2.666667 0,-1.472759 -0.895431,-2.666666 -2,-2.666666 -0.495037,1.78e-4 -0.972434,0.24514 -1.339844,0.6875 z" /><a 31 + id="a27"><path 32 + id="path26" 33 + style="fill:#a4adb4;fill-opacity:1;stroke:none;stroke-width:1.06667;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke" 34 + d="M 28,20 C 19.615996,22.218354 10,32 3.4999999,37 H 22.5 C 30,37 32,30 32,25 32,22 30,20 28,20 Z" /></a></g></svg>
themes/seamark/public/static/seamark_seagull_buoy.png

This is a binary file and will not be displayed.

themes/seamark/public/web-app-manifest-192x192.png

This is a binary file and will not be displayed.

themes/seamark/public/web-app-manifest-512x512.png

This is a binary file and will not be displayed.

+66
themes/seamark/templates/components/hero.html
··· 1 + {{ define "hero" }} 2 + {{/* 3 + Hero section component - displays landing page hero for non-authenticated users 4 + */}} 5 + <section class="hero bg-base-200 min-h-[60vh] py-16 pb-24 relative overflow-hidden"> 6 + <img src="/static/seamark_seagull_buoy.png" alt="" class="hidden lg:block absolute left-[12%] top-1/3 w-60 h-auto z-10 pointer-events-none animate-rock" aria-hidden="true"> 7 + <div class="hero-content text-center flex-col relative z-10 w-full"> 8 + <h1 id="hero-heading" class="text-4xl md:text-5xl font-bold">your beacon <span class="text-primary">at</span> sea.</h1> 9 + <script> 10 + (function() { 11 + var h = [ 12 + ["guiding you ", "at", " sea."], 13 + ["your beacon ", "at", " sea."], 14 + ["never lost ", "at", " sea."], 15 + ["find your way ", "at", " sea."], 16 + ["charting courses ", "at", " sea."] 17 + ]; 18 + var pick = h[Math.floor(Math.random() * h.length)]; 19 + var el = document.getElementById("hero-heading"); 20 + el.innerHTML = pick[0] + '<span class="text-primary">' + pick[1] + '</span>' + pick[2]; 21 + })(); 22 + </script> 23 + <p class="text-lg text-base-content/70 max-w-lg mt-4"> 24 + Push and pull Docker images on the AT Protocol.<br> 25 + Browse public registries or control your data. 26 + </p> 27 + 28 + <div class="mockup-code bg-base-300 text-base-content text-left w-full max-w-lg text-base mt-8"> 29 + <pre data-prefix="$"><code>docker login {{ .RegistryURL }}</code></pre> 30 + <pre data-prefix="$"><code>docker push {{ .RegistryURL }}/you/app</code></pre> 31 + <pre data-prefix="#" class="text-base-content/65"><code>same docker, decentralized</code></pre> 32 + </div> 33 + 34 + <div class="flex items-center justify-center gap-4 mt-8"> 35 + <a href="/auth/oauth/login?return_to=/" class="btn btn-primary btn-lg">Get Started</a> 36 + <a href="/learn-more" class="btn btn-ghost btn-lg" aria-label="How the {{ .ClientShortName }} container registry works">How It Works</a> 37 + </div> 38 + 39 + <!-- Benefit Cards --> 40 + <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mt-12 w-full max-w-4xl"> 41 + <div class="card bg-base-100 shadow-sm p-6 text-center"> 42 + <div class="text-primary mb-4 flex justify-center"> 43 + {{ icon "ship" "size-8" }} 44 + </div> 45 + <h2 class="font-semibold text-lg">Works with Docker</h2> 46 + <p class="text-base-content/70 mt-2">Use docker push &amp; pull. No new tools to learn.</p> 47 + </div> 48 + <div class="card bg-base-100 shadow-sm p-6 text-center"> 49 + <div class="text-primary mb-4 flex justify-center"> 50 + {{ icon "anchor" "size-8" }} 51 + </div> 52 + <h2 class="font-semibold text-lg">Your Data</h2> 53 + <p class="text-base-content/70 mt-2">Join shared holds or captain your own storage.</p> 54 + </div> 55 + <div class="card bg-base-100 shadow-sm p-6 text-center"> 56 + <div class="text-primary mb-4 flex justify-center"> 57 + {{ icon "compass" "size-8" }} 58 + </div> 59 + <h2 class="font-semibold text-lg">Discover Images</h2> 60 + <p class="text-base-content/70 mt-2">Browse and star public container registries.</p> 61 + </div> 62 + </div> 63 + </div> 64 + <img src="/static/wave-pattern.svg" width="1440" height="128" alt="" class="absolute bottom-0 left-0 w-full h-32 pointer-events-none" aria-hidden="true"> 65 + </section> 66 + {{ end }}
+10
themes/seamark/theme.css
··· 12 12 --color-accent: oklch(66.6% 0.121 28); 13 13 --color-accent-content: oklch(28% 0.121 28); 14 14 } 15 + 16 + @keyframes rock { 17 + 0%, 100% { transform: translateY(-50%) rotate(-3deg); } 18 + 50% { transform: translateY(-50%) rotate(3deg); } 19 + } 20 + 21 + .animate-rock { 22 + animation: rock 3s ease-in-out infinite; 23 + transform-origin: bottom center; 24 + }