diff --git a/config-appview.example.yaml b/config-appview.example.yaml
index 6ab814e..cd29815 100644
--- a/config-appview.example.yaml
+++ b/config-appview.example.yaml
@@ -45,7 +45,7 @@ ui:
# SQLite/libSQL database for OAuth sessions, stars, pull counts, and device approvals.
database_path: /var/lib/atcr/ui.db
# Visual theme name (e.g. "seamark"). Empty uses default atcr.io branding.
- theme: ""
+ theme: "seamark"
# libSQL sync URL (libsql://...). Works with Turso cloud or self-hosted libsql-server. Leave empty for local-only SQLite.
libsql_sync_url: ""
# Auth token for libSQL sync. Required if libsql_sync_url is set.
@@ -74,6 +74,7 @@ jetstream:
relay_endpoints:
- https://relay1.us-east.bsky.network
- https://relay1.us-west.bsky.network
+ - https://zlay.waow.tech
# JWT authentication settings.
auth:
# RSA private key for signing registry JWTs issued to Docker clients.
diff --git a/lexicons/io/atcr/sailor/profile.json b/lexicons/io/atcr/sailor/profile.json
index 63877da..d13a09d 100644
--- a/lexicons/io/atcr/sailor/profile.json
+++ b/lexicons/io/atcr/sailor/profile.json
@@ -15,6 +15,10 @@
"format": "uri",
"description": "Default hold endpoint for blob storage. If null, user has opted out of defaults."
},
+ "autoRemoveUntagged": {
+ "type": "boolean",
+ "description": "Automatically delete manifest records that become untagged after a tag overwrite. Layers are cleaned up by hold garbage collection."
+ },
"createdAt": {
"type": "string",
"format": "datetime",
diff --git a/package-lock.json b/package-lock.json
index e6bb743..a334db9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,21 +11,21 @@
"actor-typeahead": "^0.1.2",
"htmx-ext-json-enc": "^2.0.3",
"htmx.org": "^2.0.8",
- "lucide": "^0.575.0"
+ "lucide": "^0.577.0"
},
"devDependencies": {
- "@tailwindcss/cli": "^4.2.0",
+ "@tailwindcss/cli": "^4.2.1",
"@tailwindcss/typography": "^0.5.19",
"daisyui": "^5.5.19",
- "esbuild": "^0.27.3",
+ "esbuild": "^0.27.4",
"glob": "^13.0.6",
"tailwindcss": "^4.2"
}
},
"node_modules/@esbuild/aix-ppc64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
- "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz",
+ "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==",
"cpu": [
"ppc64"
],
@@ -40,9 +40,9 @@
}
},
"node_modules/@esbuild/android-arm": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
- "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz",
+ "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==",
"cpu": [
"arm"
],
@@ -57,9 +57,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
- "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz",
+ "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==",
"cpu": [
"arm64"
],
@@ -74,9 +74,9 @@
}
},
"node_modules/@esbuild/android-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
- "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz",
+ "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==",
"cpu": [
"x64"
],
@@ -91,9 +91,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
- "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz",
+ "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==",
"cpu": [
"arm64"
],
@@ -108,9 +108,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
- "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz",
+ "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==",
"cpu": [
"x64"
],
@@ -125,9 +125,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
- "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz",
+ "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==",
"cpu": [
"arm64"
],
@@ -142,9 +142,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
- "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz",
+ "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==",
"cpu": [
"x64"
],
@@ -159,9 +159,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
- "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz",
+ "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==",
"cpu": [
"arm"
],
@@ -176,9 +176,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
- "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz",
+ "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==",
"cpu": [
"arm64"
],
@@ -193,9 +193,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
- "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz",
+ "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==",
"cpu": [
"ia32"
],
@@ -210,9 +210,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
- "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz",
+ "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==",
"cpu": [
"loong64"
],
@@ -227,9 +227,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
- "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz",
+ "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==",
"cpu": [
"mips64el"
],
@@ -244,9 +244,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
- "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz",
+ "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==",
"cpu": [
"ppc64"
],
@@ -261,9 +261,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
- "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz",
+ "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==",
"cpu": [
"riscv64"
],
@@ -278,9 +278,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
- "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz",
+ "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==",
"cpu": [
"s390x"
],
@@ -295,9 +295,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
- "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz",
+ "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==",
"cpu": [
"x64"
],
@@ -312,9 +312,9 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
- "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz",
+ "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==",
"cpu": [
"arm64"
],
@@ -329,9 +329,9 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
- "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz",
+ "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==",
"cpu": [
"x64"
],
@@ -346,9 +346,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
- "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz",
+ "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==",
"cpu": [
"arm64"
],
@@ -363,9 +363,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
- "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz",
+ "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==",
"cpu": [
"x64"
],
@@ -380,9 +380,9 @@
}
},
"node_modules/@esbuild/openharmony-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
- "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz",
+ "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==",
"cpu": [
"arm64"
],
@@ -397,9 +397,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
- "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz",
+ "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==",
"cpu": [
"x64"
],
@@ -414,9 +414,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
- "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz",
+ "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==",
"cpu": [
"arm64"
],
@@ -431,9 +431,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
- "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz",
+ "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==",
"cpu": [
"ia32"
],
@@ -448,9 +448,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
- "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz",
+ "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==",
"cpu": [
"x64"
],
@@ -824,28 +824,28 @@
}
},
"node_modules/@tailwindcss/cli": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.2.0.tgz",
- "integrity": "sha512-C62SWDp+6Rj5DHJDlMyAqESpmljiQ35H4SncAcVn3Gm0rEPrKFDIdAheT74s9zAbrsa2D/L+jJaPgCO1fyZG6g==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.2.1.tgz",
+ "integrity": "sha512-b7MGn51IA80oSG+7fuAgzfQ+7pZBgjzbqwmiv6NO7/+a1sev32cGqnwhscT7h0EcAvMa9r7gjRylqOH8Xhc4DA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@parcel/watcher": "^2.5.1",
- "@tailwindcss/node": "4.2.0",
- "@tailwindcss/oxide": "4.2.0",
+ "@tailwindcss/node": "4.2.1",
+ "@tailwindcss/oxide": "4.2.1",
"enhanced-resolve": "^5.19.0",
"mri": "^1.2.0",
"picocolors": "^1.1.1",
- "tailwindcss": "4.2.0"
+ "tailwindcss": "4.2.1"
},
"bin": {
"tailwindcss": "dist/index.mjs"
}
},
"node_modules/@tailwindcss/node": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.0.tgz",
- "integrity": "sha512-Yv+fn/o2OmL5fh/Ir62VXItdShnUxfpkMA4Y7jdeC8O81WPB8Kf6TT6GSHvnqgSwDzlB5iT7kDpeXxLsUS0T6Q==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz",
+ "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -855,37 +855,37 @@
"lightningcss": "1.31.1",
"magic-string": "^0.30.21",
"source-map-js": "^1.2.1",
- "tailwindcss": "4.2.0"
+ "tailwindcss": "4.2.1"
}
},
"node_modules/@tailwindcss/oxide": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.0.tgz",
- "integrity": "sha512-AZqQzADaj742oqn2xjl5JbIOzZB/DGCYF/7bpvhA8KvjUj9HJkag6bBuwZvH1ps6dfgxNHyuJVlzSr2VpMgdTQ==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz",
+ "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 20"
},
"optionalDependencies": {
- "@tailwindcss/oxide-android-arm64": "4.2.0",
- "@tailwindcss/oxide-darwin-arm64": "4.2.0",
- "@tailwindcss/oxide-darwin-x64": "4.2.0",
- "@tailwindcss/oxide-freebsd-x64": "4.2.0",
- "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.0",
- "@tailwindcss/oxide-linux-arm64-gnu": "4.2.0",
- "@tailwindcss/oxide-linux-arm64-musl": "4.2.0",
- "@tailwindcss/oxide-linux-x64-gnu": "4.2.0",
- "@tailwindcss/oxide-linux-x64-musl": "4.2.0",
- "@tailwindcss/oxide-wasm32-wasi": "4.2.0",
- "@tailwindcss/oxide-win32-arm64-msvc": "4.2.0",
- "@tailwindcss/oxide-win32-x64-msvc": "4.2.0"
+ "@tailwindcss/oxide-android-arm64": "4.2.1",
+ "@tailwindcss/oxide-darwin-arm64": "4.2.1",
+ "@tailwindcss/oxide-darwin-x64": "4.2.1",
+ "@tailwindcss/oxide-freebsd-x64": "4.2.1",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.2.1",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.2.1",
+ "@tailwindcss/oxide-linux-x64-musl": "4.2.1",
+ "@tailwindcss/oxide-wasm32-wasi": "4.2.1",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.2.1"
}
},
"node_modules/@tailwindcss/oxide-android-arm64": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.0.tgz",
- "integrity": "sha512-F0QkHAVaW/JNBWl4CEKWdZ9PMb0khw5DCELAOnu+RtjAfx5Zgw+gqCHFvqg3AirU1IAd181fwOtJQ5I8Yx5wtw==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz",
+ "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==",
"cpu": [
"arm64"
],
@@ -900,9 +900,9 @@
}
},
"node_modules/@tailwindcss/oxide-darwin-arm64": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.0.tgz",
- "integrity": "sha512-I0QylkXsBsJMZ4nkUNSR04p6+UptjcwhcVo3Zu828ikiEqHjVmQL9RuQ6uT/cVIiKpvtVA25msu/eRV97JeNSA==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz",
+ "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==",
"cpu": [
"arm64"
],
@@ -917,9 +917,9 @@
}
},
"node_modules/@tailwindcss/oxide-darwin-x64": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.0.tgz",
- "integrity": "sha512-6TmQIn4p09PBrmnkvbYQ0wbZhLtbaksCDx7Y7R3FYYx0yxNA7xg5KP7dowmQ3d2JVdabIHvs3Hx4K3d5uCf8xg==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz",
+ "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==",
"cpu": [
"x64"
],
@@ -934,9 +934,9 @@
}
},
"node_modules/@tailwindcss/oxide-freebsd-x64": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.0.tgz",
- "integrity": "sha512-qBudxDvAa2QwGlq9y7VIzhTvp2mLJ6nD/G8/tI70DCDoneaUeLWBJaPcbfzqRIWraj+o969aDQKvKW9dvkUizw==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz",
+ "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==",
"cpu": [
"x64"
],
@@ -951,9 +951,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.0.tgz",
- "integrity": "sha512-7XKkitpy5NIjFZNUQPeUyNJNJn1CJeV7rmMR+exHfTuOsg8rxIO9eNV5TSEnqRcaOK77zQpsyUkBWmPy8FgdSg==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz",
+ "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==",
"cpu": [
"arm"
],
@@ -968,9 +968,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.0.tgz",
- "integrity": "sha512-Mff5a5Q3WoQR01pGU1gr29hHM1N93xYrKkGXfPw/aRtK4bOc331Ho4Tgfsm5WDGvpevqMpdlkCojT3qlCQbCpA==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz",
+ "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==",
"cpu": [
"arm64"
],
@@ -985,9 +985,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.0.tgz",
- "integrity": "sha512-XKcSStleEVnbH6W/9DHzZv1YhjE4eSS6zOu2eRtYAIh7aV4o3vIBs+t/B15xlqoxt6ef/0uiqJVB6hkHjWD/0A==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz",
+ "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==",
"cpu": [
"arm64"
],
@@ -1002,9 +1002,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.0.tgz",
- "integrity": "sha512-/hlXCBqn9K6fi7eAM0RsobHwJYa5V/xzWspVTzxnX+Ft9v6n+30Pz8+RxCn7sQL/vRHHLS30iQPrHQunu6/vJA==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz",
+ "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==",
"cpu": [
"x64"
],
@@ -1019,9 +1019,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.0.tgz",
- "integrity": "sha512-lKUaygq4G7sWkhQbfdRRBkaq4LY39IriqBQ+Gk6l5nKq6Ay2M2ZZb1tlIyRNgZKS8cbErTwuYSor0IIULC0SHw==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz",
+ "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==",
"cpu": [
"x64"
],
@@ -1036,9 +1036,9 @@
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.0.tgz",
- "integrity": "sha512-xuDjhAsFdUuFP5W9Ze4k/o4AskUtI8bcAGU4puTYprr89QaYFmhYOPfP+d1pH+k9ets6RoE23BXZM1X1jJqoyw==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz",
+ "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==",
"bundleDependencies": [
"@napi-rs/wasm-runtime",
"@emnapi/core",
@@ -1066,9 +1066,9 @@
}
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.0.tgz",
- "integrity": "sha512-2UU/15y1sWDEDNJXxEIrfWKC2Yb4YgIW5Xz2fKFqGzFWfoMHWFlfa1EJlGO2Xzjkq/tvSarh9ZTjvbxqWvLLXA==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz",
+ "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==",
"cpu": [
"arm64"
],
@@ -1083,9 +1083,9 @@
}
},
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.0.tgz",
- "integrity": "sha512-CrFadmFoc+z76EV6LPG1jx6XceDsaCG3lFhyLNo/bV9ByPrE+FnBPckXQVP4XRkN76h3Fjt/a+5Er/oA/nCBvQ==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz",
+ "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==",
"cpu": [
"x64"
],
@@ -1129,9 +1129,9 @@
}
},
"node_modules/brace-expansion": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz",
- "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==",
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
+ "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1175,9 +1175,9 @@
}
},
"node_modules/enhanced-resolve": {
- "version": "5.19.0",
- "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz",
- "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==",
+ "version": "5.20.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz",
+ "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1189,9 +1189,9 @@
}
},
"node_modules/esbuild": {
- "version": "0.27.3",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
- "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
+ "version": "0.27.4",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz",
+ "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -1202,32 +1202,32 @@
"node": ">=18"
},
"optionalDependencies": {
- "@esbuild/aix-ppc64": "0.27.3",
- "@esbuild/android-arm": "0.27.3",
- "@esbuild/android-arm64": "0.27.3",
- "@esbuild/android-x64": "0.27.3",
- "@esbuild/darwin-arm64": "0.27.3",
- "@esbuild/darwin-x64": "0.27.3",
- "@esbuild/freebsd-arm64": "0.27.3",
- "@esbuild/freebsd-x64": "0.27.3",
- "@esbuild/linux-arm": "0.27.3",
- "@esbuild/linux-arm64": "0.27.3",
- "@esbuild/linux-ia32": "0.27.3",
- "@esbuild/linux-loong64": "0.27.3",
- "@esbuild/linux-mips64el": "0.27.3",
- "@esbuild/linux-ppc64": "0.27.3",
- "@esbuild/linux-riscv64": "0.27.3",
- "@esbuild/linux-s390x": "0.27.3",
- "@esbuild/linux-x64": "0.27.3",
- "@esbuild/netbsd-arm64": "0.27.3",
- "@esbuild/netbsd-x64": "0.27.3",
- "@esbuild/openbsd-arm64": "0.27.3",
- "@esbuild/openbsd-x64": "0.27.3",
- "@esbuild/openharmony-arm64": "0.27.3",
- "@esbuild/sunos-x64": "0.27.3",
- "@esbuild/win32-arm64": "0.27.3",
- "@esbuild/win32-ia32": "0.27.3",
- "@esbuild/win32-x64": "0.27.3"
+ "@esbuild/aix-ppc64": "0.27.4",
+ "@esbuild/android-arm": "0.27.4",
+ "@esbuild/android-arm64": "0.27.4",
+ "@esbuild/android-x64": "0.27.4",
+ "@esbuild/darwin-arm64": "0.27.4",
+ "@esbuild/darwin-x64": "0.27.4",
+ "@esbuild/freebsd-arm64": "0.27.4",
+ "@esbuild/freebsd-x64": "0.27.4",
+ "@esbuild/linux-arm": "0.27.4",
+ "@esbuild/linux-arm64": "0.27.4",
+ "@esbuild/linux-ia32": "0.27.4",
+ "@esbuild/linux-loong64": "0.27.4",
+ "@esbuild/linux-mips64el": "0.27.4",
+ "@esbuild/linux-ppc64": "0.27.4",
+ "@esbuild/linux-riscv64": "0.27.4",
+ "@esbuild/linux-s390x": "0.27.4",
+ "@esbuild/linux-x64": "0.27.4",
+ "@esbuild/netbsd-arm64": "0.27.4",
+ "@esbuild/netbsd-x64": "0.27.4",
+ "@esbuild/openbsd-arm64": "0.27.4",
+ "@esbuild/openbsd-x64": "0.27.4",
+ "@esbuild/openharmony-arm64": "0.27.4",
+ "@esbuild/sunos-x64": "0.27.4",
+ "@esbuild/win32-arm64": "0.27.4",
+ "@esbuild/win32-ia32": "0.27.4",
+ "@esbuild/win32-x64": "0.27.4"
}
},
"node_modules/glob": {
@@ -1564,9 +1564,9 @@
}
},
"node_modules/lru-cache": {
- "version": "11.2.6",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
- "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==",
+ "version": "11.2.7",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz",
+ "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
@@ -1574,9 +1574,9 @@
}
},
"node_modules/lucide": {
- "version": "0.575.0",
- "resolved": "https://registry.npmjs.org/lucide/-/lucide-0.575.0.tgz",
- "integrity": "sha512-+xwqZpvrqPioU8bSH49zH2xARfnKyZgIjdnfbex0CrURB3q4wNFhinYN1Z9Q3lE16Q/6N9iEXnStvyS3c70RKw==",
+ "version": "0.577.0",
+ "resolved": "https://registry.npmjs.org/lucide/-/lucide-0.577.0.tgz",
+ "integrity": "sha512-PpC/m5eOItp/WU/GlQPFBXDOhq6HibL73KzYP37OX3LM7VmzWQF8voEj8QRWUFvy9FIKfeDQkWYoyS1D/MdWFA==",
"license": "ISC"
},
"node_modules/magic-string": {
@@ -1590,9 +1590,9 @@
}
},
"node_modules/minimatch": {
- "version": "10.2.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz",
- "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==",
+ "version": "10.2.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
+ "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
@@ -1694,9 +1694,9 @@
}
},
"node_modules/tailwindcss": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.0.tgz",
- "integrity": "sha512-yYzTZ4++b7fNYxFfpnberEEKu43w44aqDMNM9MHMmcKuCH7lL8jJ4yJ7LGHv7rSwiqM0nkiobF9I6cLlpS2P7Q==",
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz",
+ "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==",
"dev": true,
"license": "MIT"
},
diff --git a/package.json b/package.json
index ff031aa..db07c4d 100644
--- a/package.json
+++ b/package.json
@@ -16,10 +16,10 @@
"watch": "npm run css:watch & npm run js:watch"
},
"devDependencies": {
- "@tailwindcss/cli": "^4.2.0",
+ "@tailwindcss/cli": "^4.2.1",
"@tailwindcss/typography": "^0.5.19",
"daisyui": "^5.5.19",
- "esbuild": "^0.27.3",
+ "esbuild": "^0.27.4",
"glob": "^13.0.6",
"tailwindcss": "^4.2"
},
@@ -27,6 +27,6 @@
"actor-typeahead": "^0.1.2",
"htmx-ext-json-enc": "^2.0.3",
"htmx.org": "^2.0.8",
- "lucide": "^0.575.0"
+ "lucide": "^0.577.0"
}
}
diff --git a/pkg/appview/db/queries.go b/pkg/appview/db/queries.go
index 69b7291..a5e759c 100644
--- a/pkg/appview/db/queries.go
+++ b/pkg/appview/db/queries.go
@@ -1252,6 +1252,25 @@ func UpdateFirehoseCursor(db DBTX, cursor int64) error {
return err
}
+// IsManifestReferenced checks if a manifest digest is referenced as a child of
+// any manifest list for the given user. Used to protect manifest list children
+// from auto-removal (they are untagged but still needed by their parent list).
+func IsManifestReferenced(db DBTX, did, digest string) (bool, error) {
+ var count int
+ err := db.QueryRow(`
+ SELECT COUNT(*) FROM manifest_references mr
+ JOIN manifests m ON mr.manifest_id = m.id
+ WHERE mr.digest = ? AND m.did = ?
+ LIMIT 1
+ `, digest, did).Scan(&count)
+
+ if err != nil {
+ return false, err
+ }
+
+ return count > 0, nil
+}
+
// IsManifestTagged checks if a manifest has any tags
func IsManifestTagged(db DBTX, did, repository, digest string) (bool, error) {
var count int
@@ -1295,6 +1314,49 @@ func GetManifestTags(db DBTX, did, repository, digest string) ([]string, error)
return tags, nil
}
+// GetUntaggedTopLevelManifestDigests returns digests of top-level manifests that have no tags.
+// Uses the same filtering logic as GetTopLevelManifests (manifest lists + orphaned single-arch).
+func GetUntaggedTopLevelManifestDigests(db DBTX, did, repository string) ([]string, error) {
+ rows, err := db.Query(`
+ WITH manifest_list_children AS (
+ SELECT DISTINCT mr.digest
+ FROM manifest_references mr
+ JOIN manifests m ON mr.manifest_id = m.id
+ WHERE m.did = ? AND m.repository = ?
+ )
+ SELECT m.digest
+ FROM manifests m
+ LEFT JOIN tags t ON m.digest = t.digest AND m.did = t.did AND m.repository = t.repository
+ WHERE m.did = ? AND m.repository = ?
+ AND (
+ m.media_type LIKE '%index%' OR m.media_type LIKE '%manifest.list%'
+ OR
+ m.digest NOT IN (SELECT digest FROM manifest_list_children WHERE digest IS NOT NULL)
+ )
+ GROUP BY m.id
+ HAVING COUNT(t.tag) = 0
+ `, did, repository, did, repository)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ var digests []string
+ for rows.Next() {
+ var digest string
+ if err := rows.Scan(&digest); err != nil {
+ return nil, err
+ }
+ digests = append(digests, digest)
+ }
+
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+
+ return digests, nil
+}
+
// GetAttestationDetails returns attestation manifests and their layers for a manifest list.
// Joins manifest_references (is_attestation=true) → manifests → layers.
func GetAttestationDetails(db DBTX, did, repository, manifestListDigest string) ([]AttestationDetail, error) {
@@ -1795,6 +1857,12 @@ func (h *HoldDIDDB) UpdateManifestHoldDID(did, oldHoldDID, newHoldDID string) (i
return UpdateManifestHoldDID(h.db, did, oldHoldDID, newHoldDID)
}
+// IsManifestReferenced checks if a digest is a child of any manifest list for the user.
+// Implements storage.ManifestReferenceChecker.
+func (h *HoldDIDDB) IsManifestReferenced(did, digest string) (bool, error) {
+ return IsManifestReferenced(h.db, did, digest)
+}
+
// RepoCardSortOrder specifies how repo cards should be sorted
type RepoCardSortOrder string
diff --git a/pkg/appview/db/queries_test.go b/pkg/appview/db/queries_test.go
index ee0faf4..9abb392 100644
--- a/pkg/appview/db/queries_test.go
+++ b/pkg/appview/db/queries_test.go
@@ -1307,3 +1307,70 @@ func TestDeleteUserData(t *testing.T) {
t.Errorf("Deleting non-existent user should not error, got: %v", err)
}
}
+
+func TestIsManifestReferenced(t *testing.T) {
+ db, err := InitDB(":memory:", LibsqlConfig{})
+ if err != nil {
+ t.Fatalf("Failed to init database: %v", err)
+ }
+ defer db.Close()
+
+ // Insert test user
+ if err := UpsertUser(db, &User{
+ DID: "did:plc:test123",
+ Handle: "testuser.bsky.social",
+ PDSEndpoint: "https://test.pds.example.com",
+ LastSeen: time.Now(),
+ }); err != nil {
+ t.Fatalf("Failed to insert user: %v", err)
+ }
+
+ // Insert a manifest list
+ _, err = db.Exec(`
+ INSERT INTO manifests (did, repository, digest, hold_endpoint, schema_version, media_type, created_at)
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ `, "did:plc:test123", "myapp", "sha256:indexabc", "did:web:hold.example.com", 2,
+ "application/vnd.oci.image.index.v1+json", time.Now())
+ if err != nil {
+ t.Fatalf("Failed to insert manifest list: %v", err)
+ }
+
+ var manifestID int64
+ db.QueryRow(`SELECT id FROM manifests WHERE digest = ?`, "sha256:indexabc").Scan(&manifestID)
+
+ // Insert a child manifest reference
+ _, err = db.Exec(`
+ INSERT INTO manifest_references (manifest_id, digest, media_type, size, platform_architecture, platform_os, reference_index)
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ `, manifestID, "sha256:childdef", "application/vnd.oci.image.manifest.v1+json", 1000, "amd64", "linux", 0)
+ if err != nil {
+ t.Fatalf("Failed to insert manifest reference: %v", err)
+ }
+
+ // Test 1: child digest should be referenced
+ referenced, err := IsManifestReferenced(db, "did:plc:test123", "sha256:childdef")
+ if err != nil {
+ t.Fatalf("IsManifestReferenced error: %v", err)
+ }
+ if !referenced {
+ t.Error("Expected sha256:childdef to be referenced as a manifest list child")
+ }
+
+ // Test 2: unrelated digest should NOT be referenced
+ referenced, err = IsManifestReferenced(db, "did:plc:test123", "sha256:unrelated")
+ if err != nil {
+ t.Fatalf("IsManifestReferenced error: %v", err)
+ }
+ if referenced {
+ t.Error("Expected sha256:unrelated to NOT be referenced")
+ }
+
+ // Test 3: same digest but different user should NOT be referenced
+ referenced, err = IsManifestReferenced(db, "did:plc:otheruser", "sha256:childdef")
+ if err != nil {
+ t.Fatalf("IsManifestReferenced error: %v", err)
+ }
+ if referenced {
+ t.Error("Expected sha256:childdef to NOT be referenced for different user")
+ }
+}
diff --git a/pkg/appview/handlers/images.go b/pkg/appview/handlers/images.go
index c31f2e0..49b639b 100644
--- a/pkg/appview/handlers/images.go
+++ b/pkg/appview/handlers/images.go
@@ -179,6 +179,74 @@ func (h *DeleteManifestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
w.WriteHeader(http.StatusOK)
}
+// deleteUntaggedRequest is the JSON body for delete untagged manifests requests
+type deleteUntaggedRequest struct {
+ Repo string `json:"repo"`
+}
+
+// DeleteUntaggedManifestsHandler handles bulk-deleting all untagged manifests in a repository
+type DeleteUntaggedManifestsHandler struct {
+ BaseUIHandler
+}
+
+func (h *DeleteUntaggedManifestsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ user := middleware.GetUser(r)
+ if user == nil {
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ var req deleteUntaggedRequest
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ http.Error(w, "Invalid request body", http.StatusBadRequest)
+ return
+ }
+
+ digests, err := db.GetUntaggedTopLevelManifestDigests(h.DB, user.DID, req.Repo)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("Failed to query untagged manifests: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ if len(digests) == 0 {
+ render.JSON(w, r, map[string]int{"deleted": 0})
+ return
+ }
+
+ pdsClient := atproto.NewClientWithSessionProvider(user.PDSEndpoint, user.DID, h.Refresher)
+
+ deleted := 0
+ for _, digest := range digests {
+ rkey := strings.TrimPrefix(digest, "sha256:")
+
+ if err := pdsClient.DeleteRecord(r.Context(), atproto.ManifestCollection, rkey); err != nil {
+ if handleOAuthError(r.Context(), h.Refresher, user.DID, err) {
+ http.Error(w, "Authentication failed, please log in again", http.StatusUnauthorized)
+ return
+ }
+ render.Status(r, http.StatusInternalServerError)
+ render.JSON(w, r, map[string]any{
+ "error": fmt.Sprintf("Failed to delete manifest %s from PDS: %v", digest, err),
+ "deleted": deleted,
+ })
+ return
+ }
+
+ if err := db.DeleteManifest(h.DB, user.DID, req.Repo, digest); err != nil {
+ render.Status(r, http.StatusInternalServerError)
+ render.JSON(w, r, map[string]any{
+ "error": fmt.Sprintf("Failed to delete manifest %s from cache: %v", digest, err),
+ "deleted": deleted,
+ })
+ return
+ }
+
+ deleted++
+ }
+
+ render.JSON(w, r, map[string]int{"deleted": deleted})
+}
+
// UploadAvatarHandler handles uploading/updating a repository avatar
type UploadAvatarHandler struct {
BaseUIHandler
diff --git a/pkg/appview/handlers/settings.go b/pkg/appview/handlers/settings.go
index 7830400..4e536da 100644
--- a/pkg/appview/handlers/settings.go
+++ b/pkg/appview/handlers/settings.go
@@ -132,10 +132,11 @@ func (h *SettingsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
PageData
Meta *PageMeta
Profile struct {
- Handle string
- DID string
- PDSEndpoint string
- DefaultHold string
+ Handle string
+ DID string
+ PDSEndpoint string
+ DefaultHold string
+ AutoRemoveUntagged bool
}
ActiveHold *HoldDisplay
OtherHolds []HoldDisplay
@@ -156,6 +157,7 @@ func (h *SettingsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
data.Profile.DID = user.DID
data.Profile.PDSEndpoint = user.PDSEndpoint
data.Profile.DefaultHold = profile.DefaultHold
+ data.Profile.AutoRemoveUntagged = profile.AutoRemoveUntagged
if err := h.Templates.ExecuteTemplate(w, "settings", data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -381,6 +383,40 @@ func (h *UpdateDefaultHoldHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
}
}
+// UpdateAutoRemoveUntaggedHandler handles toggling the auto-remove-untagged setting
+type UpdateAutoRemoveUntaggedHandler struct {
+ BaseUIHandler
+}
+
+func (h *UpdateAutoRemoveUntaggedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ user := middleware.GetUser(r)
+ if user == nil {
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ // Create ATProto client with session provider
+ client := atproto.NewClientWithSessionProvider(user.PDSEndpoint, user.DID, h.Refresher)
+
+ // Fetch existing profile
+ profile, err := storage.GetProfile(r.Context(), client)
+ if err != nil || profile == nil {
+ http.Error(w, "Failed to fetch profile", http.StatusInternalServerError)
+ return
+ }
+
+ // Toggle the setting (checkbox sends value when checked, absent when unchecked)
+ profile.AutoRemoveUntagged = !profile.AutoRemoveUntagged
+ profile.UpdatedAt = time.Now()
+
+ if err := storage.UpdateProfile(r.Context(), client, profile); err != nil {
+ http.Error(w, "Failed to update profile: "+err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusNoContent)
+}
+
// refreshCaptainRecord fetches a hold's captain record via XRPC and caches it locally.
// This ensures badge tiers and other captain metadata are available immediately
// without waiting for Jetstream or the next backfill cycle.
diff --git a/pkg/appview/jetstream/processor.go b/pkg/appview/jetstream/processor.go
index 3023f55..6f8049b 100644
--- a/pkg/appview/jetstream/processor.go
+++ b/pkg/appview/jetstream/processor.go
@@ -772,6 +772,13 @@ func (p *Processor) ProcessStats(ctx context.Context, holdDID string, recordData
return fmt.Errorf("failed to unmarshal stats record: %w", err)
}
+ // Ensure the owner user exists in the users table (repository_stats has FK to users.did)
+ if !isDelete && statsRecord.OwnerDID != "" {
+ if err := p.EnsureUserExists(ctx, statsRecord.OwnerDID); err != nil {
+ return fmt.Errorf("failed to ensure stats owner user exists: %w", err)
+ }
+ }
+
if isDelete {
// Delete from in-memory cache
p.statsCache.Delete(holdDID, statsRecord.OwnerDID, statsRecord.Repository)
diff --git a/pkg/appview/middleware/registry.go b/pkg/appview/middleware/registry.go
index e02c089..de25b37 100644
--- a/pkg/appview/middleware/registry.go
+++ b/pkg/appview/middleware/registry.go
@@ -170,10 +170,11 @@ func (vc *validationCache) getOrFetch(ctx context.Context, cacheKey string, fetc
// These are set by main.go during startup and copied into NamespaceResolver instances.
// After initialization, request handling uses the NamespaceResolver's instance fields.
var (
- globalRefresher *oauth.Refresher
- globalDatabase storage.HoldDIDLookup
- globalAuthorizer auth.HoldAuthorizer
- globalWebhookDispatcher storage.PushWebhookDispatcher
+ globalRefresher *oauth.Refresher
+ globalDatabase storage.HoldDIDLookup
+ globalAuthorizer auth.HoldAuthorizer
+ globalWebhookDispatcher storage.PushWebhookDispatcher
+ globalManifestRefChecker storage.ManifestReferenceChecker
)
// SetGlobalRefresher sets the OAuth refresher instance during initialization
@@ -188,6 +189,11 @@ func SetGlobalDatabase(database storage.HoldDIDLookup) {
globalDatabase = database
}
+// SetGlobalManifestRefChecker sets the manifest reference checker during initialization
+func SetGlobalManifestRefChecker(checker storage.ManifestReferenceChecker) {
+ globalManifestRefChecker = checker
+}
+
// SetGlobalAuthorizer sets the authorizer instance during initialization
// Must be called before the registry starts serving requests
func SetGlobalAuthorizer(authorizer auth.HoldAuthorizer) {
@@ -216,15 +222,16 @@ func init() {
// NamespaceResolver wraps a namespace and resolves names
type NamespaceResolver struct {
distribution.Namespace
- defaultHoldDID string // Default hold DID (e.g., "did:web:hold01.atcr.io")
- baseURL string // Base URL for error messages (e.g., "https://atcr.io")
- testMode bool // If true, fallback to default hold when user's hold is unreachable
- refresher *oauth.Refresher // OAuth session manager (copied from global on init)
- database storage.HoldDIDLookup // Database for hold DID lookups (copied from global on init)
- authorizer auth.HoldAuthorizer // Hold authorization (copied from global on init)
- webhookDispatcher storage.PushWebhookDispatcher // Push webhook dispatcher (copied from global on init)
- validationCache *validationCache // Request-level service token cache
- readmeFetcher *readme.Fetcher // README fetcher for repo pages
+ defaultHoldDID string // Default hold DID (e.g., "did:web:hold01.atcr.io")
+ baseURL string // Base URL for error messages (e.g., "https://atcr.io")
+ testMode bool // If true, fallback to default hold when user's hold is unreachable
+ refresher *oauth.Refresher // OAuth session manager (copied from global on init)
+ database storage.HoldDIDLookup // Database for hold DID lookups (copied from global on init)
+ authorizer auth.HoldAuthorizer // Hold authorization (copied from global on init)
+ webhookDispatcher storage.PushWebhookDispatcher // Push webhook dispatcher (copied from global on init)
+ manifestRefChecker storage.ManifestReferenceChecker // Manifest reference checker (copied from global on init)
+ validationCache *validationCache // Request-level service token cache
+ readmeFetcher *readme.Fetcher // README fetcher for repo pages
}
// initATProtoResolver initializes the name resolution middleware
@@ -251,16 +258,17 @@ func initATProtoResolver(ctx context.Context, ns distribution.Namespace, _ drive
// Copy shared services from globals into the instance
// This avoids accessing globals during request handling
return &NamespaceResolver{
- Namespace: ns,
- defaultHoldDID: defaultHoldDID,
- baseURL: baseURL,
- testMode: testMode,
- refresher: globalRefresher,
- database: globalDatabase,
- authorizer: globalAuthorizer,
- webhookDispatcher: globalWebhookDispatcher,
- validationCache: newValidationCache(),
- readmeFetcher: readme.NewFetcher(),
+ Namespace: ns,
+ defaultHoldDID: defaultHoldDID,
+ baseURL: baseURL,
+ testMode: testMode,
+ refresher: globalRefresher,
+ database: globalDatabase,
+ authorizer: globalAuthorizer,
+ webhookDispatcher: globalWebhookDispatcher,
+ manifestRefChecker: globalManifestRefChecker,
+ validationCache: newValidationCache(),
+ readmeFetcher: readme.NewFetcher(),
}, nil
}
@@ -297,7 +305,8 @@ func (nr *NamespaceResolver) Repository(ctx context.Context, name reference.Name
slog.Debug("Resolved identity", "component", "registry/middleware", "did", did, "pds", pdsEndpoint, "handle", handle)
// Query for hold DID - either user's hold or default hold service
- holdDID := nr.findHoldDID(ctx, did, pdsEndpoint)
+ // Also returns the sailor profile so we can read preferences (e.g. AutoRemoveUntagged)
+ holdDID, sailorProfile := nr.findHoldDIDAndProfile(ctx, did, pdsEndpoint)
if holdDID == "" {
// This is a fatal configuration error - registry cannot function without a hold service
return nil, fmt.Errorf("no hold DID configured: ensure default_hold_did is set in middleware config")
@@ -476,22 +485,24 @@ func (nr *NamespaceResolver) Repository(ctx context.Context, name reference.Name
// 3. The refresher already caches sessions efficiently (in-memory + DB)
// 4. Caching the repository with a stale ATProtoClient causes refresh token errors
registryCtx := &storage.RegistryContext{
- DID: did,
- Handle: handle,
- HoldDID: holdDID,
- HoldURL: holdURL,
- PDSEndpoint: pdsEndpoint,
- Repository: repositoryName,
- ServiceToken: serviceToken, // Cached service token from puller's PDS
- ATProtoClient: atprotoClient,
- AuthMethod: authMethod, // Auth method from JWT token
- PullerDID: pullerDID, // Authenticated user making the request
- PullerPDSEndpoint: pullerPDSEndpoint, // Puller's PDS for service token refresh
- Database: nr.database,
- Authorizer: nr.authorizer,
- Refresher: nr.refresher,
- ReadmeFetcher: nr.readmeFetcher,
- WebhookDispatcher: nr.webhookDispatcher,
+ DID: did,
+ Handle: handle,
+ HoldDID: holdDID,
+ HoldURL: holdURL,
+ PDSEndpoint: pdsEndpoint,
+ Repository: repositoryName,
+ ServiceToken: serviceToken, // Cached service token from puller's PDS
+ ATProtoClient: atprotoClient,
+ AuthMethod: authMethod, // Auth method from JWT token
+ PullerDID: pullerDID, // Authenticated user making the request
+ PullerPDSEndpoint: pullerPDSEndpoint, // Puller's PDS for service token refresh
+ AutoRemoveUntagged: sailorProfile != nil && sailorProfile.AutoRemoveUntagged,
+ Database: nr.database,
+ Authorizer: nr.authorizer,
+ Refresher: nr.refresher,
+ ReadmeFetcher: nr.readmeFetcher,
+ WebhookDispatcher: nr.webhookDispatcher,
+ ManifestRefChecker: nr.manifestRefChecker,
}
return storage.NewRoutingRepository(repo, registryCtx), nil
@@ -512,12 +523,14 @@ func (nr *NamespaceResolver) BlobStatter() distribution.BlobStatter {
return nr.Namespace.BlobStatter()
}
-// findHoldDID determines which hold DID to use for blob storage
+// findHoldDIDAndProfile determines which hold DID to use for blob storage and
+// returns the user's sailor profile (if available) for reading preferences like
+// AutoRemoveUntagged without an extra PDS call.
// Priority order:
// 1. User's sailor profile defaultHold (if set)
// 2. AppView's default hold DID
// Returns a hold DID (e.g., "did:web:hold01.atcr.io"), or empty string if none configured
-func (nr *NamespaceResolver) findHoldDID(ctx context.Context, did, pdsEndpoint string) string {
+func (nr *NamespaceResolver) findHoldDIDAndProfile(ctx context.Context, did, pdsEndpoint string) (string, *atproto.SailorProfileRecord) {
// Create ATProto client (without auth - reading public records)
client := atproto.NewClient(pdsEndpoint, did, "")
@@ -533,16 +546,16 @@ func (nr *NamespaceResolver) findHoldDID(ctx context.Context, did, pdsEndpoint s
// In test mode, verify it's reachable before using it
if nr.testMode {
if nr.isHoldReachable(ctx, profile.DefaultHold) {
- return profile.DefaultHold
+ return profile.DefaultHold, profile
}
slog.Debug("User's defaultHold unreachable, falling back to default", "component", "registry/middleware/testmode", "default_hold", profile.DefaultHold)
- return nr.defaultHoldDID
+ return nr.defaultHoldDID, profile
}
- return profile.DefaultHold
+ return profile.DefaultHold, profile
}
// No profile defaultHold - use AppView default
- return nr.defaultHoldDID
+ return nr.defaultHoldDID, profile
}
// resolveSuccessor checks if a hold has declared a successor and returns it.
diff --git a/pkg/appview/middleware/registry_test.go b/pkg/appview/middleware/registry_test.go
index f2e6235..80d9bf9 100644
--- a/pkg/appview/middleware/registry_test.go
+++ b/pkg/appview/middleware/registry_test.go
@@ -160,7 +160,7 @@ func TestFindHoldDID_DefaultFallback(t *testing.T) {
}
ctx := context.Background()
- holdDID := resolver.findHoldDID(ctx, "did:plc:test123", mockPDS.URL)
+ holdDID, _ := resolver.findHoldDIDAndProfile(ctx, "did:plc:test123", mockPDS.URL)
assert.Equal(t, "did:web:default.atcr.io", holdDID, "should fall back to default hold DID")
}
@@ -188,7 +188,7 @@ func TestFindHoldDID_SailorProfile(t *testing.T) {
}
ctx := context.Background()
- holdDID := resolver.findHoldDID(ctx, "did:plc:test123", mockPDS.URL)
+ holdDID, _ := resolver.findHoldDIDAndProfile(ctx, "did:plc:test123", mockPDS.URL)
assert.Equal(t, "did:web:user.hold.io", holdDID, "should use sailor profile's defaultHold")
}
@@ -215,7 +215,7 @@ func TestFindHoldDID_Priority(t *testing.T) {
}
ctx := context.Background()
- holdDID := resolver.findHoldDID(ctx, "did:plc:test123", mockPDS.URL)
+ holdDID, _ := resolver.findHoldDIDAndProfile(ctx, "did:plc:test123", mockPDS.URL)
// Profile should take priority over hold records and default
assert.Equal(t, "did:web:profile.hold.io", holdDID, "should prioritize sailor profile over hold records")
@@ -244,7 +244,7 @@ func TestFindHoldDID_TestModeFallback(t *testing.T) {
}
ctx := context.Background()
- holdDID := resolver.findHoldDID(ctx, "did:plc:test123", mockPDS.URL)
+ holdDID, _ := resolver.findHoldDIDAndProfile(ctx, "did:plc:test123", mockPDS.URL)
// In test mode with unreachable hold, should fall back to default
assert.Equal(t, "did:web:default.atcr.io", holdDID, "should fall back to default in test mode when hold unreachable")
diff --git a/pkg/appview/public/js/bundle.min.js b/pkg/appview/public/js/bundle.min.js
index 739403f..7f74411 100644
--- a/pkg/appview/public/js/bundle.min.js
+++ b/pkg/appview/public/js/bundle.min.js
@@ -1,4 +1,4 @@
-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(/
]*)?>[\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(''+t+"");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=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"&&s--}a0;){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=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;r0?(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",""),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(;n0}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;m0?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;i0;){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;a0?(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", "+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;thtmx.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=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;nt.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;a0?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.${t}{opacity:0;visibility: hidden} .${n} .${t}, .${n}.${t}{opacity:1;visibility: visible;transition: opacity 200ms ease-in}`)}}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=`
+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(/]*)?>[\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(''+t+"");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=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"&&s--}a0;){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=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;r0?(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",""),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(;n0}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;m0?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;i0;){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;a0?(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", "+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;thtmx.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=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;nt.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;a0?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.${t}{opacity:0;visibility: hidden} .${n} .${t}, .${n}.${t}{opacity:1;visibility: visible;transition: opacity 200ms ease-in}`)}}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=`
@@ -92,4 +92,4 @@ var ae=(function(){"use strict";let htmx={onLoad:null,process:null,on:null,off:n
-`;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=' 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='
',n.showModal();try{let o=await fetch(`/api/vuln-details?digest=${encodeURIComponent(e)}&holdEndpoint=${encodeURIComponent(t)}`);r.innerHTML=await o.text()}catch{r.innerHTML='Failed to load vulnerability details
'}}}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=`${e}`,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;
+`;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=' 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='
',n.showModal();try{let o=await fetch(`/api/vuln-details?digest=${encodeURIComponent(e)}&holdEndpoint=${encodeURIComponent(t)}`);r.innerHTML=await o.text()}catch{r.innerHTML='Failed to load vulnerability details
'}}}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=`${e}`,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;
diff --git a/pkg/appview/routes/routes.go b/pkg/appview/routes/routes.go
index 51001ea..29782b8 100644
--- a/pkg/appview/routes/routes.go
+++ b/pkg/appview/routes/routes.go
@@ -158,6 +158,7 @@ func RegisterUIRoutes(router chi.Router, deps UIDependencies) {
r.Get("/settings", (&uihandlers.SettingsHandler{BaseUIHandler: base}).ServeHTTP)
r.Get("/api/storage", (&uihandlers.StorageHandler{BaseUIHandler: base}).ServeHTTP)
r.Post("/api/profile/default-hold", (&uihandlers.UpdateDefaultHoldHandler{BaseUIHandler: base}).ServeHTTP)
+ r.Post("/api/profile/auto-remove-untagged", (&uihandlers.UpdateAutoRemoveUntaggedHandler{BaseUIHandler: base}).ServeHTTP)
// Subscription management
r.Get("/settings/subscription/checkout", (&uihandlers.SubscriptionCheckoutHandler{BaseUIHandler: base}).ServeHTTP)
@@ -165,6 +166,7 @@ func RegisterUIRoutes(router chi.Router, deps UIDependencies) {
r.Delete("/api/tags", (&uihandlers.DeleteTagHandler{BaseUIHandler: base}).ServeHTTP)
r.Delete("/api/manifests", (&uihandlers.DeleteManifestHandler{BaseUIHandler: base}).ServeHTTP)
+ r.Delete("/api/manifests/untagged", (&uihandlers.DeleteUntaggedManifestsHandler{BaseUIHandler: base}).ServeHTTP)
r.Post("/api/avatar", (&uihandlers.UploadAvatarHandler{BaseUIHandler: base}).ServeHTTP)
// Webhook management
diff --git a/pkg/appview/server.go b/pkg/appview/server.go
index a19df95..137b60d 100644
--- a/pkg/appview/server.go
+++ b/pkg/appview/server.go
@@ -231,9 +231,10 @@ func NewAppViewServer(cfg *Config, branding *BrandingOverrides) (*AppViewServer,
// Set global refresher for middleware
middleware.SetGlobalRefresher(s.Refresher)
- // Set global database for hold DID lookups
+ // Set global database for hold DID lookups and manifest reference checks
holdDIDDB := db.NewHoldDIDDB(s.Database)
middleware.SetGlobalDatabase(holdDIDDB)
+ middleware.SetGlobalManifestRefChecker(holdDIDDB)
// Create RemoteHoldAuthorizer for hold authorization with caching
s.HoldAuthorizer = auth.NewRemoteHoldAuthorizer(s.Database, testMode)
diff --git a/pkg/appview/src/js/app.js b/pkg/appview/src/js/app.js
index 8491bda..0406abc 100644
--- a/pkg/appview/src/js/app.js
+++ b/pkg/appview/src/js/app.js
@@ -352,6 +352,45 @@ async function confirmManifestDelete(repository, digest, sanitizedId) {
}
}
+// Delete all untagged manifests in a repository
+async function deleteUntaggedManifests(repository) {
+ const confirmBtn = document.getElementById('confirm-untagged-delete-btn');
+ const originalText = confirmBtn.textContent;
+
+ try {
+ confirmBtn.disabled = true;
+ confirmBtn.textContent = 'Deleting...';
+
+ const response = await fetch('/api/manifests/untagged', {
+ method: 'DELETE',
+ credentials: 'include',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ repo: repository }),
+ });
+
+ const data = await response.json();
+
+ if (response.ok) {
+ document.getElementById('untagged-delete-modal').close();
+ showToast(`Deleted ${data.deleted} untagged manifest(s)`, 'success');
+ if (data.deleted > 0) {
+ location.reload();
+ }
+ confirmBtn.disabled = false;
+ confirmBtn.textContent = originalText;
+ } else {
+ alert(`Failed to delete untagged manifests: ${data.error || 'Unknown error'}`);
+ confirmBtn.disabled = false;
+ confirmBtn.textContent = originalText;
+ }
+ } catch (err) {
+ console.error('Error deleting untagged manifests:', err);
+ alert(`Error: ${err.message}`);
+ confirmBtn.disabled = false;
+ confirmBtn.textContent = originalText;
+ }
+}
+
// Remove a manifest element from the DOM
function removeManifestElement(sanitizedId) {
const element = document.getElementById(`manifest-${sanitizedId}`);
@@ -758,6 +797,7 @@ window.closeSearch = closeSearch;
window.copyToClipboard = copyToClipboard;
window.toggleOfflineManifests = toggleOfflineManifests;
window.deleteManifest = deleteManifest;
+window.deleteUntaggedManifests = deleteUntaggedManifests;
window.closeManifestDeleteModal = closeManifestDeleteModal;
window.openVulnDetails = openVulnDetails;
window.showToast = showToast;
diff --git a/pkg/appview/storage/context.go b/pkg/appview/storage/context.go
index 1c7d9fd..ea1d98e 100644
--- a/pkg/appview/storage/context.go
+++ b/pkg/appview/storage/context.go
@@ -15,6 +15,13 @@ type PushWebhookDispatcher interface {
DispatchForPush(ctx context.Context, event PushWebhookEvent)
}
+// ManifestReferenceChecker checks if a manifest digest is referenced as a child
+// of a manifest list (multi-arch image). Used to protect manifest list children
+// from auto-removal when their parent list is still tagged.
+type ManifestReferenceChecker interface {
+ IsManifestReferenced(did, digest string) (bool, error)
+}
+
// PushWebhookEvent contains the data needed to dispatch a push webhook.
type PushWebhookEvent struct {
OwnerDID string
@@ -53,10 +60,14 @@ type RegistryContext struct {
PullerDID string // Puller's DID - who is making the request (from JWT Subject)
PullerPDSEndpoint string // Puller's PDS endpoint URL
+ // Per-request user preferences
+ AutoRemoveUntagged bool // Whether to auto-delete untagged manifests on tag overwrite
+
// Shared services (same for all requests)
- Database HoldDIDLookup // Database for hold DID lookups
- Authorizer auth.HoldAuthorizer // Hold access authorization
- Refresher *oauth.Refresher // OAuth session manager
- ReadmeFetcher *readme.Fetcher // README fetcher for repo pages
- WebhookDispatcher PushWebhookDispatcher // Push webhook dispatcher (nil if not configured)
+ Database HoldDIDLookup // Database for hold DID lookups
+ Authorizer auth.HoldAuthorizer // Hold access authorization
+ Refresher *oauth.Refresher // OAuth session manager
+ ReadmeFetcher *readme.Fetcher // README fetcher for repo pages
+ WebhookDispatcher PushWebhookDispatcher // Push webhook dispatcher (nil if not configured)
+ ManifestRefChecker ManifestReferenceChecker // Checks if digest is a manifest list child (nil-safe)
}
diff --git a/pkg/appview/storage/manifest_store.go b/pkg/appview/storage/manifest_store.go
index 05394d7..73e4829 100644
--- a/pkg/appview/storage/manifest_store.go
+++ b/pkg/appview/storage/manifest_store.go
@@ -210,11 +210,25 @@ func (s *ManifestStore) Put(ctx context.Context, manifest distribution.Manifest,
// Also handle tag if specified
var tag string
+ var oldDigest string // Track previous digest for auto-remove-untagged cleanup
for _, option := range options {
if tagOpt, ok := option.(distribution.WithTagOption); ok {
tag = tagOpt.Tag
- tagRecord := atproto.NewTagRecord(s.ctx.ATProtoClient.DID(), s.ctx.Repository, tag, dgst.String())
tagRKey := atproto.RepositoryTagToRKey(s.ctx.Repository, tag)
+
+ // Before overwriting, capture the old digest so we can clean it up
+ if s.ctx.AutoRemoveUntagged {
+ if oldRec, getErr := s.ctx.ATProtoClient.GetRecord(ctx, atproto.TagCollection, tagRKey); getErr == nil {
+ var oldTag atproto.TagRecord
+ if json.Unmarshal(oldRec.Value, &oldTag) == nil {
+ if d, dErr := oldTag.GetManifestDigest(); dErr == nil {
+ oldDigest = d
+ }
+ }
+ }
+ }
+
+ tagRecord := atproto.NewTagRecord(s.ctx.ATProtoClient.DID(), s.ctx.Repository, tag, dgst.String())
_, err = s.ctx.ATProtoClient.PutRecord(ctx, atproto.TagCollection, tagRKey, tagRecord)
if err != nil {
return "", fmt.Errorf("failed to store tag in ATProto: %w", err)
@@ -222,6 +236,12 @@ func (s *ManifestStore) Put(ctx context.Context, manifest distribution.Manifest,
}
}
+ // Auto-remove untagged manifests: if the tag pointed to a different digest before,
+ // check if the old manifest is now untagged and clean it up
+ if s.ctx.AutoRemoveUntagged && oldDigest != "" && oldDigest != dgst.String() {
+ go s.cleanupUntaggedManifest(oldDigest)
+ }
+
// Notify hold about manifest push (for layer tracking, Bluesky posts, and stats)
// Do this asynchronously to avoid blocking the push
// Note: We notify even for tagless pushes (e.g., buildx platform manifests) to create layer records
@@ -697,3 +717,73 @@ func detectImageMimeType(contentType, url string) string {
// Unknown or unsupported type - reject
return ""
}
+
+// cleanupUntaggedManifest checks if a manifest digest is now untagged and deletes it.
+// Runs asynchronously after a tag overwrite. Protects manifest list children.
+func (s *ManifestStore) cleanupUntaggedManifest(oldDigest string) {
+ defer func() {
+ if r := recover(); r != nil {
+ slog.Error("Panic in cleanupUntaggedManifest", "panic", r)
+ }
+ }()
+
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+ defer cancel()
+
+ // Re-check: list all tags for this repository and see if any still point to the old digest
+ records, err := s.ctx.ATProtoClient.ListRecords(ctx, atproto.TagCollection, 100)
+ if err != nil {
+ slog.Warn("Auto-remove: failed to list tags", "error", err)
+ return
+ }
+
+ for _, rec := range records {
+ var tagRecord atproto.TagRecord
+ if json.Unmarshal(rec.Value, &tagRecord) != nil {
+ continue
+ }
+ if tagRecord.Repository != s.ctx.Repository {
+ continue
+ }
+ d, dErr := tagRecord.GetManifestDigest()
+ if dErr == nil && d == oldDigest {
+ // Still tagged by another tag — do not delete
+ slog.Debug("Auto-remove: manifest still tagged, skipping",
+ "digest", oldDigest, "tag", tagRecord.Tag)
+ return
+ }
+ }
+
+ // Check if this digest is a child of a manifest list (multi-arch)
+ if s.ctx.ManifestRefChecker != nil {
+ referenced, err := s.ctx.ManifestRefChecker.IsManifestReferenced(s.ctx.DID, oldDigest)
+ if err != nil {
+ slog.Warn("Auto-remove: failed to check manifest references", "digest", oldDigest, "error", err)
+ return
+ }
+ if referenced {
+ slog.Debug("Auto-remove: manifest is a manifest list child, skipping",
+ "digest", oldDigest)
+ return
+ }
+ }
+
+ // Safe to delete — manifest is untagged and not referenced by any manifest list
+ dgst, err := digest.Parse(oldDigest)
+ if err != nil {
+ slog.Warn("Auto-remove: invalid digest", "digest", oldDigest, "error", err)
+ return
+ }
+
+ rkey := digestToRKey(dgst)
+ if err := s.ctx.ATProtoClient.DeleteRecord(ctx, atproto.ManifestCollection, rkey); err != nil {
+ slog.Warn("Auto-remove: failed to delete untagged manifest", "digest", oldDigest, "error", err)
+ return
+ }
+
+ slog.Info("Auto-removed untagged manifest",
+ "component", "manifest-store",
+ "digest", oldDigest,
+ "repository", s.ctx.Repository,
+ "did", s.ctx.DID)
+}
diff --git a/pkg/appview/templates/pages/repository.html b/pkg/appview/templates/pages/repository.html
index b6e0439..fb32ecc 100644
--- a/pkg/appview/templates/pages/repository.html
+++ b/pkg/appview/templates/pages/repository.html
@@ -200,10 +200,19 @@
{{ if .Manifests }}
@@ -313,6 +322,23 @@
+
+
+
diff --git a/pkg/atproto/lexicon.go b/pkg/atproto/lexicon.go
index 67b538d..9ff8059 100644
--- a/pkg/atproto/lexicon.go
+++ b/pkg/atproto/lexicon.go
@@ -335,6 +335,11 @@ type SailorProfileRecord struct {
// If null/empty, user has opted out of defaults
DefaultHold string `json:"defaultHold,omitempty"`
+ // AutoRemoveUntagged controls whether untagged manifests are automatically
+ // cleaned up. When true, manifests that lose all tags (e.g., after a tag
+ // overwrite) are deleted from PDS, and their layers are cleaned up by hold GC.
+ AutoRemoveUntagged bool `json:"autoRemoveUntagged,omitempty"`
+
// CreatedAt timestamp
CreatedAt time.Time `json:"createdAt"`
diff --git a/pkg/atproto/relays.go b/pkg/atproto/relays.go
index 6755fb4..92c9f53 100644
--- a/pkg/atproto/relays.go
+++ b/pkg/atproto/relays.go
@@ -35,6 +35,7 @@ var KnownRelays = []KnownRelay{
{Name: "Feeds Blue", URL: "https://relay.feeds.blue"},
{Name: "Waow", URL: "https://relay.waow.tech"},
{Name: "Zlay", URL: "https://zlay.waow.tech"},
+ {Name: "Bassh", URL: "https://relay.bas.sh"},
}
// RelayHTTPError indicates the relay responded with a non-200 status code.
diff --git a/pkg/hold/gc/gc.go b/pkg/hold/gc/gc.go
index af494ba..dbdd72d 100644
--- a/pkg/hold/gc/gc.go
+++ b/pkg/hold/gc/gc.go
@@ -578,13 +578,38 @@ func (gc *GarbageCollector) analyzeRecords(ctx context.Context) (*analysisResult
result.usersChecked++
gc.setProgress("manifests", fmt.Sprintf("Fetching manifests (%d/%d users)", result.usersChecked, totalUsers), gc.operationType)
- manifests, err := gc.fetchUserManifests(ctx, did)
+ // Resolve PDS endpoint (needed for both manifests and optional profile/tag fetches)
+ pdsEndpoint, err := atproto.ResolveDIDToPDS(ctx, did)
+ if err != nil {
+ gc.logger.Warn("Failed to resolve PDS for user, treating their records as referenced",
+ "did", did, "error", err)
+ continue
+ }
+
+ manifests, err := gc.fetchUserManifestsFromEndpoint(ctx, did, pdsEndpoint, gc.pds.DID())
if err != nil {
gc.logger.Warn("Failed to fetch manifests for user, treating their records as referenced",
"did", did, "error", err)
continue
}
+ // If user opted into auto-remove-untagged, filter out untagged manifests
+ // so their layers are treated as orphaned and cleaned up
+ if profile, _ := gc.fetchUserProfile(ctx, pdsEndpoint, did); profile != nil && profile.AutoRemoveUntagged {
+ taggedDigests, tagErr := gc.fetchUserTags(ctx, pdsEndpoint, did)
+ if tagErr != nil {
+ gc.logger.Warn("Failed to fetch tags for auto-remove, keeping all manifests referenced",
+ "did", did, "error", tagErr)
+ } else {
+ before := len(manifests)
+ manifests = gc.filterUntaggedManifests(manifests, taggedDigests)
+ if filtered := before - len(manifests); filtered > 0 {
+ gc.logger.Info("Filtered untagged manifests for auto-remove user",
+ "did", did, "filtered", filtered, "remaining", len(manifests))
+ }
+ }
+ }
+
fetchedUsers[did] = true
for _, m := range manifests {
result.manifestsChecked++
@@ -835,18 +860,153 @@ func (gc *GarbageCollector) discoverUserDIDs(ctx context.Context) ([]string, err
return dids, nil
}
-// fetchUserManifests fetches all io.atcr.manifest records for a user from their PDS,
-// filtered to manifests that reference this hold.
-func (gc *GarbageCollector) fetchUserManifests(ctx context.Context, userDID string) ([]*manifestInfo, error) {
- pdsEndpoint, err := atproto.ResolveDIDToPDS(ctx, userDID)
+// fetchUserProfile fetches the sailor profile from a user's PDS.
+// Returns nil without error if the profile doesn't exist.
+func (gc *GarbageCollector) fetchUserProfile(ctx context.Context, pdsEndpoint, userDID string) (*atproto.SailorProfileRecord, error) {
+ client := &http.Client{Timeout: 10 * time.Second}
+
+ reqURL := fmt.Sprintf("%s/xrpc/com.atproto.repo.getRecord?repo=%s&collection=%s&rkey=self",
+ pdsEndpoint, userDID, atproto.SailorProfileCollection)
+
+ req, err := http.NewRequestWithContext(ctx, "GET", reqURL, nil)
if err != nil {
- return nil, fmt.Errorf("resolve PDS for %s: %w", userDID, err)
+ return nil, fmt.Errorf("create request: %w", err)
+ }
+
+ resp, err := client.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("http request: %w", err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode == http.StatusBadRequest || resp.StatusCode == http.StatusNotFound {
+ return nil, nil // No profile
+ }
+ if resp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("getRecord returned status %d", resp.StatusCode)
+ }
+
+ var envelope struct {
+ Value json.RawMessage `json:"value"`
+ }
+ if err := json.NewDecoder(resp.Body).Decode(&envelope); err != nil {
+ return nil, fmt.Errorf("decode response: %w", err)
+ }
+
+ var profile atproto.SailorProfileRecord
+ if err := json.Unmarshal(envelope.Value, &profile); err != nil {
+ return nil, fmt.Errorf("unmarshal profile: %w", err)
+ }
+
+ return &profile, nil
+}
+
+// fetchUserTags fetches all tag records from a user's PDS.
+// Returns a map of digest → true for all tagged manifest digests.
+func (gc *GarbageCollector) fetchUserTags(ctx context.Context, pdsEndpoint, userDID string) (map[string]bool, error) {
+ tagged := make(map[string]bool)
+ cursor := ""
+ client := &http.Client{Timeout: 30 * time.Second}
+
+ for {
+ reqURL := fmt.Sprintf("%s/xrpc/com.atproto.repo.listRecords?repo=%s&collection=%s&limit=100",
+ pdsEndpoint, userDID, atproto.TagCollection)
+ if cursor != "" {
+ reqURL += "&cursor=" + cursor
+ }
+
+ req, err := http.NewRequestWithContext(ctx, "GET", reqURL, nil)
+ if err != nil {
+ return nil, fmt.Errorf("create request: %w", err)
+ }
+
+ resp, err := client.Do(req)
+ if err != nil {
+ return nil, fmt.Errorf("http request: %w", err)
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ resp.Body.Close()
+ return nil, fmt.Errorf("listRecords returned status %d", resp.StatusCode)
+ }
+
+ var listResult struct {
+ Records []struct {
+ Value json.RawMessage `json:"value"`
+ } `json:"records"`
+ Cursor string `json:"cursor,omitempty"`
+ }
+
+ if err := json.NewDecoder(resp.Body).Decode(&listResult); err != nil {
+ resp.Body.Close()
+ return nil, fmt.Errorf("decode response: %w", err)
+ }
+ resp.Body.Close()
+
+ for _, rec := range listResult.Records {
+ var tag atproto.TagRecord
+ if err := json.Unmarshal(rec.Value, &tag); err != nil {
+ continue
+ }
+ if d, err := tag.GetManifestDigest(); err == nil {
+ tagged[d] = true
+ }
+ }
+
+ if listResult.Cursor == "" {
+ break
+ }
+ cursor = listResult.Cursor
+ }
+
+ return tagged, nil
+}
+
+// filterUntaggedManifests removes untagged manifests from the list, preserving
+// manifest list children that are referenced by tagged manifest lists.
+func (gc *GarbageCollector) filterUntaggedManifests(manifests []*manifestInfo, taggedDigests map[string]bool) []*manifestInfo {
+ // Build a set of digests that are children of tagged manifest lists
+ childDigests := make(map[string]bool)
+ for _, m := range manifests {
+ // Check if this manifest is a manifest list AND is tagged
+ mDigest := extractDigestFromManifestURI(m.URI)
+ if mDigest == "" || !taggedDigests[mDigest] {
+ continue
+ }
+ // This is a tagged manifest list — protect its children
+ for _, ref := range m.Record.Manifests {
+ childDigests[ref.Digest] = true
+ }
+ }
+
+ // Filter: keep manifests that are tagged, or are children of tagged manifest lists
+ var kept []*manifestInfo
+ for _, m := range manifests {
+ mDigest := extractDigestFromManifestURI(m.URI)
+ if mDigest == "" {
+ kept = append(kept, m)
+ continue
+ }
+ if taggedDigests[mDigest] || childDigests[mDigest] {
+ kept = append(kept, m)
+ }
+ }
+ return kept
+}
+
+// extractDigestFromManifestURI extracts the digest from a manifest AT-URI.
+// URI format: at://did:plc:xxx/io.atcr.manifest/{encoded_digest}
+// Returns the full digest (e.g., "sha256:abc123...") or empty string.
+func extractDigestFromManifestURI(uri string) string {
+ parts := parseATURI(uri)
+ if parts == nil || parts.Collection != atproto.ManifestCollection {
+ return ""
}
- return gc.fetchUserManifestsFromEndpoint(ctx, userDID, pdsEndpoint, gc.pds.DID())
+ // The rkey is the digest without the "sha256:" prefix
+ return "sha256:" + parts.Rkey
}
// fetchUserManifestsFromEndpoint fetches manifests from a specific PDS endpoint.
-// Separated from fetchUserManifests for testability (avoids DID resolution).
func (gc *GarbageCollector) fetchUserManifestsFromEndpoint(ctx context.Context, userDID, pdsEndpoint, holdDID string) ([]*manifestInfo, error) {
var manifests []*manifestInfo
cursor := ""
diff --git a/pkg/hold/gc/gc_test.go b/pkg/hold/gc/gc_test.go
index db91a8c..6503e4e 100644
--- a/pkg/hold/gc/gc_test.go
+++ b/pkg/hold/gc/gc_test.go
@@ -451,6 +451,306 @@ func TestFetchUserManifests_PDSError(t *testing.T) {
}
}
+func TestExtractDigestFromManifestURI(t *testing.T) {
+ tests := []struct {
+ name string
+ uri string
+ expected string
+ }{
+ {
+ name: "valid manifest URI",
+ uri: "at://did:plc:user1/io.atcr.manifest/abc123def456",
+ expected: "sha256:abc123def456",
+ },
+ {
+ name: "wrong collection",
+ uri: "at://did:plc:user1/io.atcr.tag/abc123def456",
+ expected: "",
+ },
+ {
+ name: "invalid URI",
+ uri: "not-an-at-uri",
+ expected: "",
+ },
+ {
+ name: "empty URI",
+ uri: "",
+ expected: "",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := extractDigestFromManifestURI(tt.uri)
+ if got != tt.expected {
+ t.Errorf("extractDigestFromManifestURI(%q) = %q, want %q", tt.uri, got, tt.expected)
+ }
+ })
+ }
+}
+
+func TestFilterUntaggedManifests(t *testing.T) {
+ gc := &GarbageCollector{logger: newTestLogger()}
+
+ t.Run("keeps tagged manifests, removes untagged", func(t *testing.T) {
+ manifests := []*manifestInfo{
+ {URI: "at://did:plc:u1/io.atcr.manifest/aaa111", Record: &atproto.ManifestRecord{}},
+ {URI: "at://did:plc:u1/io.atcr.manifest/bbb222", Record: &atproto.ManifestRecord{}},
+ {URI: "at://did:plc:u1/io.atcr.manifest/ccc333", Record: &atproto.ManifestRecord{}},
+ }
+ tagged := map[string]bool{
+ "sha256:aaa111": true,
+ "sha256:ccc333": true,
+ }
+
+ result := gc.filterUntaggedManifests(manifests, tagged)
+ if len(result) != 2 {
+ t.Fatalf("expected 2 kept, got %d", len(result))
+ }
+ if result[0].URI != manifests[0].URI || result[1].URI != manifests[2].URI {
+ t.Errorf("unexpected manifests kept: %v, %v", result[0].URI, result[1].URI)
+ }
+ })
+
+ t.Run("preserves manifest list children", func(t *testing.T) {
+ // Manifest list (tagged) references child (untagged)
+ manifests := []*manifestInfo{
+ {
+ URI: "at://did:plc:u1/io.atcr.manifest/index111",
+ Record: &atproto.ManifestRecord{
+ Manifests: []atproto.ManifestReference{
+ {Digest: "sha256:child222", MediaType: "application/vnd.oci.image.manifest.v1+json"},
+ {Digest: "sha256:child333", MediaType: "application/vnd.oci.image.manifest.v1+json"},
+ },
+ },
+ },
+ {URI: "at://did:plc:u1/io.atcr.manifest/child222", Record: &atproto.ManifestRecord{}},
+ {URI: "at://did:plc:u1/io.atcr.manifest/child333", Record: &atproto.ManifestRecord{}},
+ {URI: "at://did:plc:u1/io.atcr.manifest/orphan444", Record: &atproto.ManifestRecord{}},
+ }
+ tagged := map[string]bool{
+ "sha256:index111": true, // Only the index is tagged
+ }
+
+ result := gc.filterUntaggedManifests(manifests, tagged)
+ if len(result) != 3 {
+ t.Fatalf("expected 3 kept (index + 2 children), got %d", len(result))
+ }
+ // orphan444 should be filtered out
+ for _, m := range result {
+ if extractDigestFromManifestURI(m.URI) == "sha256:orphan444" {
+ t.Error("orphan manifest should have been filtered out")
+ }
+ }
+ })
+
+ t.Run("removes everything when nothing is tagged", func(t *testing.T) {
+ manifests := []*manifestInfo{
+ {URI: "at://did:plc:u1/io.atcr.manifest/aaa111", Record: &atproto.ManifestRecord{}},
+ {URI: "at://did:plc:u1/io.atcr.manifest/bbb222", Record: &atproto.ManifestRecord{}},
+ }
+ tagged := map[string]bool{}
+
+ result := gc.filterUntaggedManifests(manifests, tagged)
+ if len(result) != 0 {
+ t.Fatalf("expected 0 kept, got %d", len(result))
+ }
+ })
+
+ t.Run("keeps everything when all are tagged", func(t *testing.T) {
+ manifests := []*manifestInfo{
+ {URI: "at://did:plc:u1/io.atcr.manifest/aaa111", Record: &atproto.ManifestRecord{}},
+ {URI: "at://did:plc:u1/io.atcr.manifest/bbb222", Record: &atproto.ManifestRecord{}},
+ }
+ tagged := map[string]bool{
+ "sha256:aaa111": true,
+ "sha256:bbb222": true,
+ }
+
+ result := gc.filterUntaggedManifests(manifests, tagged)
+ if len(result) != 2 {
+ t.Fatalf("expected 2 kept, got %d", len(result))
+ }
+ })
+}
+
+func TestFetchUserProfile(t *testing.T) {
+ gc := &GarbageCollector{logger: newTestLogger()}
+
+ t.Run("returns profile with autoRemoveUntagged", func(t *testing.T) {
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]any{
+ "value": map[string]any{
+ "$type": "io.atcr.sailor.profile",
+ "defaultHold": "did:web:hold.example.com",
+ "autoRemoveUntagged": true,
+ "createdAt": "2025-01-01T00:00:00Z",
+ },
+ })
+ }))
+ defer server.Close()
+
+ profile, err := gc.fetchUserProfile(t.Context(), server.URL, "did:plc:test")
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if profile == nil {
+ t.Fatal("expected non-nil profile")
+ }
+ if !profile.AutoRemoveUntagged {
+ t.Error("expected AutoRemoveUntagged to be true")
+ }
+ if profile.DefaultHold != "did:web:hold.example.com" {
+ t.Errorf("expected DefaultHold %q, got %q", "did:web:hold.example.com", profile.DefaultHold)
+ }
+ })
+
+ t.Run("returns nil for 404", func(t *testing.T) {
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusNotFound)
+ }))
+ defer server.Close()
+
+ profile, err := gc.fetchUserProfile(t.Context(), server.URL, "did:plc:test")
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if profile != nil {
+ t.Error("expected nil profile for 404")
+ }
+ })
+
+ t.Run("returns error for server failure", func(t *testing.T) {
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusInternalServerError)
+ }))
+ defer server.Close()
+
+ _, err := gc.fetchUserProfile(t.Context(), server.URL, "did:plc:test")
+ if err == nil {
+ t.Fatal("expected error for 500 response")
+ }
+ })
+}
+
+func TestFetchUserTags(t *testing.T) {
+ gc := &GarbageCollector{logger: newTestLogger()}
+
+ t.Run("returns tagged digests", func(t *testing.T) {
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]any{
+ "records": []map[string]any{
+ {
+ "uri": "at://did:plc:user1/io.atcr.tag/myapp_latest",
+ "cid": "bafyrei1",
+ "value": map[string]any{
+ "$type": "io.atcr.tag",
+ "repository": "myapp",
+ "tag": "latest",
+ "manifest": "at://did:plc:user1/io.atcr.manifest/abc123",
+ },
+ },
+ {
+ "uri": "at://did:plc:user1/io.atcr.tag/myapp_v1",
+ "cid": "bafyrei2",
+ "value": map[string]any{
+ "$type": "io.atcr.tag",
+ "repository": "myapp",
+ "tag": "v1",
+ "manifestDigest": "sha256:def456",
+ },
+ },
+ },
+ })
+ }))
+ defer server.Close()
+
+ tagged, err := gc.fetchUserTags(t.Context(), server.URL, "did:plc:user1")
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if len(tagged) != 2 {
+ t.Fatalf("expected 2 tagged digests, got %d", len(tagged))
+ }
+ if !tagged["sha256:abc123"] {
+ t.Error("expected sha256:abc123 to be tagged")
+ }
+ if !tagged["sha256:def456"] {
+ t.Error("expected sha256:def456 to be tagged")
+ }
+ })
+
+ t.Run("handles pagination", func(t *testing.T) {
+ callCount := 0
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ callCount++
+ w.Header().Set("Content-Type", "application/json")
+ if callCount == 1 {
+ json.NewEncoder(w).Encode(map[string]any{
+ "records": []map[string]any{
+ {
+ "uri": "at://did:plc:user1/io.atcr.tag/myapp_latest",
+ "cid": "bafyrei1",
+ "value": map[string]any{
+ "$type": "io.atcr.tag",
+ "repository": "myapp",
+ "tag": "latest",
+ "manifestDigest": "sha256:aaa111",
+ },
+ },
+ },
+ "cursor": "page2",
+ })
+ } else {
+ json.NewEncoder(w).Encode(map[string]any{
+ "records": []map[string]any{
+ {
+ "uri": "at://did:plc:user1/io.atcr.tag/myapp_v2",
+ "cid": "bafyrei2",
+ "value": map[string]any{
+ "$type": "io.atcr.tag",
+ "repository": "myapp",
+ "tag": "v2",
+ "manifestDigest": "sha256:bbb222",
+ },
+ },
+ },
+ })
+ }
+ }))
+ defer server.Close()
+
+ tagged, err := gc.fetchUserTags(t.Context(), server.URL, "did:plc:user1")
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if callCount != 2 {
+ t.Errorf("expected 2 HTTP calls for pagination, got %d", callCount)
+ }
+ if len(tagged) != 2 {
+ t.Fatalf("expected 2 tagged digests, got %d", len(tagged))
+ }
+ })
+
+ t.Run("returns empty for no tags", func(t *testing.T) {
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(map[string]any{"records": []any{}})
+ }))
+ defer server.Close()
+
+ tagged, err := gc.fetchUserTags(t.Context(), server.URL, "did:plc:user1")
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if len(tagged) != 0 {
+ t.Errorf("expected 0 tagged digests, got %d", len(tagged))
+ }
+ })
+}
+
func newTestLogger() *slog.Logger {
return slog.Default().With("component", "gc-test")
}
diff --git a/themes/seamark/embed.go b/themes/seamark/embed.go
index 14a454f..4114cf3 100644
--- a/themes/seamark/embed.go
+++ b/themes/seamark/embed.go
@@ -13,6 +13,9 @@ import (
//go:embed public
var publicFS embed.FS
+//go:embed templates
+var templatesFS embed.FS
+
//go:embed theme.css
var themeCSS string
@@ -29,8 +32,9 @@ func branding() *appview.BrandingOverrides {
}
return &appview.BrandingOverrides{
- PublicFS: prefixFS{sub: pubSub},
- ExtraCSS: themeCSS,
+ PublicFS: prefixFS{sub: pubSub},
+ TemplatesFS: templatesFS,
+ ExtraCSS: themeCSS,
}
}
diff --git a/themes/seamark/public/apple-touch-icon.png b/themes/seamark/public/apple-touch-icon.png
index 53ec455db0a76daed044daf3d8cbd38c9b4369bc..1b0909a0428a3b7c8cc331fdb8912e34a7d97a5c 100644
GIT binary patch
literal 3394
zcmeAS@N?(olHy`uVBq!ia0y~yVAuk}9Bd2>47O+4j2IYrIXzt*Ln`9l-mNT{@|5xT
z$9S{jX_I};zHhzhdt#&ayJlmKDN(NMEPoaUtv+EfH)#1~zoq{I8!uYTy%?CYJZtOX
zju$;nD;3%`dfZ)FjFeclgw_?fnJnw%Y*%y;TJU*~vY5b`ch73?*WOF7pD)Sw-=l2)
z_q*%$-|xFF{B*v}Z>0_q!J`X2Re5GjIkdo2lTMWA#JTUBdha(cbpG_
HVrsq2Cj~CCaLqU^;3vc&l85a616)Q2=;TfOrig?yKcjlb=~b4zJauGyK{rmTE%s&nk>Zy(9G%Vf`1l%Cyx;nJ_In(vpZv*j}K
z-#0(|LHG03S$uVkmj1fV`SJT%uU+_Wm~ry#oGRwDJ7vxhZaaUx1!;S>VNdxAi7iLm
z!bBf_|9+?Ii%5Ee+s(>F(*AqD@ojr&B{S_iKWp=}8}HxETOpgyw>)=uf5JWE(}yiv
z78qoBidJv>@Z-%(4^JmOd2{5Db6eSyT5CGTcf2yL#ja_qXOTa(N@
zZU6Sru8lk`j^3{p{$7^2=iu=<%v%e8*V*iPaMq#TW{t#_CG%z)GpD*%2KD5
z|B)okV@3jOx+_&b*Y}>eq7v)2{r>^SH@C0%?E6<&Vf#}eE}nb#S+Nh<rZ^G3{?sT0o%xpR1F1^&HROi3+M>xqI_w
z>wcTRGI}vPKI~mO$LMERkF2rPgQe*gOf;%k_SPO>9Tdam!57}_qq+Uir`2KWD~IV86%3E}-U
z>06w&oK?uu+>Kw4EKprqb>YyuJn?xMnwLwx&n{3E<#goE-}3zNd0pqy-y#xQmME!a
zB$gG++S$!qcUt}60##8@NBu0_L*4OWFF$!2WO%v;U3lG}p7`!yR;(Q-o9;>vu3sl+
zy4<;WWdT^ZYV-x0V2|cQ3!D$@ZdoExk|9Ag(&f@5;eb~s9U>FOy!J+Rbay*99qwDx
zW3pq?*UBwix}6iqxOVM|6&e+vHU&N~{D1NvZmwYPZ@Foj(WKU+Luue-Ry3?kCp8_1QP}
zFwd-g!t&zHvo6{6tki3{EV%sitdF~jeeMPsXIyfUi+p%=1=sp-_p5|<%ZH|_E~|}9
zxghBCM!5U$W6r}}rTjmOzU_$MS-+MeYu%Ip!xtP+HXL8HN<}>MsO5>%9%iY$Q+Pfq
znOh(J@IC2*pqla3&Sa2x`}pRY3oD7(iWI-8DYC0@9{>5|^Dt4H2OUVXZ>
zjkSEiQr`WQZ$b~p#ryL}HSAXq4n7_#QI$GpV$4aukm%J>>Eir
z|NM>refg6GvO!RCi$$sJu1nS{e*a1dGTg$Fsd?;g|IdT!)duw!ZyV`;Ds>IH@a=|#
z?vsmCSrd0(xyem(K0fET
z@F4%ZnO{#RZK=5`xz3fRYg2Saq2Rt7THSYf3O}z}rS<2ihqvjwTW=mO_e*}Zht=Zx
z?flNr6)6{1p5)72ZL{Z0)bb6@s`JXjqaQl^^z1wTXUCm;hu7}--+A;PpKi_+ZY%rK
zt%jkkw{F^BO-)~RI6NldMN-|4QhvGmlTz((Se7<@t2VwE5GwF(cG%gVs3)#|(>9)e
z60z%D@}5mqlOwxLGETAR@*VG-aXFT=IZiW7KsMLew^hEAlh3*R_n+6VH@<4lZQIvA
zPe*>sj=IP@wYJMvF?{5z|G9gXZ2~tSw21S_`Jo;yq;RqxT9=-{!2H%o+13k
zMd_pJhJCxvd~mh@jQ{hKVcblnv0X&2Mk^ecZa_W!tD
z^*yWgo(-E_E_~ZD<9%OB$KlY4r(SUX)c^kEboRv^soS51ZT;fd9m~COzs|fEk&{Qn
z4?a3n?4f*eHfgm_2vp+3877V((nPbhnt#`1#%ie{)k4`J;T=%bxpQ_3$)$F1=^X
z5xq8R_2Aj>ZoH9Xin`sO{IIKk3;U^v*c(k^^0RInl?Z<6+H`nFy4MN4lQSE7wuHRZ
zXq!=dAt$N(&!sNji0zrnZ|3KAY+!lo)pMLTRE|g6{M$FhNL7nRTmL?ulA<27PUVPH
zh5rMGxX8VN-q#~P&-lD`hTN7JbBccTPL+5R_CLkbr(}!Sf6tPVEoFg$NsE}zwXoz~
z(~|UixIX93GTCL;_5Thz+>LHq>FDkr>(aJZer8|Ykz0CEX{vuGv;`^`{JwH>TJ$vj
zZEvo9o4wnwEIRLv{gEvTaswNV-|XH}k!^EdG2rqtpF=Y)Dl`8r)Rf8nE5-lsYpHkK
zYD0nGCNaV8wM9-nR{ytf?z<#(=Eu*H8*dAl*QRUEdHeS8s$;nkH+RUY@2*}VG5H=3
ze^kqxvTsi7R%(4sT)Ou2%;LQfTZ)vgMLb#9z0gSK#gZMLPxQQRKV4^cWuvFI>d{ut
z$H%jNpWpP_;vcV+{k`5}y1o|IuBrYmU_U!yf$JGo&%LK=KJIq$nI9Gu)FOEJ^+SEr
zvP~7X9V=Ci_^x|Y7&r0IgayHqg+D%?n0iaTeqmv->V@xb?QgZ|&X+whZ7zG^i-_tY
zYn5V~#r%KHG1wHtz4My>|B^#m0+W-vvS-YWzP0c7?%mJVaCM6b-EGqQmh$jk=3|3Z
zyDXM8Uiex%*QR&hzq+^AR$Tigd~07p_VGfQzn`*Ya^G(F!|GafNjF1D(jX&47O+4j2IY1k9fK`hE&A8y~|!A9lDGC
z!~Qgt)6;Ty9^ZJ`P(?sNz$xNY(BxJYAt!#_D*^Wx>zvl(o^yQDoYquNv5G58b)Q~z
zdXf8b?u1Q^6H=0$6pj{d*NMJ6Ded0Rb;*ww87ZGsi>cjTTqLyN*}05A*XLS)zqfOf
z&y32Ye8*H0A37M%ELboz#NvXFmQ044D4)q>F7{cTt;}ba9BNDpdgx%hawthxajfWK
zYq#a`KVZjFeL7F(h2n=RruvD8PK2G=``4u=KHiF@`b?b63&*e-;)kA>+o*jjdfM#%
zxH&YLy|VviFSG3liwn-(%yZSUq}@$f7jBT+z9jU!okCsNY7++L!k{OWfS^
z(ve0#=hTh->+gRtxw9|k%>LgNe{O`xY~Eu1e1C6kmQ>+0+2Rd5mu4Kue&JmBKQ%w0
z$X8jeK3`v_;$?$LpVsce1(yxhUFsFjuZa7Sw580d;NRTMkJ_x}-Ah#azJH_syZYk$
zP-P~$pZItD-SgdL#p~N#hM$-1T*R4lWvhB^pE3Xcuswh7PPKk`%e~)dtL^J<=DCa_
zulAp*md~Bbce&B-^M}wK-)W8MN09?;)eAb
zt^e5WD-M4&=eM8ChpJcqtgB9aul@IbPwVmIr)>|_`i-g!7x-5Fc{TlLM_m5#dnV-$
zzPF6qm8C8TW?cQfr!Mw(!JO|8IGMlQ+3i0o;H>}R?yaRyp}IERUNpmEm#Yc?iD}ao
z&DdJ3)u{H=&%P+R#{wVZejlW`GEufBKLJQqC34Lrka({
z->Y-mhU>bmajn4jIeX!`rlf6-d95nntQ|)KGN)SRJ&D@ZyCtXim!|44E3kwtwCa(8EKxDrvk5nm{J;6d2_Bii
zZ;u?7cg~ZHDY>g;s2Q*O*ZRt#?uC{AJ3Vfza(aI=ynX;rOz1MrZauzhcs_0m>dO
z=NT?-Za5OQm1WwTy#krG^0VH*Y*A&uUEC5vM=yH9CKY!-fCtl@CHmlwoZS=cbJAH3w
zvwB|JqeOv&`i6y8ZdH-5)G!PglP?@942czr&2XuRc6@I$m5gqFuLb
zk~#CmZ_)y9Vz6!@R&k|QJH-R9o3?rP{3vC)
z&3^5|*L_KIO0K_bJDn)=V#`;Zsopt4M}$z9F)iSHJ!PTbfhXxy+Qf-{@Oc8v5uCJKH+NvqFy@
zmOogrxBkE(?)!q#mkP{ztKIL`J8gMvc6sT=8opO=Uz`^{dpQ4J)w-9THp#MEu}+uW
z%_A=BoSPczsv|Lu6P{M+PRXL61;nw@;f(aAV6DO-DeuHdw5y+^0|pYd3GN9SmxS?2za(H}pEuJ2rX
zWm5D)tMx6Zph*MG-IC
z0%ZFN9zVR`>8t$qm
z$KNrEp3UC0SZgHMr*!eDQUD@K?M*W!9pEYq6gne0q|a5^wa_LE7cAYvwHtoyxn$Z`PPT
zYK*8dFbhu7&AyO)@OOLS$8cfet=gG|3wRfF{xMlNU+r7;o8Uz0f1MG`v!>}ZT+3U)
zlKeR5mg$~RxIUSAQsn^aa^`zSH1~U;TM%3g4<&mdgrtFcQEH3@J+ALnWn37
z?Nq(v$-kybYd&rjX^wTf2VBn
z4Hn9eWZtxGE0_2m<;`!)ZWb(7R5Y67(Z+3gzep{J&q-EmT8#`~Y?lwK@#ao^%pU-M-Ey&v{z&SHo>&NpA&TLVEYc=K@$Q?~ScE~b`rHCu3UO8mNX5qPC
zqc289bAMmvd%?7Me<_1?_|6aQlkJb!32*CCh(0aPso&>VoRU*~`OCVc7nGg*2g-?8&<<^M`fBX8P@AK)GXLn!yakaf>YbwvWs-m>{Mmk+hj|`Iyavr^V
z6mz*gy=nPE#^`A}jn$KXUJW3%`}9TweP
zwHLb!58wWHNM8J4nzqZi6;^@P?z7Szx%Xr*xiP=>ro;70?}Afzi;r%!TD>a3td8ka
z`Q6YfXPR4Q)qm9vTk!X|e0|oxi|og@DsG;h||9EG%1$Sn)cP;v}
z(*%IDql7FB2*>DAMhx?TIB
zUFf@v=+oPQflm%>cvSNwVsiWJygQq%F5Ghv-?pV>(ayu>54!IPpFF)WDWz37bSdj1
zvHsMT&HNp+lK*s=U3~IG)lm9{TfLy)j?KTldzQtOd~8_Ow(NDrYn}xg?{ZI9Pk0q0
zY<2u{I0*J#9t
zsI^vFGIuSqdsBb<){`5V_gWtXxz5kynsHoSh;i9uZ{HrDMbA769pCw@&(vG~uypl;
zycqSe;@5k=8y4@E%`Y*S=CXS7vCR*f4{xYA>0;sD@A!2~@`G=zuDx%#mzhgU<=DF3
zoBMOXgz3t+ZU_1ves-*k<^GvBi?cd;GNpID?hy7#oz~>PP^b7=r&{aTRfm>oJv!`Q
z+Ux#@^Wfw6Lenp#h=d!j$)3jaP%lL6_lHNk=NB62AKM`b&U+b(kO?P=Hc^L@5%X2~v|?8o@?
zk1i49)!1-s|D>&@q7N*hGEXKsrd(pYaH>c5OT?V{-IETz^IEX{rr1T3!aD03#gZS*
z%Vw*oJZ~01JEv*On+c7}{3}xn?M`*eEL!H<_U_p1J9as_Usd+5-1sk@bxPa2#>ttJ
z>kqtrzL!1p<-XbdSzPzx;+mJ+&1o^6-OFnhu)^Q2f|u`Z@n`?1OOj6Rni%orrc2F-
zvk`?dCwtZvJf5UH{n3M2@(aKHI<|1b(Jo^-3*ORPzZaFP9P$RXg}rHvTHV|(#=TkB
zTn-8HuAN(Fvh}x1Z+=J^+bq5CD>4gbTkVQjdg?9jqI*wWv)k>{*5LJ}5no=ud!%(?_SuIGdp8KIy31GX*!Y5JF(c2elds-ydg)zoX-fV>&Zmzq
z9Z&BzaNeq9^R?hb-=P`pPT@9I*AIJh^L!2a;_iEdZMu7lwTPdMv0*mT-1*tfmlMSu
z^)yesvEf?fyJC5ZpM|M+)pn054eZ-Q=R3Ubvc1{%%q-;XR`XM3H&x%+2uZ!mjoT}A
zHv4+VhOchHf#2_M<(quWjKAWRsz~6++FDJ!cx$eF9qImnit6j)zFbm-EBs|leZSxDw=;tRHeI#|mhVbgVVSsDtML0=pKITozq`->
zX4W5nC-(c7j0?*IBeedR9d3X2Dox~mavkuuN-A?e@
z6Lp5;7i*9|=hqnl8+sPRf4Ff<>ElB$_OF81ciTM5SAJ1@$yb&))HvvjXTpVv@5`(Sb2zLNIa)r)6M{^luMu|j*&
zt$nK&b<_uE{5`ng2SZ@7(#0dILjoqZH2Iztn$_#U9Qfsb$W^6p+TZr)-v7AEG}!h1
z`b{eGsjYk)|Ih364r0Ic#v@TJaCZMP2ccgIk`fUql1jPT@1B>;`1Tppg!7QjIL3B+
n0^b?~vm5ZC8d?o-{^vKIq)|V;B6tM@0|SGntDnm{r-UW|uwuF7
diff --git a/themes/seamark/public/favicon-96x96.png b/themes/seamark/public/favicon-96x96.png
index 3650661e18144cfa8685c61f89a580ad999b9870..efcfb250a7fd085701fba90ee08f0cdfdd4a52a8 100644
GIT binary patch
delta 2071
zcmeyYwpd_-ay|PiPZ!6KinzCP>T_nhOCH~U{W1{w-TSbGC{7EYsIf
zPJ*+{N^i?2=g6`MLr
zg=MK62Lo;$^}M;Mt8;B`#vi$=%Vj1eesRC=U7vpR`@}_0`m5*H@2|ToR{N4!{4!=^n
zH-kIwz{{L(W(_itM9ADwLa5)$D$#N!(!0Sv%(ix;CDt*ahXfSG`iU*?8Kq{y;o;
zM4s5G)4P_{t+~*>F7+U{#avm_{3&*;J$IQ{c1~9J_Z5BrDyRMU*pRi
zpuepC#@!z~?Mk+6fBJTnhyi;mM@4mw%_QXUEw}|
zpDlgEB6A|;Rd4qXHNmnED*m}#3oPXxavHiua(eKj?Q>+4V_P`QyqHVy+Lw|;XDzu-
zIXV1a_@zo?3%f&)`N!**gt`?(o>XlrNaWsObpF3Y%kH;6e?q1Hlx9SiF^9ikwPxb8
zsZXh2%=oySW!2+T7j%Azg)UgWd#?7MCgFs8DK}Z_*d7SS%-q;;dg+4l((SF2enti`
zv9Vp2KPhSA(V`F@rjsPsu|9hDsic>Zh9$nb8JvIqpPB9a^GHG(yJ3wu!~aKjl)4r@
zSZpqC&m@s7ym7;}6G^4UN-+m6uMF`$y5#lqXZv{l-Rrv!zW$nUcGbBgy}3<{|NAnv
zuJ3CLU}Qd7lFcWfq*NUzuRB32UNx-rY5li)J&6LdWh)o%Jax9sfU)z*L|ztU#zkK9
z_&z*XAKmlq+O$Q^1`qTNePaVl7zF*@<*m!k%(eag?>j?H>+*+X3)q-5`ab5kZ@B+@
z*0#Rox~j5_%qv6cCmX)B;(3rZ&+WFWw@p_-@W1Wx&zcN*jvac{w)524ZcU9sjmzF#
z-i#qN{khMMoeK*IkF0C_R3$O*!r@ofD`tgG5}e?XwvXijr_z~U=h8I|r!UyMI=pGc
z4-tXG&KEai`7L)1u6ZkV_GjL5(dpx)N9$L+R%aW>&-JO_>CMk>Y~v^+
zY%sykT;l)2#SB#yQZGcj9|y8nKC2hzcMK2x%HuCBD`+s`PiS~wkvr#uC}r!Dw`TA9
z;deyBdU^X+)jhdeVh=PPJd-YQc=I0KVj=5o3mKP`_--xXvz6Y)^`hSHuZF|@GNa0}
z3m3PEeBEjqvrrZAf
z$LGJ#*$E%ZaMzK!mFxb$OU%H);!ELS1HR@1TV7ChA6nYE)~9E^GW`Dk=R*0HQ7=~iDbx$z(C{y%O21>n
zBSRj#KJQ8m-d}0xe<0vt?{TI^!+R0o8+8uY=$wyDXcukWu`S-av+}{Ei??{zPq+U$
z{dwJ+uGXvhH*d0gGYPF+th=*YSbyhTy?JLpPpTIRn=R0vwe9N3hQ#^D@7}y|r@e>P)c+0K|le(TmG+2!4k_@KO~HD_kgy2ME$uX@{@j<4NzRBLlz!`{E`
za^FJ3-ffav6EY+EyL+3=C&R@J`_A5$Q$OU~s&dlv-R|#sYu@d=cv5TK;h2va
zExLie#U;swvp{NQalWw>I6K?+Plig9zWtA*LF)gPNWb%aSa6whx%NMY1Ix{AKU6Ax
z6IQzM`CT8cPW-hd|J)tyrdLDFTsAW#M=Cv?xI*k!`ob-`H460)e7@z#C+|G&5b~$l
zNIotufTLPz)vwb*<&9_7m#6de3tv1H6dq@F!^kZ%ntwn
literal 4979
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
literal 15086
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
Jzk1ZMVgSl|2@U`N
diff --git a/themes/seamark/public/favicon.svg b/themes/seamark/public/favicon.svg
index c4d9e17..2900bc0 100644
--- a/themes/seamark/public/favicon.svg
+++ b/themes/seamark/public/favicon.svg
@@ -1,51 +1 @@
-
-
-
-
+
\ No newline at end of file
diff --git a/themes/seamark/public/static/seamark_seagull.svg b/themes/seamark/public/static/seamark_seagull.svg
new file mode 100644
index 0000000..7390a4b
--- /dev/null
+++ b/themes/seamark/public/static/seamark_seagull.svg
@@ -0,0 +1,34 @@
+
+
+
+
diff --git a/themes/seamark/public/static/seamark_seagull_buoy.png b/themes/seamark/public/static/seamark_seagull_buoy.png
new file mode 100644
index 0000000000000000000000000000000000000000..262bc8681ec7dabf000b59b5e312b2229780a535
GIT binary patch
literal 62082
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