From 81a93a489b92f76b5efabcc3cf3884d91a94f4e7 Mon Sep 17 00:00:00 2001 From: DioCrafts Date: Mon, 15 Jun 2026 00:17:17 +0200 Subject: [PATCH] feat: photo/video capture-date pipeline + premium UI/UX overhaul MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend — Photos timeline now groups by real capture date instead of upload time. New MediaMetadataService (FileLifecycleHook) extracts EXIF DateTimeOriginal from images and container creation_time from videos (mov/mp4/mkv) via nom-exif, timezone-correct (OffsetTimeOriginal), persisting captured_at so the existing media_sort_date trigger takes over. Adds POST /admin/photos/metadata/reextract to backfill existing media. Falls back to upload date when no embedded date exists. Frontend — premium grid cards: combined metadata line (relative date · size, owner avatar when shared), custom selection checkbox with a clear checked state, uniform full-width 4:3 thumbnail tiles independent of filename length, centered file-type icons, and a hit-test fix so checkbox/star/kebab clicks reach the controls (the decorative thumbnail no longer captures pointer events). Notification messages internationalised across all 16 locales. Broader polish: design tokens, a11y/focus-visible states, brand + PWA assets. Chore — bump semver-compatible dependencies (cargo upgrade); add nom-exif 3.6.1. Co-Authored-By: Claude Opus 4.8 (1M context) --- .stylelintrc.json | 2 +- Cargo.lock | 431 ++++++-------- Cargo.toml | 47 +- build.rs | 45 +- docs/DESIGN-SYSTEM.md | 105 ++++ docs/TOKENS.md | 537 ++++++++++++++++++ docs/UIUX-ROADMAP.md | 423 ++++++++++++++ justfile | 12 + scripts/check-brand-drift.mjs | 49 ++ scripts/check-contrast.mjs | 103 ++++ scripts/check-dead-tokens.mjs | 43 ++ scripts/check-headings.mjs | 35 ++ scripts/check-locales.mjs | 55 ++ scripts/gen-token-docs.mjs | 62 ++ src/common/di.rs | 21 + src/common/mime_detect.rs | 69 +++ .../services/media_metadata_service.rs | 485 ++++++++++++++++ src/infrastructure/services/mod.rs | 1 + src/interfaces/api/handlers/admin_handler.rs | 28 + src/interfaces/api/handlers/file_handler.rs | 15 +- static/admin.html | 3 +- static/css/base/a11y.css | 146 +++++ static/css/base/animations.css | 18 + static/css/base/forms.css | 16 +- static/css/base/reset.css | 35 +- static/css/base/typography.css | 82 +++ static/css/base/variables.css | 410 +++++++++---- static/css/components/batchToolbar.css | 32 +- static/css/components/brandLogo.css | 24 + static/css/components/breadcrumb.css | 18 +- static/css/components/buttons.css | 90 ++- static/css/components/commandPalette.css | 106 ++++ static/css/components/contextMenu.css | 14 +- static/css/components/csp-utilities.css | 71 +-- static/css/components/dialogs.css | 206 +++---- static/css/components/expiryChip.css | 8 +- static/css/components/fileType.css | 4 +- static/css/components/groupsModal.css | 108 ++-- static/css/components/itemTooltip.css | 8 +- static/css/components/languageSelector.css | 44 +- static/css/components/linkChip.css | 6 +- static/css/components/modals.css | 108 ++-- static/css/components/notifications.css | 250 ++------ static/css/components/resourceList.css | 476 ++++++++++++---- static/css/components/roleChip.css | 10 +- static/css/components/search.css | 18 +- static/css/components/shareDialog.css | 22 +- static/css/components/shareModal.css | 162 +++--- static/css/components/skeleton.css | 93 +++ static/css/components/spinner.css | 61 +- static/css/components/tooltip.css | 16 +- static/css/components/uploadDropdown.css | 14 +- static/css/components/userMenu.css | 79 +-- static/css/components/userVignette.css | 16 +- static/css/layout/content.css | 59 +- static/css/layout/sidebar.css | 85 ++- static/css/layout/topbar.css | 44 +- static/css/main.css | 6 + static/css/themes/dark.css | 1 - static/css/views/admin.css | 404 +++++++------ static/css/views/auth.css | 455 ++++++++++++--- static/css/views/device-verify.css | 35 +- static/css/views/favorites.css | 14 +- static/css/views/inlineViewer.css | 89 ++- static/css/views/music.css | 345 ++++++----- static/css/views/mySharesView.css | 72 +-- static/css/views/photos.css | 109 ++-- static/css/views/photosLightbox.css | 26 +- static/css/views/profile.css | 394 +++++++------ static/css/views/recent.css | 8 +- static/css/views/share-public.css | 94 +-- static/css/views/sharedWithMe.css | 2 +- static/device-verify.html | 3 +- static/index.html | 100 ++-- static/js/app/commandPalette.js | 177 ++++++ static/js/app/filesView.js | 69 ++- static/js/app/main.js | 2 +- static/js/app/navigation.js | 35 +- static/js/app/searchView.js | 3 +- static/js/app/ui.js | 69 ++- static/js/app/uiNotifications.js | 5 + static/js/app/userMenu.js | 10 +- static/js/components/modal.js | 39 ++ static/js/components/resourceList.js | 50 +- static/js/components/userVignette.js | 32 +- static/js/core/errorBoundary.js | 39 ++ static/js/core/formatters.js | 42 +- static/js/core/liveRegion.js | 61 ++ static/js/features/auth/auth.js | 125 ++++ static/js/features/files/batchToolbar.js | 20 +- static/js/features/files/contextMenus.js | 6 +- static/js/features/files/fileOperations.js | 58 +- static/js/features/files/search.js | 9 +- static/js/features/files/wopiEditor.js | 3 +- static/js/features/library/photos.js | 24 +- static/js/features/library/photosLightbox.js | 14 + static/js/features/sharing/fileSharing.js | 7 +- static/js/views/admin/admin.js | 11 +- static/js/views/profile/profile.js | 10 +- static/locales/ar.json | 101 +++- static/locales/de.json | 101 +++- static/locales/en.json | 101 +++- static/locales/es.json | 101 +++- static/locales/fa.json | 101 +++- static/locales/fr.json | 101 +++- static/locales/hi.json | 101 +++- static/locales/it.json | 101 +++- static/locales/ja.json | 101 +++- static/locales/ko.json | 101 +++- static/locales/nl.json | 109 +++- static/locales/pl.json | 101 +++- static/locales/pt.json | 101 +++- static/locales/ru.json | 101 +++- static/locales/zh-TW.json | 101 +++- static/locales/zh.json | 101 +++- static/login.html | 296 ++++++---- static/logo/apple-touch-icon.png | Bin 0 -> 22378 bytes static/logo/logo-maskable.svg | 17 + static/logo/maskable-192.png | Bin 0 -> 28574 bytes static/logo/maskable-512.png | Bin 0 -> 194485 bytes static/logo/og-image.png | Bin 0 -> 58743 bytes static/logo/og-image.svg | 24 + static/manifest.webmanifest | 42 ++ static/nextcloud-error.html | 2 +- static/nextcloud-login.html | 2 +- static/nextcloud-success.html | 2 +- static/profile.html | 4 +- static/share.html | 28 +- static/sw.js | 19 +- 129 files changed, 8194 insertions(+), 2473 deletions(-) create mode 100644 docs/DESIGN-SYSTEM.md create mode 100644 docs/TOKENS.md create mode 100644 docs/UIUX-ROADMAP.md create mode 100644 scripts/check-brand-drift.mjs create mode 100644 scripts/check-contrast.mjs create mode 100644 scripts/check-dead-tokens.mjs create mode 100644 scripts/check-headings.mjs create mode 100644 scripts/check-locales.mjs create mode 100644 scripts/gen-token-docs.mjs create mode 100644 src/infrastructure/services/media_metadata_service.rs create mode 100644 static/css/base/a11y.css create mode 100644 static/css/base/animations.css create mode 100644 static/css/base/typography.css create mode 100644 static/css/components/brandLogo.css create mode 100644 static/css/components/commandPalette.css create mode 100644 static/css/components/skeleton.css create mode 100644 static/js/app/commandPalette.js create mode 100644 static/js/core/errorBoundary.js create mode 100644 static/js/core/liveRegion.js create mode 100644 static/logo/apple-touch-icon.png create mode 100644 static/logo/logo-maskable.svg create mode 100644 static/logo/maskable-192.png create mode 100644 static/logo/maskable-512.png create mode 100644 static/logo/og-image.png create mode 100644 static/logo/og-image.svg create mode 100644 static/manifest.webmanifest diff --git a/.stylelintrc.json b/.stylelintrc.json index e802f384..46dbbd5b 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -1,5 +1,5 @@ { - "files": ["statc/css/**/*.css"], + "files": ["static/css/**/*.css"], "plugins": [ "stylelint-value-no-unknown-custom-properties" ], diff --git a/Cargo.lock b/Cargo.lock index 48c87324..3b77cea2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -139,6 +139,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "arc-swap" version = "1.9.1" @@ -331,9 +340,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-config" -version = "1.8.16" +version = "1.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f156acdd2cf55f5aa53ee416c4ac851cf1222694506c0b1f78c85695e9ca9d" +checksum = "e33f815b73a3899c03b380d543532e5865f230dce9678d108dc10732a8682275" dependencies = [ "aws-credential-types", "aws-runtime", @@ -345,6 +354,7 @@ dependencies = [ "aws-smithy-json", "aws-smithy-runtime", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "aws-types", "bytes", @@ -395,9 +405,9 @@ dependencies = [ [[package]] name = "aws-runtime" -version = "1.7.3" +version = "1.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dcd93c82209ac7413532388067dce79be5a8780c1786e5fae3df22e4dee2864" +checksum = "6c9b9de216a988dd54b754a82a7660cfe14cee4f6782ae4524470972fa0ccb39" dependencies = [ "aws-credential-types", "aws-sigv4", @@ -423,10 +433,11 @@ dependencies = [ [[package]] name = "aws-sdk-s3" -version = "1.131.0" +version = "1.136.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe1b8c5282bf859170836045296b3cd710b7573aceb909498366bb508a41058e" +checksum = "fbd03e531a7d981fba45114c5813a7565dd470a1bd3ef1188ca98c98ebdfc668" dependencies = [ + "arc-swap", "aws-credential-types", "aws-runtime", "aws-sigv4", @@ -458,10 +469,11 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.98.0" +version = "1.102.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d69c77aafa20460c68b6b3213c84f6423b6e76dbf89accd3e1789a686ffd9489" +checksum = "8c82b3ac19f1431854f7ace3a7531674633e286bfdde21976893bfee36fd493b" dependencies = [ + "arc-swap", "aws-credential-types", "aws-runtime", "aws-smithy-async", @@ -482,10 +494,11 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.100.0" +version = "1.104.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c7e7b09346d5ca22a2a08267555843a6a0127fb20d8964cb6ecfb8fdb190225" +checksum = "321000d2b4c5519ee573f73167f612efd7329322d9b26969ad1979f0427f1913" dependencies = [ + "arc-swap", "aws-credential-types", "aws-runtime", "aws-smithy-async", @@ -506,10 +519,11 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.103.0" +version = "1.107.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2249b81a2e73a8027c41c378463a81ec39b8510f184f2caab87de912af0f49b" +checksum = "3d0d328ba962af23ecfa3c9f23b98d3d35e325fa218d7f13d17a6bf522f8a560" dependencies = [ + "arc-swap", "aws-credential-types", "aws-runtime", "aws-smithy-async", @@ -531,9 +545,9 @@ dependencies = [ [[package]] name = "aws-sigv4" -version = "1.4.3" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68dc0b907359b120170613b5c09ccc61304eac3998ff6274b97d93ee6490115a" +checksum = "bae38512beae0ffee7010fc24e7a8a123c53efdfef42a61e80fda4882418dc71" dependencies = [ "aws-credential-types", "aws-smithy-eventstream", @@ -541,15 +555,14 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "bytes", - "crypto-bigint 0.5.5", + "crypto-bigint", "form_urlencoded", "hex", "hmac 0.13.0", "http 0.2.12", "http 1.4.0", - "p256 0.11.1", + "p256", "percent-encoding", - "ring", "sha2 0.11.0", "subtle", "time", @@ -570,9 +583,9 @@ dependencies = [ [[package]] name = "aws-smithy-checksums" -version = "0.64.7" +version = "0.64.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10efbbcec1e044b81600e2fc562a391951d291152d95b482d5b7e7132299d762" +checksum = "e9e8e65f4f81fcccdeb6c3eca2af17ac21d421a1786a26a394aecf421d616d3a" dependencies = [ "aws-smithy-http", "aws-smithy-types", @@ -591,9 +604,9 @@ dependencies = [ [[package]] name = "aws-smithy-eventstream" -version = "0.60.20" +version = "0.60.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf09d74e5e32f76b8762da505a3cd59303e367a664ca67295387baa8c1d7548" +checksum = "78d8391e65fcea47c586a22e1a41f173b38615b112b2c6b7a44e80cec3e6b706" dependencies = [ "aws-smithy-types", "bytes", @@ -654,10 +667,12 @@ dependencies = [ [[package]] name = "aws-smithy-json" -version = "0.62.5" +version = "0.62.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9648b0bb82a2eedd844052c6ad2a1a822d1f8e3adee5fbf668366717e428856a" +checksum = "701a947f4797e52a911e114a898667c746c39feea467bbd1abd7b3721f702ffa" dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", ] @@ -682,15 +697,16 @@ dependencies = [ [[package]] name = "aws-smithy-runtime" -version = "1.11.1" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0504b1ab12debb5959e5165ee5fe97dd387e7aa7ea6a477bfd7635dfe769a4f5" +checksum = "b8e6f5caf6fea86f8c2206541ab5857cfcda9013426cdbe8fa0098b9e2d32182" dependencies = [ "aws-smithy-async", "aws-smithy-http", "aws-smithy-http-client", "aws-smithy-observability", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "bytes", "fastrand 2.4.1", @@ -707,9 +723,9 @@ dependencies = [ [[package]] name = "aws-smithy-runtime-api" -version = "1.12.0" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71a13df6ada0aafbf21a73bdfcdf9324cfa9df77d96b8446045be3cde61b42e" +checksum = "9db177daa6ba8afb9ee1aefcf548c907abcf52065e394ee11a92780057fe0e8c" dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api-macros", @@ -735,10 +751,21 @@ dependencies = [ ] [[package]] -name = "aws-smithy-types" -version = "1.4.7" +name = "aws-smithy-schema" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d73dbfbaa8e4bc57b9045137680b958d274823509a360abfd8e1d514d40c95c" +checksum = "7442cb268338f0eb8278140a107c046756aa01093d8ef5e99628d34ae09c94f5" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "http 1.4.0", +] + +[[package]] +name = "aws-smithy-types" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b42fcf341259d85ca10fac9a2f6448a8ec691c6955a18e45bc3b71a85fab85" dependencies = [ "base64-simd 0.8.0", "bytes", @@ -771,13 +798,14 @@ dependencies = [ [[package]] name = "aws-types" -version = "1.3.15" +version = "1.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4bbcaa9304ea40902d3d5f42a0428d1bd895a2b0f6999436fb279ffddc58ac" +checksum = "d16bf10b03a3c01e6b3b7d47cd964e873ffe9e7d4e80fad16bd4c077cb068531" dependencies = [ "aws-credential-types", "aws-smithy-async", "aws-smithy-runtime-api", + "aws-smithy-schema", "aws-smithy-types", "rustc_version", "tracing", @@ -934,12 +962,6 @@ dependencies = [ "time", ] -[[package]] -name = "base16ct" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" - [[package]] name = "base16ct" version = "0.2.0" @@ -1265,9 +1287,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "js-sys", @@ -1530,18 +1552,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" -[[package]] -name = "crypto-bigint" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "subtle", - "zeroize", -] - [[package]] name = "crypto-bigint" version = "0.5.5" @@ -1700,9 +1710,9 @@ dependencies = [ [[package]] name = "dashmap" -version = "6.1.0" +version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" dependencies = [ "cfg-if", "crossbeam-utils", @@ -1733,16 +1743,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c286de4e81ea2590afc24d754e0f83810c566f50a1388fa75ebd57928c0d9745" -[[package]] -name = "der" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" -dependencies = [ - "const-oid 0.9.6", - "zeroize", -] - [[package]] name = "der" version = "0.7.10" @@ -1859,30 +1859,18 @@ dependencies = [ "cipher", ] -[[package]] -name = "ecdsa" -version = "0.14.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" -dependencies = [ - "der 0.6.1", - "elliptic-curve 0.12.3", - "rfc6979 0.3.1", - "signature 1.6.4", -] - [[package]] name = "ecdsa" version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "der 0.7.10", + "der", "digest 0.10.7", - "elliptic-curve 0.13.8", - "rfc6979 0.4.0", - "signature 2.2.0", - "spki 0.7.3", + "elliptic-curve", + "rfc6979", + "signature", + "spki", ] [[package]] @@ -1891,8 +1879,8 @@ version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ - "pkcs8 0.10.2", - "signature 2.2.0", + "pkcs8", + "signature", ] [[package]] @@ -1918,43 +1906,23 @@ dependencies = [ "serde", ] -[[package]] -name = "elliptic-curve" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" -dependencies = [ - "base16ct 0.1.1", - "crypto-bigint 0.4.9", - "der 0.6.1", - "digest 0.10.7", - "ff 0.12.1", - "generic-array", - "group 0.12.1", - "pkcs8 0.9.0", - "rand_core 0.6.4", - "sec1 0.3.0", - "subtle", - "zeroize", -] - [[package]] name = "elliptic-curve" version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ - "base16ct 0.2.0", - "crypto-bigint 0.5.5", + "base16ct", + "crypto-bigint", "digest 0.10.7", - "ff 0.13.1", + "ff", "generic-array", - "group 0.13.0", + "group", "hkdf", "pem-rfc7468", - "pkcs8 0.10.2", + "pkcs8", "rand_core 0.6.4", - "sec1 0.7.3", + "sec1", "subtle", "zeroize", ] @@ -2111,16 +2079,6 @@ dependencies = [ "simd-adler32", ] -[[package]] -name = "ff" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "ff" version = "0.13.1" @@ -2371,6 +2329,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "geo-types" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94776032c45f950d30a13af6113c2ad5625316c9abfbccee4dd5a6695f8fe0f5" +dependencies = [ + "approx", + "num-traits", + "serde", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -2448,24 +2417,13 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" -[[package]] -name = "group" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" -dependencies = [ - "ff 0.12.1", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "group" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "ff 0.13.1", + "ff", "rand_core 0.6.4", "subtle", ] @@ -2814,7 +2772,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2 0.5.10", "tokio", "tower-service", "tracing", @@ -2934,9 +2892,9 @@ checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" [[package]] name = "id3" -version = "1.16.4" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "965c5e6a62a241f2f673df956ea5f52c27780bc1031855890a551ed9b869e2d1" +checksum = "70d6d9f23a28c6feab60d7b4a339e004809019ec9d5fd964b3f4c655ff9bc141" dependencies = [ "bitflags", "byteorder", @@ -3060,13 +3018,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] -name = "iri-string" -version = "0.7.12" +name = "iso6709parse" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +checksum = "b5090db9c6a716d1f4eeb729957e889e9c28156061c825cbccd44950cf0f3c66" dependencies = [ - "memchr", - "serde", + "geo-types", + "nom 7.1.3", ] [[package]] @@ -3123,16 +3081,16 @@ checksum = "35e770254dd7802184595b1d30da2a15cb72569e2aca2b177aef8d22eac8a693" [[package]] name = "jsonwebtoken" -version = "10.3.0" +version = "10.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" +checksum = "eba32bfb4ffdeaca3e34431072faf01745c9b26d25504aa7a6cf5684334fc4fc" dependencies = [ "base64 0.22.1", "ed25519-dalek", "getrandom 0.2.17", "hmac 0.12.1", "js-sys", - "p256 0.13.2", + "p256", "p384", "pem", "rand 0.8.6", @@ -3140,8 +3098,9 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.9", - "signature 2.2.0", + "signature", "simple_asn1", + "zeroize", ] [[package]] @@ -3189,7 +3148,7 @@ dependencies = [ "quoted_printable", "rustls 0.23.40", "rustls-native-certs", - "socket2 0.6.3", + "socket2 0.6.4", "tokio", "tokio-rustls 0.26.4", "url", @@ -3216,9 +3175,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libmimalloc-sys" -version = "0.1.47" +version = "0.1.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d1eacfa31c33ec25e873c136ba5669f00f9866d0688bea7be4d3f7e43067df6" +checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9" dependencies = [ "cc", ] @@ -3428,9 +3387,9 @@ dependencies = [ [[package]] name = "mimalloc" -version = "0.1.50" +version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3627c4272df786b9260cabaa46aec1d59c93ede723d4c3ef646c503816b0640" +checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862" dependencies = [ "libmimalloc-sys", ] @@ -3591,6 +3550,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "nom-exif" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55d41fe63e6184159b55a57cc73651ec8dc60e5ce2d647cd16f373a27b82ff33" +dependencies = [ + "bytes", + "chrono", + "iso6709parse", + "nom 8.0.0", + "regex", + "thiserror 2.0.18", + "tracing", +] + [[package]] name = "nom_locate" version = "5.0.0" @@ -4123,7 +4097,7 @@ dependencies = [ "blake3", "bytes", "chrono", - "dashmap 6.1.0", + "dashmap 6.2.1", "dotenvy", "fastcdc", "flate2", @@ -4148,6 +4122,7 @@ dependencies = [ "mockall", "moka", "mp3-duration", + "nom-exif", "oxc_allocator", "oxc_codegen", "oxc_minifier", @@ -4156,7 +4131,7 @@ dependencies = [ "oxc_span", "pdf-extract", "percent-encoding", - "quick-xml 0.39.2", + "quick-xml 0.39.4", "rand_core 0.6.4", "rayon", "reqwest", @@ -4164,7 +4139,7 @@ dependencies = [ "serde_json", "sha2 0.11.0", "smol_str", - "socket2 0.6.3", + "socket2 0.6.4", "sqlx", "tantivy", "tempfile", @@ -4183,25 +4158,14 @@ dependencies = [ "zip", ] -[[package]] -name = "p256" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" -dependencies = [ - "ecdsa 0.14.8", - "elliptic-curve 0.12.3", - "sha2 0.10.9", -] - [[package]] name = "p256" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" dependencies = [ - "ecdsa 0.16.9", - "elliptic-curve 0.13.8", + "ecdsa", + "elliptic-curve", "primeorder", "sha2 0.10.9", ] @@ -4212,8 +4176,8 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" dependencies = [ - "ecdsa 0.16.9", - "elliptic-curve 0.13.8", + "ecdsa", + "elliptic-curve", "primeorder", "sha2 0.10.9", ] @@ -4481,19 +4445,9 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ - "der 0.7.10", - "pkcs8 0.10.2", - "spki 0.7.3", -] - -[[package]] -name = "pkcs8" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" -dependencies = [ - "der 0.6.1", - "spki 0.6.0", + "der", + "pkcs8", + "spki", ] [[package]] @@ -4502,8 +4456,8 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der 0.7.10", - "spki 0.7.3", + "der", + "spki", ] [[package]] @@ -4645,7 +4599,7 @@ version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" dependencies = [ - "elliptic-curve 0.13.8", + "elliptic-curve", ] [[package]] @@ -4701,9 +4655,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.39.2" +version = "0.39.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" +checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" dependencies = [ "memchr", ] @@ -4721,7 +4675,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.40", - "socket2 0.6.3", + "socket2 0.5.10", "thiserror 2.0.18", "tokio", "tracing", @@ -4758,7 +4712,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.3", + "socket2 0.5.10", "tracing", "windows-sys 0.60.2", ] @@ -5025,17 +4979,6 @@ dependencies = [ "webpki-roots 1.0.7", ] -[[package]] -name = "rfc6979" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" -dependencies = [ - "crypto-bigint 0.4.9", - "hmac 0.12.1", - "zeroize", -] - [[package]] name = "rfc6979" version = "0.4.0" @@ -5101,10 +5044,10 @@ dependencies = [ "num-integer", "num-traits", "pkcs1", - "pkcs8 0.10.2", + "pkcs8", "rand_core 0.6.4", - "signature 2.2.0", - "spki 0.7.3", + "signature", + "spki", "subtle", "zeroize", ] @@ -5262,30 +5205,16 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" -[[package]] -name = "sec1" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" -dependencies = [ - "base16ct 0.1.1", - "der 0.6.1", - "generic-array", - "pkcs8 0.9.0", - "subtle", - "zeroize", -] - [[package]] name = "sec1" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ - "base16ct 0.2.0", - "der 0.7.10", + "base16ct", + "der", "generic-array", - "pkcs8 0.10.2", + "pkcs8", "subtle", "zeroize", ] @@ -5372,9 +5301,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -5486,16 +5415,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "1.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" -dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", -] - [[package]] name = "signature" version = "2.2.0" @@ -5597,9 +5516,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", "windows-sys 0.61.2", @@ -5620,16 +5539,6 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" -[[package]] -name = "spki" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" -dependencies = [ - "base64ct", - "der 0.6.1", -] - [[package]] name = "spki" version = "0.7.3" @@ -5637,7 +5546,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", - "der 0.7.10", + "der", ] [[package]] @@ -6213,16 +6122,16 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.1" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", "mio", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.3", + "socket2 0.6.4", "tokio-macros", "windows-sys 0.61.2", ] @@ -6301,9 +6210,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "async-compression", "bitflags", @@ -6315,7 +6224,6 @@ dependencies = [ "http-body-util", "http-range-header", "httpdate", - "iri-string", "mime", "mime_guess", "percent-encoding", @@ -6326,6 +6234,7 @@ dependencies = [ "tower-layer", "tower-service", "tracing", + "url", "uuid", ] @@ -6578,9 +6487,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "utoipa" -version = "5.4.0" +version = "5.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993" +checksum = "8bde15df68e80b16c7d16b9616e80770ad158988daa56a27dccd1e55558b0160" dependencies = [ "indexmap", "serde", @@ -6590,9 +6499,9 @@ dependencies = [ [[package]] name = "utoipa-gen" -version = "5.4.0" +version = "5.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d79d08d92ab8af4c5e8a6da20c47ae3f61a0f1dabc1997cdf2d082b757ca08b" +checksum = "6ba0b99ee52df3028635d93840c797102da61f8a7bb3cf751032455895b52ef8" dependencies = [ "proc-macro2", "quote", @@ -6603,9 +6512,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.23.1" +version = "1.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -7355,6 +7264,20 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] name = "zerotrie" diff --git a/Cargo.toml b/Cargo.toml index d39835e0..1bb33a08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,46 +6,46 @@ default-run = "oxicloud" [dependencies] -mimalloc = { version = "0.1.48", default-features = false } +mimalloc = { version = "0.1.52", default-features = false } axum = { version = "0.8.9", features = ["multipart", "http1", "http2", "tokio", "macros"] } -tokio = { version = "1.52.0", features = ["rt-multi-thread", "macros", "io-util", "net", "time", "sync", "fs"] } +tokio = { version = "1.52.3", features = ["rt-multi-thread", "macros", "io-util", "net", "time", "sync", "fs"] } tokio-util = { version = "0.7.18", features = ["io", "codec", "compat"] } tokio-stream = { version = "0.1.18", features = ["fs"] } bytes = "1.11.1" -tempfile = "3.26.0" +tempfile = "3.27.0" tower = "0.5.3" -tower-http = { version = "0.6.8", features = ["fs", "compression-gzip", "compression-br", "trace", "cors", "add-extension", "request-id", "set-header", "limit"] } +tower-http = { version = "0.6.11", features = ["fs", "compression-gzip", "compression-br", "trace", "cors", "add-extension", "request-id", "set-header", "limit"] } flate2 = "1.1.9" tracing = "0.1.44" -tracing-subscriber = { version = "0.3.22", features = ["env-filter"] } -chrono = { version = "0.4.44", features = ["serde"] } +tracing-subscriber = { version = "0.3.23", features = ["env-filter"] } +chrono = { version = "0.4.45", features = ["serde"] } http-body = "1.0.1" serde = { version = "1.0.228", features = ["derive"] } -serde_json = "1.0.149" +serde_json = "1.0.150" futures = "0.3.32" async-stream = "0.3.6" -async-trait = "0.1.83" +async-trait = "0.1.89" mime_guess = "2.0.5" -uuid = { version = "1.23.0", features = ["v4", "v7", "serde"] } +uuid = { version = "1.23.3", features = ["v4", "v7", "serde"] } thiserror = "2.0.18" mockall = { version = "0.14.0", optional = true } sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio", "tls-rustls", "chrono", "uuid", "json", "migrate"] } -jsonwebtoken = { version = "10.3.0", features = ["rust_crypto"] } +jsonwebtoken = { version = "10.4.0", features = ["rust_crypto"] } argon2 = "0.5.3" rand_core = { version = "0.6", features = ["std", "getrandom"] } -quick-xml = "0.39.2" +quick-xml = "0.39.4" dotenvy = "0.15.7" moka = { version = "0.12.15", features = ["future", "sync"] } http-range-header = "0.4" image = { version = "0.25.10", default-features = false, features = ["jpeg", "png", "gif", "webp"] } -id3 = "1.14" +id3 = "1.17" mp3-duration = "0.1" kamadak-exif = "0.6.1" md-5 = "0.11.0" sha2 = "0.11.0" -unicode-normalization = "0.1.24" -blake3 = { version = "1.8.4", features = ["rayon", "mmap"] } +unicode-normalization = "0.1.25" +blake3 = { version = "1.8.5", features = ["rayon", "mmap"] } hex = "0.4.3" http-body-util = "0.1.3" percent-encoding = "2.3.2" @@ -54,15 +54,15 @@ base64 = "0.22.1" fs2 = "0.4" rayon = "1.12.0" infer = "0.19" -async-compression = { version = "0.4.41", features = ["tokio", "gzip"] } +async-compression = { version = "0.4.42", features = ["tokio", "gzip"] } async_zip = { version = "0.0.18", features = ["tokio", "deflate"] } -dashmap = "6.1.0" -socket2 = { version = "0.6.3", features = ["all"] } +dashmap = "6.2.1" +socket2 = { version = "0.6.4", features = ["all"] } urlencoding = "2.1.3" -utoipa = { version = "5.4.0", features = ["axum_extras", "uuid", "chrono"] } -aws-sdk-s3 = "1.129.0" -aws-config = { version = "1.8.15", features = ["behavior-version-latest"] } -aws-smithy-types = "1.4.7" +utoipa = { version = "5.5.0", features = ["axum_extras", "uuid", "chrono"] } +aws-sdk-s3 = "1.136.0" +aws-config = { version = "1.8.18", features = ["behavior-version-latest"] } +aws-smithy-types = "1.5.0" azure_core = { version = "0.21", default-features = false, features = ["enable_reqwest_rustls", "hmac_rust"] } azure_storage = { version = "0.21", default-features = false, features = ["enable_reqwest_rustls", "hmac_rust"] } azure_storage_blobs = { version = "0.21", default-features = false, features = ["enable_reqwest_rustls", "hmac_rust"] } @@ -71,12 +71,13 @@ lru = "0.16.4" fastcdc = { version = "4.0.0", features = ["tokio"] } lettre = { version = "0.11.18", default-features = false, features = ["smtp-transport", "tokio1-rustls-tls", "rustls-native-certs", "builder"] } idna = "1.1" -smol_str = { version = "0.3.2", features = ["serde"] } +smol_str = { version = "0.3.6", features = ["serde"] } accept-language = "3.1.0" askama = "0.16.0" tantivy = "0.26.1" zip = { version = "8.6.0", default-features = false, features = ["deflate"] } pdf-extract = "0.10.0" +nom-exif = "3.6.1" [features] default = [] @@ -104,7 +105,7 @@ oxc_minifier = "0.125.0" lightningcss = "1.0.0-alpha.71" [profile.release] -lto = "fat" +lto = "thin" codegen-units = 1 opt-level = 3 panic = "abort" diff --git a/build.rs b/build.rs index 6627d670..e493e2be 100644 --- a/build.rs +++ b/build.rs @@ -191,10 +191,16 @@ fn process_release(manifest_dir: &Path, static_dir: &Path, out_dir: &Path) { minify_tree_js(&dist_dir.join("js")); // ── 7. Rewrite index.html ──────────────────────────────────────────────── + // Inline the tiny render-blocking theme-init.js (classic script) so the + // critical path drops a request. Other pages keep the external reference. + let theme_init_min = js_minify_script_safe( + &fs::read_to_string(static_dir.join("js/core/theme-init.js")).unwrap_or_default(), + ); let rewritten_index = rewrite_index_html( &index_html, &format!("/css/{css_name}"), &format!("/js/{js_name}"), + &theme_init_min, ); fs::write(dist_dir.join("index.html"), &rewritten_index).expect("write dist index.html"); @@ -1099,14 +1105,49 @@ fn json_minify(source: &str) -> String { /// - Collapse all `` into the single CSS bundle. /// - Replace all `", theme_init_js.trim())); + } + continue; + } + // ── Replace all stylesheet s with single bundle ──────────────── if t.starts_with("`/`` (not clickable `
`s). Nav exposes `aria-current="page"`. +- **Focus.** A global `:focus-visible` ring lives in + [`base/a11y.css`](../static/css/base/a11y.css). Never `outline: none` without a + paired `:focus-visible` style. Mouse focus stays ring-free; keyboard focus never. +- **Contrast.** Every text/background pair clears 4.5:1 in **light AND dark** — + `node scripts/check-contrast.mjs` fails the build otherwise. +- **Landmarks + skip link.** `
Storage
@@ -125,7 +142,7 @@
- @@ -152,8 +169,8 @@
-
@@ -173,7 +190,7 @@
-
@@ -209,7 +226,7 @@
- + Appearance
-
+

Files

@@ -284,14 +301,14 @@