From 1e66815a3d2e172b660e2d86627fb5f7dbf5b174 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Tue, 16 Sep 2025 21:47:22 -0500 Subject: [PATCH] Albums (#315) * Make /db top-level module * Create Album type * Pin pnpm version * Generalize query modules * Finalize album postgres data type * Remove temp albums prop * Create basic album primitives * Fix temporary album bugs * Add albums to sidebar * Disambiguate string date utilities * Localize album language * Add album join option to core photo queries * Tweak album icon placement * Add album photo detail page * Refine Album data model * Display album subhead when available * Generate album og images * Finalize album share modal * Add albums to sitemap * Statically pre-render albums * Display tags on albums * Add albums to cmd-k menu * Handle album tag overflow * Stop truncating album subheads * Create core admin album views * Make albums editable * Create/edit albums on photo save, add delete album --- app/admin/albums/[album]/edit/page.tsx | 53 + app/admin/albums/page.tsx | 18 + app/admin/photos/[photoId]/edit/page.tsx | 7 + app/admin/photos/page.tsx | 2 +- app/admin/photos/updates/page.tsx | 2 +- app/admin/recipes/[recipe]/edit/page.tsx | 2 +- app/admin/recipes/page.tsx | 2 +- app/admin/tags/[tag]/edit/page.tsx | 4 +- app/admin/tags/page.tsx | 6 +- app/admin/uploads/[uploadPath]/page.tsx | 6 +- app/album/[album]/[photoId]/page.tsx | 97 ++ app/album/[album]/image/route.tsx | 52 + app/album/[album]/page.tsx | 99 ++ app/film/[film]/image/route.tsx | 5 +- app/film/[film]/page.tsx | 2 +- app/focal/[focal]/image/route.tsx | 5 +- app/focal/[focal]/page.tsx | 2 +- app/full/[sortType]/[sortOrder]/page.tsx | 4 +- app/full/page.tsx | 2 +- app/grid/[sortType]/[sortOrder]/page.tsx | 4 +- app/grid/page.tsx | 2 +- app/home-image/route.tsx | 2 +- app/lens/[make]/[model]/image/route.tsx | 4 +- app/lens/[make]/[model]/page.tsx | 2 +- app/p/[photoId]/image/route.tsx | 2 +- app/page.tsx | 2 +- app/recents/image/route.tsx | 2 +- app/recipe/[recipe]/image/route.tsx | 4 +- app/recipe/[recipe]/page.tsx | 2 +- app/shot-on/[make]/[model]/image/route.tsx | 4 +- app/shot-on/[make]/[model]/page.tsx | 2 +- app/sitemap.ts | 12 +- app/tag/[tag]/image/route.tsx | 4 +- app/tag/[tag]/page.tsx | 2 +- app/template-image-tight/route.tsx | 2 +- app/template-image/route.tsx | 2 +- app/year/[year]/image/route.tsx | 5 +- app/year/[year]/page.tsx | 8 +- package.json | 1 + pnpm-lock.yaml | 1122 ++++++++--------- src/admin/AdminAlbumBadge.tsx | 21 + src/admin/AdminAlbumForm.tsx | 76 ++ src/admin/AdminAlbumsTable.tsx | 46 + src/admin/AdminAppMenu.tsx | 19 +- src/admin/AdminBadge.tsx | 42 + src/admin/AdminNav.tsx | 12 + src/admin/AdminRecipeBadge.tsx | 2 +- src/admin/AdminTagBadge.tsx | 37 +- .../{AdminTagTable.tsx => AdminTagsTable.tsx} | 2 +- src/admin/actions.ts | 8 +- src/admin/insights/AdminAppInsights.tsx | 2 +- src/admin/insights/AdminAppInsightsClient.tsx | 5 +- src/admin/insights/index.ts | 4 +- src/album/AlbumHeader.tsx | 91 ++ src/album/AlbumImageResponse.tsx | 46 + src/album/AlbumOverview.tsx | 38 + src/album/AlbumShareModal.tsx | 26 + src/album/FieldsetAlbum.tsx | 19 + src/album/PhotoAlbum.tsx | 27 + src/album/actions.ts | 23 + src/album/data.ts | 16 + src/album/form.ts | 52 + src/album/index.ts | 85 ++ src/album/query.ts | 124 ++ src/album/server.ts | 30 + .../HomeImageResponse.tsx | 6 +- .../TemplateImageResponse.tsx | 2 +- src/app/config.ts | 2 + src/app/path.ts | 75 +- src/app/static.ts | 4 +- src/camera/CameraHeader.tsx | 6 +- .../CameraImageResponse.tsx | 6 +- src/camera/CameraOGTile.tsx | 4 +- src/camera/CameraOverview.tsx | 4 +- src/camera/PhotoCamera.tsx | 6 +- src/camera/meta.ts | 6 +- src/category/data.ts | 16 +- src/category/index.ts | 15 +- src/category/useCategoryCounts.ts | 16 + src/category/useCategoryCountsForPhoto.ts | 46 - src/cmdk/CommandKClient.tsx | 14 + src/components/FieldsetWithStatus.tsx | 3 + src/components/MaskedScroll.tsx | 2 + src/components/TagInput.tsx | 59 +- src/components/entity/EntityLink.tsx | 43 +- src/components/icons/IconAlbum.tsx | 9 + src/{photo => }/db/index.ts | 18 +- src/{photo => }/db/migration.ts | 0 src/db/query.ts | 101 ++ src/feed/index.ts | 2 +- src/film/FilmHeader.tsx | 6 +- .../FilmImageResponse.tsx | 6 +- src/film/FilmOGTile.tsx | 4 +- src/film/FilmOverview.tsx | 4 +- src/film/PhotoFilm.tsx | 6 +- src/film/index.tsx | 6 +- src/focal/FocalLengthHeader.tsx | 6 +- .../FocalLengthImageResponse.tsx | 6 +- src/focal/FocalLengthOGTile.tsx | 4 +- src/focal/FocalLengthOverview.tsx | 4 +- src/focal/PhotoFocalLength.tsx | 5 +- src/focal/index.ts | 6 +- src/i18n/locales/bd-bn.ts | 3 + src/i18n/locales/en-gb.ts | 3 + src/i18n/locales/en-us.ts | 3 + src/i18n/locales/hi-in.ts | 3 + src/i18n/locales/id-id.ts | 3 + src/i18n/locales/pt-br.ts | 3 + src/i18n/locales/pt-pt.ts | 3 + src/i18n/locales/tr-tr.ts | 3 + src/i18n/locales/zh-cn.ts | 3 + src/lens/LensHeader.tsx | 6 +- .../LensImageResponse.tsx | 6 +- src/lens/LensOGTile.tsx | 4 +- src/lens/LensOverview.tsx | 4 +- src/lens/PhotoLens.tsx | 5 +- src/lens/meta.ts | 15 +- src/photo/PhotoDetailPage.tsx | 57 +- src/photo/PhotoEditPageClient.tsx | 7 + src/photo/PhotoGridSidebar.tsx | 56 +- src/photo/PhotoHeader.tsx | 234 ++-- .../PhotoImageResponse.tsx | 6 +- src/photo/PhotoLarge.tsx | 22 +- src/photo/UploadPageClient.tsx | 4 + src/photo/actions.ts | 19 +- src/photo/ai/server.ts | 2 +- src/photo/cache.ts | 32 +- src/photo/form/PhotoForm.tsx | 37 +- src/photo/form/index.ts | 7 + src/photo/index.ts | 18 +- src/photo/{db => }/query.ts | 250 ++-- src/photo/server.ts | 2 +- src/recents/PhotoRecents.tsx | 8 +- src/recents/RecentsHeader.tsx | 6 +- .../RecentsImageResponse.tsx | 6 +- src/recents/RecentsOGTile.tsx | 4 +- src/recents/RecentsOverview.tsx | 4 +- src/recents/meta.ts | 4 +- src/recipe/PhotoRecipe.tsx | 5 +- src/recipe/PhotoRecipeOverlay.tsx | 8 - src/recipe/RecipeHeader.tsx | 6 +- .../RecipeImageResponse.tsx | 6 +- src/recipe/RecipeOGTile.tsx | 4 +- src/recipe/RecipeOverview.tsx | 4 +- src/recipe/index.ts | 6 +- src/share/ShareModals.tsx | 7 +- src/social/SocialButton.tsx | 4 +- src/tag/AlbumOGTile.tsx | 37 + src/tag/PhotoFavs.tsx | 5 +- src/tag/PhotoTag.tsx | 5 +- src/tag/PhotoTags.tsx | 4 +- src/tag/TagHeader.tsx | 8 +- .../TagImageResponse.tsx | 6 +- src/tag/TagOGTile.tsx | 4 +- src/tag/TagOverview.tsx | 4 +- src/tag/index.ts | 6 +- src/utility/html.ts | 14 +- src/{years => year}/PhotoYear.tsx | 7 +- src/{years => year}/YearHeader.tsx | 6 +- .../YearImageResponse.tsx | 6 +- src/{years => year}/YearOGTile.tsx | 4 +- src/{years => year}/YearOverview.tsx | 4 +- src/{years => year}/YearShareModal.tsx | 0 src/{years => year}/data.ts | 0 src/{years => year}/index.ts | 0 src/{years => year}/meta.ts | 4 +- 166 files changed, 2667 insertions(+), 1300 deletions(-) create mode 100644 app/admin/albums/[album]/edit/page.tsx create mode 100644 app/admin/albums/page.tsx create mode 100644 app/album/[album]/[photoId]/page.tsx create mode 100644 app/album/[album]/image/route.tsx create mode 100644 app/album/[album]/page.tsx create mode 100644 src/admin/AdminAlbumBadge.tsx create mode 100644 src/admin/AdminAlbumForm.tsx create mode 100644 src/admin/AdminAlbumsTable.tsx create mode 100644 src/admin/AdminBadge.tsx rename src/admin/{AdminTagTable.tsx => AdminTagsTable.tsx} (97%) create mode 100644 src/album/AlbumHeader.tsx create mode 100644 src/album/AlbumImageResponse.tsx create mode 100644 src/album/AlbumOverview.tsx create mode 100644 src/album/AlbumShareModal.tsx create mode 100644 src/album/FieldsetAlbum.tsx create mode 100644 src/album/PhotoAlbum.tsx create mode 100644 src/album/actions.ts create mode 100644 src/album/data.ts create mode 100644 src/album/form.ts create mode 100644 src/album/index.ts create mode 100644 src/album/query.ts create mode 100644 src/album/server.ts rename src/{image-response => app}/HomeImageResponse.tsx (73%) rename src/{image-response => app}/TemplateImageResponse.tsx (97%) rename src/{image-response => camera}/CameraImageResponse.tsx (86%) delete mode 100644 src/category/useCategoryCountsForPhoto.ts create mode 100644 src/components/icons/IconAlbum.tsx rename src/{photo => }/db/index.ts (92%) rename src/{photo => }/db/migration.ts (100%) create mode 100644 src/db/query.ts rename src/{image-response => film}/FilmImageResponse.tsx (80%) rename src/{image-response => focal}/FocalLengthImageResponse.tsx (82%) rename src/{image-response => lens}/LensImageResponse.tsx (82%) rename src/{image-response => photo}/PhotoImageResponse.tsx (85%) rename src/photo/{db => }/query.ts (74%) rename src/{image-response => recents}/RecentsImageResponse.tsx (79%) rename src/{image-response => recipe}/RecipeImageResponse.tsx (93%) create mode 100644 src/tag/AlbumOGTile.tsx rename src/{image-response => tag}/TagImageResponse.tsx (86%) rename src/{years => year}/PhotoYear.tsx (69%) rename src/{years => year}/YearHeader.tsx (87%) rename src/{image-response => year}/YearImageResponse.tsx (79%) rename src/{years => year}/YearOGTile.tsx (86%) rename src/{years => year}/YearOverview.tsx (85%) rename src/{years => year}/YearShareModal.tsx (100%) rename src/{years => year}/data.ts (100%) rename src/{years => year}/index.ts (100%) rename src/{years => year}/meta.ts (82%) diff --git a/app/admin/albums/[album]/edit/page.tsx b/app/admin/albums/[album]/edit/page.tsx new file mode 100644 index 00000000..c45985b5 --- /dev/null +++ b/app/admin/albums/[album]/edit/page.tsx @@ -0,0 +1,53 @@ +import AdminChildPage from '@/components/AdminChildPage'; +import { redirect } from 'next/navigation'; +import { getPhotosCached, getPhotosMetaCached } from '@/photo/cache'; +import { PATH_ADMIN, PATH_ADMIN_ALBUMS, pathForAlbum } from '@/app/path'; +import PhotoLightbox from '@/photo/PhotoLightbox'; +import { getAlbumFromSlug } from '@/album/query'; +import AdminAlbumBadge from '@/admin/AdminAlbumBadge'; +import AdminAlbumForm from '@/admin/AdminAlbumForm'; + +const MAX_PHOTO_TO_SHOW = 6; + +interface Props { + params: Promise<{ album: string }> +} + +export default async function AlbumPageEdit({ + params, +}: Props) { + const { album: albumFromParams } = await params; + + const albumSlug = decodeURIComponent(albumFromParams); + + const album = await getAlbumFromSlug(albumSlug); + + if (!album) { redirect(PATH_ADMIN); } + + const [ + { count }, + photos, + ] = await Promise.all([ + getPhotosMetaCached({ album }), + getPhotosCached({ album, limit: MAX_PHOTO_TO_SHOW }), + ]); + + if (count === 0) { redirect(PATH_ADMIN); } + + return ( + } + > + + {photos.length > 0 && + } + + + ); +}; diff --git a/app/admin/albums/page.tsx b/app/admin/albums/page.tsx new file mode 100644 index 00000000..398c54e7 --- /dev/null +++ b/app/admin/albums/page.tsx @@ -0,0 +1,18 @@ +import AdminAlbumsTable from '@/admin/AdminAlbumsTable'; +import { getAlbumsWithMeta } from '@/album/query'; +import AppGrid from '@/components/AppGrid'; + +export default async function AdminTagsPage() { + const albums = await getAlbumsWithMeta(); + + return ( + +
+ +
+ } + /> + ); +} diff --git a/app/admin/photos/[photoId]/edit/page.tsx b/app/admin/photos/[photoId]/edit/page.tsx index f0a588a9..109140b5 100644 --- a/app/admin/photos/[photoId]/edit/page.tsx +++ b/app/admin/photos/[photoId]/edit/page.tsx @@ -17,6 +17,7 @@ import { getOptimizedPhotoUrlForManipulation, getStorageUrlsForPhoto, } from '@/photo/storage'; +import { getAlbumsWithMeta, getAlbumTitlesForPhoto } from '@/album/query'; export default async function PhotoEditPage({ params, @@ -27,11 +28,15 @@ export default async function PhotoEditPage({ const [ photo, + photoAlbumTitles, + albums, uniqueTags, uniqueRecipes, uniqueFilms, ] = await Promise.all([ getPhotoNoStore(photoId, true), + getAlbumTitlesForPhoto(photoId), + getAlbumsWithMeta(), getUniqueTagsCached(), getUniqueRecipesCached(), getUniqueFilmsCached(), @@ -60,6 +65,8 @@ export default async function PhotoEditPage({ } > - + []); diff --git a/app/admin/tags/[tag]/edit/page.tsx b/app/admin/tags/[tag]/edit/page.tsx index a741fdd8..c5fa373e 100644 --- a/app/admin/tags/[tag]/edit/page.tsx +++ b/app/admin/tags/[tag]/edit/page.tsx @@ -12,7 +12,7 @@ interface Props { params: Promise<{ tag: string }> } -export default async function PhotoPageEdit({ +export default async function TagPageEdit({ params, }: Props) { const { tag: tagFromParams } = await params; @@ -35,7 +35,7 @@ export default async function PhotoPageEdit({ backLabel="Tags" breadcrumb={} > - + []); @@ -10,7 +10,7 @@ export default async function AdminTagsPage() { contentMain={
- +
} /> diff --git a/app/admin/uploads/[uploadPath]/page.tsx b/app/admin/uploads/[uploadPath]/page.tsx index fcd532b7..24b69c16 100644 --- a/app/admin/uploads/[uploadPath]/page.tsx +++ b/app/admin/uploads/[uploadPath]/page.tsx @@ -13,7 +13,8 @@ import { BLUR_ENABLED, } from '@/app/config'; import ErrorNote from '@/components/ErrorNote'; -import { getRecipeTitleForData } from '@/photo/db/query'; +import { getRecipeTitleForData } from '@/photo/query'; +import { getAlbumsWithMeta } from '@/album/query'; export const maxDuration = 60; @@ -48,11 +49,13 @@ export default async function UploadPage({ params, searchParams }: Params) { } const [ + albums, uniqueTags, uniqueRecipes, uniqueFilms, recipeTitle, ] = await Promise.all([ + getAlbumsWithMeta(), getUniqueTagsCached(), getUniqueRecipesCached(), getUniqueFilmsCached(), @@ -83,6 +86,7 @@ export default async function UploadPage({ params, searchParams }: Params) { ? + getPhotosNearIdCached( + photoId, + { album, limit: RELATED_GRID_PHOTOS_TO_SHOW + 2 }, + )); + +interface PhotoTagProps { + params: Promise<{ photoId: string, album: string }> +} + +export async function generateMetadata({ + params, +}: PhotoTagProps): Promise { + const { photoId, album: albumFromParams } = await params; + + const albumSlug = decodeURIComponent(albumFromParams); + + const album = await getAlbumFromSlug(albumSlug); + + if (!album) { return {}; } + + const { photo } = await getPhotosNearIdCachedCached(photoId, album); + + if (!photo) { return {}; } + + const title = titleForPhoto(photo); + const description = descriptionForPhoto(photo); + const descriptionHtml = descriptionForPhoto(photo, true); + const images = absolutePathForPhotoImage(photo); + const url = absolutePathForPhoto({ photo, album }); + + return { + title, + description: descriptionHtml, + openGraph: { + title, + images, + description, + url, + }, + twitter: { + title, + description, + images, + card: 'summary_large_image', + }, + }; +} + +export default async function PhotoAlbumPage({ + params, +}: PhotoTagProps) { + const { photoId, album: albumFromParams } = await params; + + const albumSlug = decodeURIComponent(albumFromParams); + + const album = await getAlbumFromSlug(albumSlug); + + if (!album) { redirect(PATH_ROOT); } + + const { photo, photos, photosGrid, indexNumber } = + await getPhotosNearIdCachedCached(photoId, album); + + if (!photo) { redirect(PATH_ROOT); } + + const { count, dateRange } = await getPhotosMetaCached({ album }); + + return ( + + ); +} diff --git a/app/album/[album]/image/route.tsx b/app/album/[album]/image/route.tsx new file mode 100644 index 00000000..0d527016 --- /dev/null +++ b/app/album/[album]/image/route.tsx @@ -0,0 +1,52 @@ +import { getPhotosCached } from '@/photo/cache'; +import { + IMAGE_OG_DIMENSION_SMALL, + MAX_PHOTOS_TO_SHOW_PER_CATEGORY, +} from '@/image-response'; +import { getIBMPlexMono } from '@/app/font'; +import { ImageResponse } from 'next/og'; +import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; +import { staticallyGenerateCategoryIfConfigured } from '@/app/static'; +import AlbumImageResponse from '@/album/AlbumImageResponse'; +import { getAlbumFromSlug, getAlbumsWithMeta } from '@/album/query'; + +export const generateStaticParams = staticallyGenerateCategoryIfConfigured( + 'albums', + 'image', + getAlbumsWithMeta, + albums => albums.map(({ album }) => ({ album: album.slug })), +); + +export async function GET( + _: Request, + context: { params: Promise<{ album: string }> }, +) { + const { album: albumParam } = await context.params; + + const album = await getAlbumFromSlug(decodeURIComponent(albumParam)); + + if (!album) { return new Response('Album not found', { status: 404 }); } + + const [ + photos, + { fontFamily, fonts }, + headers, + ] = await Promise.all([ + getPhotosCached({ limit: MAX_PHOTOS_TO_SHOW_PER_CATEGORY, album }), + getIBMPlexMono(), + getImageResponseCacheControlHeaders(), + ]); + + const { width, height } = IMAGE_OG_DIMENSION_SMALL; + + return new ImageResponse( + , + { width, height, fonts, headers }, + ); +} diff --git a/app/album/[album]/page.tsx b/app/album/[album]/page.tsx new file mode 100644 index 00000000..f4a9747d --- /dev/null +++ b/app/album/[album]/page.tsx @@ -0,0 +1,99 @@ +import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo'; +import { getPhotos } from '@/photo/query'; +import { PATH_ROOT } from '@/app/path'; +import type { Metadata } from 'next'; +import { redirect } from 'next/navigation'; +import { cache } from 'react'; +import { staticallyGenerateCategoryIfConfigured } from '@/app/static'; +import { getAppText } from '@/i18n/state/server'; +import AlbumOverview from '@/album/AlbumOverview'; +import { + getAlbumFromSlug, + getAlbumsWithMeta, + getTagsForAlbum, +} from '@/album/query'; +import { Album, generateMetaForAlbum } from '@/album'; +import { getPhotosAlbumDataCached } from '@/album/data'; + +const getPhotosAlbumDataCachedCached = cache((album: Album) => + getPhotosAlbumDataCached({ album, limit: INFINITE_SCROLL_GRID_INITIAL})); + +export const generateStaticParams = staticallyGenerateCategoryIfConfigured( + 'albums', + 'page', + getAlbumsWithMeta, + albums => albums.map(({ album }) => ({ album: album.slug })), +); + +interface AlbumProps { + params: Promise<{ album: string }> +} + +export async function generateMetadata({ + params, +}: AlbumProps): Promise { + const { album: albumFromParams } = await params; + + const albumSlug = decodeURIComponent(albumFromParams); + + const album = await getAlbumFromSlug(albumSlug); + + if (!album) { return {}; } + + const [ + photos, + { count, dateRange }, + ] = await getPhotosAlbumDataCachedCached(album); + + if (photos.length === 0) { return {}; } + + const appText = await getAppText(); + + const { + url, + title, + description, + images, + } = generateMetaForAlbum(album, photos, appText, count, dateRange); + + return { + title, + openGraph: { + title, + description, + images, + url, + }, + twitter: { + images, + description, + card: 'summary_large_image', + }, + description, + }; +} + +export default async function AlbumPage({ + params, +}:AlbumProps) { + const { album: albumFromParams } = await params; + + const albumSlug = decodeURIComponent(albumFromParams); + + const album = await getAlbumFromSlug(albumSlug); + + if (!album) { redirect(PATH_ROOT); } + + const photos = await getPhotos({ album }); + + const tags = await getTagsForAlbum(album.id); + + return ( + + ); +} diff --git a/app/film/[film]/image/route.tsx b/app/film/[film]/image/route.tsx index 330d0355..fc98f85a 100644 --- a/app/film/[film]/image/route.tsx +++ b/app/film/[film]/image/route.tsx @@ -3,12 +3,11 @@ import { IMAGE_OG_DIMENSION_SMALL, MAX_PHOTOS_TO_SHOW_PER_CATEGORY, } from '@/image-response'; -import FilmImageResponse from - '@/image-response/FilmImageResponse'; +import FilmImageResponse from '@/film/FilmImageResponse'; import { getIBMPlexMono } from '@/app/font'; import { ImageResponse } from 'next/og'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; -import { getUniqueFilms } from '@/photo/db/query'; +import { getUniqueFilms } from '@/photo/query'; import { staticallyGenerateCategoryIfConfigured } from '@/app/static'; export const generateStaticParams = staticallyGenerateCategoryIfConfigured( diff --git a/app/film/[film]/page.tsx b/app/film/[film]/page.tsx index 94a51666..c3f01789 100644 --- a/app/film/[film]/page.tsx +++ b/app/film/[film]/page.tsx @@ -1,5 +1,5 @@ import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo'; -import { getUniqueFilms } from '@/photo/db/query'; +import { getUniqueFilms } from '@/photo/query'; import { generateMetaForFilm } from '@/film'; import FilmOverview from '@/film/FilmOverview'; import { getPhotosFilmDataCached } from '@/film/data'; diff --git a/app/focal/[focal]/image/route.tsx b/app/focal/[focal]/image/route.tsx index 0e3ed652..a5998a31 100644 --- a/app/focal/[focal]/image/route.tsx +++ b/app/focal/[focal]/image/route.tsx @@ -6,10 +6,9 @@ import { import { getIBMPlexMono } from '@/app/font'; import { ImageResponse } from 'next/og'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; -import FocalLengthImageResponse from - '@/image-response/FocalLengthImageResponse'; +import FocalLengthImageResponse from '@/focal/FocalLengthImageResponse'; import { formatFocalLength, getFocalLengthFromString } from '@/focal'; -import { getUniqueFocalLengths } from '@/photo/db/query'; +import { getUniqueFocalLengths } from '@/photo/query'; import { staticallyGenerateCategoryIfConfigured } from '@/app/static'; export const generateStaticParams = staticallyGenerateCategoryIfConfigured( diff --git a/app/focal/[focal]/page.tsx b/app/focal/[focal]/page.tsx index 3cd36f90..d8a7c028 100644 --- a/app/focal/[focal]/page.tsx +++ b/app/focal/[focal]/page.tsx @@ -2,7 +2,7 @@ import { generateMetaForFocalLength, getFocalLengthFromString } from '@/focal'; import FocalLengthOverview from '@/focal/FocalLengthOverview'; import { getPhotosFocalLengthDataCached } from '@/focal/data'; import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo'; -import { getUniqueFocalLengths } from '@/photo/db/query'; +import { getUniqueFocalLengths } from '@/photo/query'; import { PATH_ROOT } from '@/app/path'; import type { Metadata } from 'next'; import { redirect } from 'next/navigation'; diff --git a/app/full/[sortType]/[sortOrder]/page.tsx b/app/full/[sortType]/[sortOrder]/page.tsx index 9204760e..ec60ea4d 100644 --- a/app/full/[sortType]/[sortOrder]/page.tsx +++ b/app/full/[sortType]/[sortOrder]/page.tsx @@ -2,12 +2,12 @@ import { generateOgImageMetaForPhotos } from '@/photo'; import PhotosEmptyState from '@/photo/PhotosEmptyState'; import { Metadata } from 'next/types'; import { cache } from 'react'; -import { getPhotos } from '@/photo/db/query'; +import { getPhotos } from '@/photo/query'; import PhotoFullPage from '@/photo/PhotoFullPage'; import { getPhotosMetaCached } from '@/photo/cache'; import { SortProps } from '@/photo/sort'; import { getSortOptionsFromParams } from '@/photo/sort/path'; -import { PhotoQueryOptions } from '@/photo/db'; +import { PhotoQueryOptions } from '@/db'; import { FEED_META_QUERY_OPTIONS, getFeedQueryOptions } from '@/feed'; export const maxDuration = 60; diff --git a/app/full/page.tsx b/app/full/page.tsx index 57e41bb2..c3e17380 100644 --- a/app/full/page.tsx +++ b/app/full/page.tsx @@ -2,7 +2,7 @@ import { generateOgImageMetaForPhotos } from '@/photo'; import PhotosEmptyState from '@/photo/PhotosEmptyState'; import { Metadata } from 'next/types'; import { cache } from 'react'; -import { getPhotos } from '@/photo/db/query'; +import { getPhotos } from '@/photo/query'; import PhotoFullPage from '@/photo/PhotoFullPage'; import { getPhotosMetaCached } from '@/photo/cache'; import { USER_DEFAULT_SORT_OPTIONS } from '@/app/config'; diff --git a/app/grid/[sortType]/[sortOrder]/page.tsx b/app/grid/[sortType]/[sortOrder]/page.tsx index e688b3ea..e332d7ef 100644 --- a/app/grid/[sortType]/[sortOrder]/page.tsx +++ b/app/grid/[sortType]/[sortOrder]/page.tsx @@ -1,7 +1,7 @@ import { generateOgImageMetaForPhotos } from '@/photo'; import PhotosEmptyState from '@/photo/PhotosEmptyState'; import { Metadata } from 'next/types'; -import { getPhotos } from '@/photo/db/query'; +import { getPhotos } from '@/photo/query'; import { cache } from 'react'; import PhotoGridPage from '@/photo/PhotoGridPage'; import { getDataForCategoriesCached } from '@/category/cache'; @@ -9,7 +9,7 @@ import { getPhotosMetaCached } from '@/photo/cache'; import { SortProps } from '@/photo/sort'; import { getSortOptionsFromParams } from '@/photo/sort/path'; import { FEED_META_QUERY_OPTIONS, getFeedQueryOptions } from '@/feed'; -import { PhotoQueryOptions } from '@/photo/db'; +import { PhotoQueryOptions } from '@/db'; export const maxDuration = 60; diff --git a/app/grid/page.tsx b/app/grid/page.tsx index 8f59e512..ba6fe55f 100644 --- a/app/grid/page.tsx +++ b/app/grid/page.tsx @@ -1,7 +1,7 @@ import { generateOgImageMetaForPhotos } from '@/photo'; import PhotosEmptyState from '@/photo/PhotosEmptyState'; import { Metadata } from 'next/types'; -import { getPhotos } from '@/photo/db/query'; +import { getPhotos } from '@/photo/query'; import { cache } from 'react'; import PhotoGridPage from '@/photo/PhotoGridPage'; import { getDataForCategoriesCached } from '@/category/cache'; diff --git a/app/home-image/route.tsx b/app/home-image/route.tsx index 68df76be..b85df89a 100644 --- a/app/home-image/route.tsx +++ b/app/home-image/route.tsx @@ -3,7 +3,7 @@ import { IMAGE_OG_DIMENSION_SMALL, MAX_PHOTOS_TO_SHOW_OG, } from '@/image-response'; -import HomeImageResponse from '@/image-response/HomeImageResponse'; +import HomeImageResponse from '@/app/HomeImageResponse'; import { getIBMPlexMono } from '@/app/font'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; import { APP_OG_IMAGE_QUERY_OPTIONS } from '@/feed'; diff --git a/app/lens/[make]/[model]/image/route.tsx b/app/lens/[make]/[model]/image/route.tsx index 3b90b86b..0d6f1529 100644 --- a/app/lens/[make]/[model]/image/route.tsx +++ b/app/lens/[make]/[model]/image/route.tsx @@ -6,13 +6,13 @@ import { import { getIBMPlexMono } from '@/app/font'; import { ImageResponse } from 'next/og'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; -import { getUniqueLenses } from '@/photo/db/query'; +import { getUniqueLenses } from '@/photo/query'; import { getLensFromParams, LensProps, safelyGenerateLensStaticParams, } from '@/lens'; -import LensImageResponse from '@/image-response/LensImageResponse'; +import LensImageResponse from '@/lens/LensImageResponse'; import { staticallyGenerateCategoryIfConfigured } from '@/app/static'; export const generateStaticParams = staticallyGenerateCategoryIfConfigured( diff --git a/app/lens/[make]/[model]/page.tsx b/app/lens/[make]/[model]/page.tsx index cf8585d9..0f6df487 100644 --- a/app/lens/[make]/[model]/page.tsx +++ b/app/lens/[make]/[model]/page.tsx @@ -1,7 +1,7 @@ import { Metadata } from 'next/types'; import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo'; import { cache } from 'react'; -import { getUniqueLenses } from '@/photo/db/query'; +import { getUniqueLenses } from '@/photo/query'; import { generateMetaForLens } from '@/lens/meta'; import { getPhotosLensDataCached } from '@/lens/data'; import LensOverview from '@/lens/LensOverview'; diff --git a/app/p/[photoId]/image/route.tsx b/app/p/[photoId]/image/route.tsx index 6f8b6024..516e9180 100644 --- a/app/p/[photoId]/image/route.tsx +++ b/app/p/[photoId]/image/route.tsx @@ -1,6 +1,6 @@ import { getPhotoCached } from '@/photo/cache'; import { IMAGE_OG_DIMENSION } from '@/image-response'; -import PhotoImageResponse from '@/image-response/PhotoImageResponse'; +import PhotoImageResponse from '@/photo/PhotoImageResponse'; import { getIBMPlexMono } from '@/app/font'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; import { staticallyGeneratePhotosIfConfigured } from '@/app/static'; diff --git a/app/page.tsx b/app/page.tsx index 9711ae98..f954497d 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -2,7 +2,7 @@ import { generateOgImageMetaForPhotos } from '@/photo'; import PhotosEmptyState from '@/photo/PhotosEmptyState'; import { Metadata } from 'next/types'; import { cache } from 'react'; -import { getPhotos } from '@/photo/db/query'; +import { getPhotos } from '@/photo/query'; import { GRID_HOMEPAGE_ENABLED, USER_DEFAULT_SORT_OPTIONS } from '@/app/config'; import { NULL_CATEGORY_DATA } from '@/category/data'; import PhotoFullPage from '@/photo/PhotoFullPage'; diff --git a/app/recents/image/route.tsx b/app/recents/image/route.tsx index d7a40faa..e4fab8ef 100644 --- a/app/recents/image/route.tsx +++ b/app/recents/image/route.tsx @@ -4,7 +4,7 @@ import { MAX_PHOTOS_TO_SHOW_PER_CATEGORY, } from '@/image-response'; import RecentsImageResponse from - '@/image-response/RecentsImageResponse'; + '@/recents/RecentsImageResponse'; import { getIBMPlexMono } from '@/app/font'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; import { getAppText } from '@/i18n/state/server'; diff --git a/app/recipe/[recipe]/image/route.tsx b/app/recipe/[recipe]/image/route.tsx index 74868273..2a023ad0 100644 --- a/app/recipe/[recipe]/image/route.tsx +++ b/app/recipe/[recipe]/image/route.tsx @@ -6,8 +6,8 @@ import { import { getIBMPlexMono } from '@/app/font'; import { ImageResponse } from 'next/og'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; -import { getUniqueRecipes } from '@/photo/db/query'; -import RecipeImageResponse from '@/image-response/RecipeImageResponse'; +import { getUniqueRecipes } from '@/photo/query'; +import RecipeImageResponse from '@/recipe/RecipeImageResponse'; import { staticallyGenerateCategoryIfConfigured } from '@/app/static'; export const generateStaticParams = staticallyGenerateCategoryIfConfigured( diff --git a/app/recipe/[recipe]/page.tsx b/app/recipe/[recipe]/page.tsx index 1e3d9cca..bcafa2a6 100644 --- a/app/recipe/[recipe]/page.tsx +++ b/app/recipe/[recipe]/page.tsx @@ -1,5 +1,5 @@ import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo'; -import { getUniqueRecipes } from '@/photo/db/query'; +import { getUniqueRecipes } from '@/photo/query'; import { PATH_ROOT } from '@/app/path'; import type { Metadata } from 'next'; import { redirect } from 'next/navigation'; diff --git a/app/shot-on/[make]/[model]/image/route.tsx b/app/shot-on/[make]/[model]/image/route.tsx index 658a8efb..86bbcf66 100644 --- a/app/shot-on/[make]/[model]/image/route.tsx +++ b/app/shot-on/[make]/[model]/image/route.tsx @@ -4,11 +4,11 @@ import { IMAGE_OG_DIMENSION_SMALL, MAX_PHOTOS_TO_SHOW_PER_CATEGORY, } from '@/image-response'; -import CameraImageResponse from '@/image-response/CameraImageResponse'; +import CameraImageResponse from '@/camera/CameraImageResponse'; import { getIBMPlexMono } from '@/app/font'; import { ImageResponse } from 'next/og'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; -import { getUniqueCameras } from '@/photo/db/query'; +import { getUniqueCameras } from '@/photo/query'; import { staticallyGenerateCategoryIfConfigured } from '@/app/static'; export const generateStaticParams = staticallyGenerateCategoryIfConfigured( diff --git a/app/shot-on/[make]/[model]/page.tsx b/app/shot-on/[make]/[model]/page.tsx index a9795a6b..07794cc9 100644 --- a/app/shot-on/[make]/[model]/page.tsx +++ b/app/shot-on/[make]/[model]/page.tsx @@ -5,7 +5,7 @@ import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo'; import { getPhotosCameraDataCached } from '@/camera/data'; import CameraOverview from '@/camera/CameraOverview'; import { cache } from 'react'; -import { getUniqueCameras } from '@/photo/db/query'; +import { getUniqueCameras } from '@/photo/query'; import { staticallyGenerateCategoryIfConfigured } from '@/app/static'; import { getAppText } from '@/i18n/state/server'; diff --git a/app/sitemap.ts b/app/sitemap.ts index cfccc1ca..036cabda 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -3,6 +3,7 @@ import { getDataForCategoriesCached } from '@/category/cache'; import { ABSOLUTE_PATH_FULL, ABSOLUTE_PATH_GRID, + absolutePathForAlbum, absolutePathForCamera, absolutePathForFilm, absolutePathForFocalLength, @@ -15,7 +16,7 @@ import { } from '@/app/path'; import { isTagFavs } from '@/tag'; import { BASE_URL, GRID_HOMEPAGE_ENABLED } from '@/app/config'; -import { getPhotoIdsAndUpdatedAt } from '@/photo/db/query'; +import { getPhotoIdsAndUpdatedAt } from '@/photo/query'; // Cache for 24 hours export const revalidate = 86_400; @@ -33,6 +34,7 @@ export default async function sitemap(): Promise { years, cameras, lenses, + albums, tags, recipes, films, @@ -45,6 +47,7 @@ export default async function sitemap(): Promise { years: [], cameras: [], lenses: [], + albums: [], tags: [], recipes: [], films: [], @@ -58,6 +61,7 @@ export default async function sitemap(): Promise { ...years.map(({ lastModified }) => lastModified), ...cameras.map(({ lastModified }) => lastModified), ...lenses.map(({ lastModified }) => lastModified), + ...albums.map(({ lastModified }) => lastModified), ...tags.map(({ lastModified }) => lastModified), ...recipes.map(({ lastModified }) => lastModified), ...films.map(({ lastModified }) => lastModified), @@ -104,6 +108,12 @@ export default async function sitemap(): Promise { priority: PRIORITY_CATEGORY, lastModified, })), + // Albums + ...albums.map(({ album, lastModified }) => ({ + url: absolutePathForAlbum(album), + priority: PRIORITY_CATEGORY, + lastModified, + })), // Tags ...tags.map(({ tag, lastModified }) => ({ url: absolutePathForTag(tag), diff --git a/app/tag/[tag]/image/route.tsx b/app/tag/[tag]/image/route.tsx index ccd59e5c..e8445104 100644 --- a/app/tag/[tag]/image/route.tsx +++ b/app/tag/[tag]/image/route.tsx @@ -3,11 +3,11 @@ import { IMAGE_OG_DIMENSION_SMALL, MAX_PHOTOS_TO_SHOW_PER_CATEGORY, } from '@/image-response'; -import TagImageResponse from '@/image-response/TagImageResponse'; +import TagImageResponse from '@/tag/TagImageResponse'; import { getIBMPlexMono } from '@/app/font'; import { ImageResponse } from 'next/og'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; -import { getUniqueTags } from '@/photo/db/query'; +import { getUniqueTags } from '@/photo/query'; import { staticallyGenerateCategoryIfConfigured } from '@/app/static'; export const generateStaticParams = staticallyGenerateCategoryIfConfigured( diff --git a/app/tag/[tag]/page.tsx b/app/tag/[tag]/page.tsx index 07b4f0aa..79ba463c 100644 --- a/app/tag/[tag]/page.tsx +++ b/app/tag/[tag]/page.tsx @@ -1,5 +1,5 @@ import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo'; -import { getUniqueTags } from '@/photo/db/query'; +import { getUniqueTags } from '@/photo/query'; import { PATH_ROOT } from '@/app/path'; import { generateMetaForTag } from '@/tag'; import TagOverview from '@/tag/TagOverview'; diff --git a/app/template-image-tight/route.tsx b/app/template-image-tight/route.tsx index 61b3fcd3..007a8eeb 100644 --- a/app/template-image-tight/route.tsx +++ b/app/template-image-tight/route.tsx @@ -4,7 +4,7 @@ import { MAX_PHOTOS_TO_SHOW_TEMPLATE_TIGHT, } from '@/image-response'; import TemplateImageResponse from - '@/image-response/TemplateImageResponse'; + '@/app/TemplateImageResponse'; import { getIBMPlexMono } from '@/app/font'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; import { safePhotoImageResponse } from '@/platforms/safe-photo-image-response'; diff --git a/app/template-image/route.tsx b/app/template-image/route.tsx index 96ebe72a..3c90537a 100644 --- a/app/template-image/route.tsx +++ b/app/template-image/route.tsx @@ -4,7 +4,7 @@ import { MAX_PHOTOS_TO_SHOW_TEMPLATE, } from '@/image-response'; import TemplateImageResponse from - '@/image-response/TemplateImageResponse'; + '@/app/TemplateImageResponse'; import { getIBMPlexMono } from '@/app/font'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; import { safePhotoImageResponse } from '@/platforms/safe-photo-image-response'; diff --git a/app/year/[year]/image/route.tsx b/app/year/[year]/image/route.tsx index 669425cb..02294b7f 100644 --- a/app/year/[year]/image/route.tsx +++ b/app/year/[year]/image/route.tsx @@ -3,12 +3,11 @@ import { IMAGE_OG_DIMENSION_SMALL, MAX_PHOTOS_TO_SHOW_PER_CATEGORY, } from '@/image-response'; -import YearImageResponse from - '@/image-response/YearImageResponse'; +import YearImageResponse from '@/year/YearImageResponse'; import { getIBMPlexMono } from '@/app/font'; import { ImageResponse } from 'next/og'; import { getImageResponseCacheControlHeaders } from '@/image-response/cache'; -import { getUniqueYears } from '@/photo/db/query'; +import { getUniqueYears } from '@/photo/query'; import { staticallyGenerateCategoryIfConfigured } from '@/app/static'; export const generateStaticParams = staticallyGenerateCategoryIfConfigured( diff --git a/app/year/[year]/page.tsx b/app/year/[year]/page.tsx index 8a3871d4..27620149 100644 --- a/app/year/[year]/page.tsx +++ b/app/year/[year]/page.tsx @@ -1,8 +1,8 @@ import { INFINITE_SCROLL_GRID_INITIAL } from '@/photo'; -import { getUniqueYears } from '@/photo/db/query'; -import { generateMetaForYear } from '@/years/meta'; -import YearOverview from '@/years/YearOverview'; -import { getPhotosYearDataCached } from '@/years/data'; +import { getUniqueYears } from '@/photo/query'; +import { generateMetaForYear } from '@/year/meta'; +import YearOverview from '@/year/YearOverview'; +import { getPhotosYearDataCached } from '@/year/data'; import { Metadata } from 'next/types'; import { cache } from 'react'; import { PATH_ROOT } from '@/app/path'; diff --git a/package.json b/package.json index 54eea3bf..38e403ee 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "test": "jest --watch --transformIgnorePatterns 'node_modules/(?!my-library-dir)/'", "analyze": "ANALYZE=true next build" }, + "packageManager": "pnpm@10.16.1", "dependencies": { "@ai-sdk/openai": "^2.0.28", "@ai-sdk/rsc": "^1.0.41", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 709ac4de..fe5680b2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,10 +10,10 @@ importers: dependencies: '@ai-sdk/openai': specifier: ^2.0.28 - version: 2.0.28(zod@3.25.76) + version: 2.0.30(zod@4.1.8) '@ai-sdk/rsc': specifier: ^1.0.41 - version: 1.0.41(react@19.1.1)(zod@3.25.76) + version: 1.0.44(react@19.1.1)(zod@4.1.8) '@aws-sdk/client-s3': specifier: 3.887.0 version: 3.887.0 @@ -40,16 +40,16 @@ importers: version: 1.35.3 '@vercel/analytics': specifier: ^1.5.0 - version: 1.5.0(next@15.5.3(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1) + version: 1.5.0(next@15.5.3(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1) '@vercel/blob': specifier: ^1.1.1 version: 1.1.1 '@vercel/speed-insights': specifier: ^1.2.0 - version: 1.2.0(next@15.5.3(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1) + version: 1.2.0(next@15.5.3(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1) ai: specifier: ^5.0.41 - version: 5.0.41(zod@3.25.76) + version: 5.0.44(zod@4.1.8) camelcase-keys: specifier: ^10.0.0 version: 10.0.0 @@ -88,10 +88,10 @@ importers: version: 5.1.5 next: specifier: 15.5.3 - version: 15.5.3(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 15.5.3(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) next-auth: specifier: 5.0.0-beta.29 - version: 5.0.0-beta.29(next@15.5.3(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1) + version: 5.0.0-beta.29(next@15.5.3(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1) next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -161,7 +161,7 @@ importers: version: 30.0.0 '@types/node': specifier: ^24.3.1 - version: 24.3.1 + version: 24.3.3 '@types/pg': specifier: ^8.15.5 version: 8.15.5 @@ -188,7 +188,7 @@ importers: version: 5.2.0(eslint@9.35.0(jiti@2.5.1)) jest: specifier: ^30.1.3 - version: 30.1.3(@types/node@24.3.1)(ts-node@10.9.2(@types/node@24.3.1)(typescript@5.9.2)) + version: 30.1.3(@types/node@24.3.3)(ts-node@10.9.2(@types/node@24.3.3)(typescript@5.9.2)) jest-environment-jsdom: specifier: ^30.1.2 version: 30.1.2 @@ -200,30 +200,30 @@ importers: version: 4.1.13 ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@24.3.1)(typescript@5.9.2) + version: 10.9.2(@types/node@24.3.3)(typescript@5.9.2) typescript: specifier: 5.9.2 version: 5.9.2 packages: - '@adobe/css-tools@4.4.3': - resolution: {integrity: sha512-VQKMkwriZbaOgVCby1UDY/LDk5fIjhQicCvVPFqfe+69fWaPWydbWJ3wRt59/YzIwda1I81loas3oCoHxnqvdA==} + '@adobe/css-tools@4.4.4': + resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} - '@ai-sdk/gateway@1.0.21': - resolution: {integrity: sha512-yQPrMb1v0P8GwmmpcCT2DGPfgJVRkJ9QaRRJGO0+Em+wI+Xv3lvHHIc3ImVR3jjVfJPih/cNWWALUgRERfQaxQ==} + '@ai-sdk/gateway@1.0.23': + resolution: {integrity: sha512-ynV7WxpRK2zWLGkdOtrU2hW22mBVkEYVS3iMg1+ZGmAYSgzCqzC74bfOJZ2GU1UdcrFWUsFI9qAYjsPkd+AebA==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4 - '@ai-sdk/openai@2.0.28': - resolution: {integrity: sha512-Z2mG7PjUKbpT8fMexE6yrorxXVzGHSl3jKF293w2i6s9Dc6X81Gf6Z0OGNnkrftLtW4PXr7RZ/9xoyusBZW4uA==} + '@ai-sdk/openai@2.0.30': + resolution: {integrity: sha512-a9Vf64OT2dWEFyEGv+OxtCs69B18BsuzInvuyUxVPczbIiBLqUCt3zcD/8EwqbTPJwsFNsL8/9nbVZFmwA1+2A==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4 - '@ai-sdk/provider-utils@3.0.8': - resolution: {integrity: sha512-cDj1iigu7MW2tgAQeBzOiLhjHOUM9vENsgh4oAVitek0d//WdgfPCsKO3euP7m7LyO/j9a1vr/So+BGNdpFXYw==} + '@ai-sdk/provider-utils@3.0.9': + resolution: {integrity: sha512-Pm571x5efqaI4hf9yW4KsVlDBDme8++UepZRnq+kqVBWWjgvGhQlzU8glaFq0YJEB9kkxZHbRRyVeHoV2sRYaQ==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4 @@ -232,8 +232,8 @@ packages: resolution: {integrity: sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==} engines: {node: '>=18'} - '@ai-sdk/rsc@1.0.41': - resolution: {integrity: sha512-7nFQ566lwzDe7oX4T+Hu11LdtLMGMbpaNi+cCUzpg99rYAkJ3dWaszExdlf4/E2TCXGWk7sMzRy117NvyMI5Sg==} + '@ai-sdk/rsc@1.0.44': + resolution: {integrity: sha512-WOnh8JCEJv0j8xZVmOnSHdMjxsLM+LAQ3lwJJqy7xAYhrhPDvlRTZ9c2xJORqDFeYTAGZe0NjnvP8TsOiJQ6Zg==} engines: {node: '>=18'} peerDependencies: react: ^18 || ^19 || ^19.0.0-rc @@ -246,10 +246,6 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - '@asamuzakjp/css-color@3.2.0': resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} @@ -406,8 +402,8 @@ packages: resolution: {integrity: sha512-ABDSP6KsrdD+JC7qwMqUpLXqPidvfgT+Q+W8sGGuk/IBy7smgZDOdYSZLE4VBbQpH3N/zSJuslAWhL2x37Qwww==} engines: {node: '>=18.0.0'} - '@aws-sdk/util-locate-window@3.804.0': - resolution: {integrity: sha512-zVoRfpmBVPodYlnMjgVjfGoEZagyRF5IPn3Uo6ZvOZp24chnW/FRstH7ESDHDDRga4z3V+ElUQHKpFDXWyBW5A==} + '@aws-sdk/util-locate-window@3.873.0': + resolution: {integrity: sha512-xcVhZF6svjM5Rj89T1WzkjQmrTF6dpR2UvIHPMTnSZoNe6CixejPZ6f0JJ2kAhO8H+dUHwNBlsUgOTIKiK/Syg==} engines: {node: '>=18.0.0'} '@aws-sdk/util-user-agent-browser@3.887.0': @@ -434,16 +430,16 @@ packages: resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.0': - resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} + '@babel/compat-data@7.28.4': + resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==} engines: {node: '>=6.9.0'} - '@babel/core@7.28.0': - resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==} + '@babel/core@7.28.4': + resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.0': - resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} + '@babel/generator@7.28.3': + resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} engines: {node: '>=6.9.0'} '@babel/helper-compilation-targets@7.27.2': @@ -458,8 +454,8 @@ packages: resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.27.3': - resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -480,12 +476,12 @@ packages: resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.2': - resolution: {integrity: sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==} + '@babel/helpers@7.28.4': + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.0': - resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} + '@babel/parser@7.28.4': + resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} engines: {node: '>=6.0.0'} hasBin: true @@ -580,20 +576,20 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/runtime@7.28.2': - resolution: {integrity: sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==} + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.0': - resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} + '@babel/traverse@7.28.4': + resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.2': - resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} + '@babel/types@7.28.4': + resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} engines: {node: '>=6.9.0'} '@bcoe/v8-coverage@0.2.3': @@ -603,8 +599,8 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - '@csstools/color-helpers@5.0.2': - resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==} + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} engines: {node: '>=18'} '@csstools/css-calc@2.1.4': @@ -614,8 +610,8 @@ packages: '@csstools/css-parser-algorithms': ^3.0.5 '@csstools/css-tokenizer': ^3.0.4 - '@csstools/css-color-parser@3.0.10': - resolution: {integrity: sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==} + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} engines: {node: '>=18'} peerDependencies: '@csstools/css-parser-algorithms': ^3.0.5 @@ -635,23 +631,17 @@ packages: resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} - '@emnapi/core@1.4.5': - resolution: {integrity: sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==} + '@emnapi/core@1.5.0': + resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==} - '@emnapi/runtime@1.4.5': - resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==} + '@emnapi/runtime@1.5.0': + resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==} - '@emnapi/wasi-threads@1.0.4': - resolution: {integrity: sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==} + '@emnapi/wasi-threads@1.1.0': + resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} - '@eslint-community/eslint-utils@4.7.0': - resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - - '@eslint-community/eslint-utils@4.8.0': - resolution: {integrity: sha512-MJQFqrZgcW0UNYLGOuQpey/oTN59vyWwplvCGZztn1cKz9agZPPYpJB7h2OMmuu7VLqkvEjN8feFZJmxNF9D+Q==} + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 @@ -695,11 +685,11 @@ packages: '@floating-ui/core@1.7.3': resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} - '@floating-ui/dom@1.7.3': - resolution: {integrity: sha512-uZA413QEpNuhtb3/iIKoYMSK07keHPYeXF02Zhd6e213j+d1NamLix/mCLxBUDW/Gx52sPH2m+chlUsyaBs/Ag==} + '@floating-ui/dom@1.7.4': + resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} - '@floating-ui/react-dom@2.1.5': - resolution: {integrity: sha512-HDO/1/1oH9fjj4eLgegrlH3dklZpHtUYYFiVwMUwfGvk9jWDRWqkklA2/NFScknrcNSspbV868WjXORvreDX+Q==} + '@floating-ui/react-dom@2.1.6': + resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' @@ -711,18 +701,14 @@ packages: resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} - '@humanfs/node@0.16.6': - resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - '@humanwhocodes/retry@0.3.1': - resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} - engines: {node: '>=18.18'} - '@humanwhocodes/retry@0.4.3': resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} @@ -896,10 +882,6 @@ packages: resolution: {integrity: sha512-N8t1Ytw4/mr9uN28OnVf0SYE2dGhaIxOVYcwsf9IInBKjvofAjbFRvedvBBlyTYk2knbJTiEjEJ2PyyDIBnd9w==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/expect-utils@30.0.5': - resolution: {integrity: sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/expect-utils@30.1.2': resolution: {integrity: sha512-HXy1qT/bfdjCv7iC336ExbqqYtZvljrV8odNdso7dWK9bSeHtLlvwWWC3YSybSPL03Gg5rug6WLCZAZFH72m0A==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -912,10 +894,6 @@ packages: resolution: {integrity: sha512-Beljfv9AYkr9K+ETX9tvV61rJTY706BhBUtiaepQHeEGfe0DbpvUA5Z3fomwc5Xkhns6NWrcFDZn+72fLieUnA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/get-type@30.0.1': - resolution: {integrity: sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/get-type@30.1.0': resolution: {integrity: sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -965,8 +943,8 @@ packages: resolution: {integrity: sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jridgewell/gen-mapping@0.3.12': - resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} '@jridgewell/remapping@2.3.5': resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} @@ -975,14 +953,11 @@ packages: resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - '@jridgewell/sourcemap-codec@1.5.4': - resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} - '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/trace-mapping@0.3.29': - resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} @@ -1392,8 +1367,8 @@ packages: '@rushstack/eslint-patch@1.12.0': resolution: {integrity: sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==} - '@sinclair/typebox@0.34.38': - resolution: {integrity: sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==} + '@sinclair/typebox@0.34.41': + resolution: {integrity: sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==} '@sinonjs/commons@3.0.1': resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} @@ -1421,10 +1396,6 @@ packages: resolution: {integrity: sha512-Abs5rdP1o8/OINtE49wwNeWuynCu0kme1r4RI3VXVrHr4odVDG7h7mTnw1WXXfN5Il+c25QOnrdL2y56USfxkA==} engines: {node: '>=18.0.0'} - '@smithy/credential-provider-imds@4.1.0': - resolution: {integrity: sha512-iVwNhxTsCQTPdp++4C/d9xvaDmuEWhXi55qJobMp9QMaEHRGH3kErU4F8gohtdsawRqnUy/ANylCjKuhcR2mPw==} - engines: {node: '>=18.0.0'} - '@smithy/credential-provider-imds@4.1.1': resolution: {integrity: sha512-1WdBfM9DwA59pnpIizxnUvBf/de18p4GP+6zP2AqrlFzoW3ERpZaT4QueBR0nS9deDMaQRkBlngpVlnkuuTisQ==} engines: {node: '>=18.0.0'} @@ -1509,10 +1480,6 @@ packages: resolution: {integrity: sha512-REyybygHlxo3TJICPF89N2pMQSf+p+tBJqpVe1+77Cfi9HBPReNjTgtZ1Vg73exq24vkqJskKDpfF74reXjxfw==} engines: {node: '>=18.0.0'} - '@smithy/property-provider@4.1.0': - resolution: {integrity: sha512-eksMjMHUlG5PwOUWO3k+rfLNOPVPJ70mUzyYNKb5lvyIuAwS4zpWGsxGiuT74DFWonW0xRNy+jgzGauUzX7SyA==} - engines: {node: '>=18.0.0'} - '@smithy/property-provider@4.1.1': resolution: {integrity: sha512-gm3ZS7DHxUbzC2wr8MUCsAabyiXY0gaj3ROWnhSx/9sPMc6eYLMM4rX81w1zsMaObj2Lq3PZtNCC1J6lpEY7zg==} engines: {node: '>=18.0.0'} @@ -1533,16 +1500,12 @@ packages: resolution: {integrity: sha512-Iam75b/JNXyDE41UvrlM6n8DNOa/r1ylFyvgruTUx7h2Uk7vDNV9AAwP1vfL1fOL8ls0xArwEGVcGZVd7IO/Cw==} engines: {node: '>=18.0.0'} - '@smithy/shared-ini-file-loader@4.1.0': - resolution: {integrity: sha512-W0VMlz9yGdQ/0ZAgWICFjFHTVU0YSfGoCVpKaExRM/FDkTeP/yz8OKvjtGjs6oFokCRm0srgj/g4Cg0xuHu8Rw==} - engines: {node: '>=18.0.0'} - '@smithy/shared-ini-file-loader@4.1.1': resolution: {integrity: sha512-YkpikhIqGc4sfXeIbzSj10t2bJI/sSoP5qxLue6zG+tEE3ngOBSm8sO3+djacYvS/R5DfpxN/L9CyZsvwjWOAQ==} engines: {node: '>=18.0.0'} - '@smithy/signature-v4@5.1.3': - resolution: {integrity: sha512-mARDSXSEgllNzMw6N+mC+r1AQlEBO3meEAkR/UlfAgnMzJUB3goRBWgip1EAMG99wh36MDqzo86SfIX5Y+VEaw==} + '@smithy/signature-v4@5.2.1': + resolution: {integrity: sha512-M9rZhWQLjlQVCCR37cSjHfhriGRN+FQ8UfgrYNufv66TJgk+acaggShl3KS5U/ssxivvZLlnj7QH2CUOKlxPyA==} engines: {node: '>=18.0.0'} '@smithy/smithy-client@4.6.1': @@ -1760,8 +1723,8 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - '@tybys/wasm-util@0.10.0': - resolution: {integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==} + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -1808,8 +1771,8 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/node@24.3.1': - resolution: {integrity: sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==} + '@types/node@24.3.3': + resolution: {integrity: sha512-GKBNHjoNw3Kra1Qg5UXttsY5kiWMEfoHq2TmXb+b1rcm6N7B3wTrFYIf/oSZ1xNQ+hVVijgLkiDZh7jRRsh+Gw==} '@types/pg@8.15.5': resolution: {integrity: sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ==} @@ -1840,67 +1803,63 @@ packages: '@types/yargs@17.0.33': resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} - '@typescript-eslint/eslint-plugin@8.39.0': - resolution: {integrity: sha512-bhEz6OZeUR+O/6yx9Jk6ohX6H9JSFTaiY0v9/PuKT3oGK0rn0jNplLmyFUGV+a9gfYnVNwGDwS/UkLIuXNb2Rw==} + '@typescript-eslint/eslint-plugin@8.43.0': + resolution: {integrity: sha512-8tg+gt7ENL7KewsKMKDHXR1vm8tt9eMxjJBYINf6swonlWgkYn5NwyIgXpbbDxTNU5DgpDFfj95prcTq2clIQQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.39.0 + '@typescript-eslint/parser': ^8.43.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.39.0': - resolution: {integrity: sha512-g3WpVQHngx0aLXn6kfIYCZxM6rRJlWzEkVpqEFLT3SgEDsp9cpCbxxgwnE504q4H+ruSDh/VGS6nqZIDynP+vg==} + '@typescript-eslint/parser@8.43.0': + resolution: {integrity: sha512-B7RIQiTsCBBmY+yW4+ILd6mF5h1FUwJsVvpqkrgpszYifetQ2Ke+Z4u6aZh0CblkUGIdR59iYVyXqqZGkZ3aBw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.39.0': - resolution: {integrity: sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew==} + '@typescript-eslint/project-service@8.43.0': + resolution: {integrity: sha512-htB/+D/BIGoNTQYffZw4uM4NzzuolCoaA/BusuSIcC8YjmBYQioew5VUZAYdAETPjeed0hqCaW7EHg+Robq8uw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.39.0': - resolution: {integrity: sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A==} + '@typescript-eslint/scope-manager@8.43.0': + resolution: {integrity: sha512-daSWlQ87ZhsjrbMLvpuuMAt3y4ba57AuvadcR7f3nl8eS3BjRc8L9VLxFLk92RL5xdXOg6IQ+qKjjqNEimGuAg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.39.0': - resolution: {integrity: sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ==} + '@typescript-eslint/tsconfig-utils@8.43.0': + resolution: {integrity: sha512-ALC2prjZcj2YqqL5X/bwWQmHA2em6/94GcbB/KKu5SX3EBDOsqztmmX1kMkvAJHzxk7TazKzJfFiEIagNV3qEA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.39.0': - resolution: {integrity: sha512-6B3z0c1DXVT2vYA9+z9axjtc09rqKUPRmijD5m9iv8iQpHBRYRMBcgxSiKTZKm6FwWw1/cI4v6em35OsKCiN5Q==} + '@typescript-eslint/type-utils@8.43.0': + resolution: {integrity: sha512-qaH1uLBpBuBBuRf8c1mLJ6swOfzCXryhKND04Igr4pckzSEW9JX5Aw9AgW00kwfjWJF0kk0ps9ExKTfvXfw4Qg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.39.0': - resolution: {integrity: sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg==} + '@typescript-eslint/types@8.43.0': + resolution: {integrity: sha512-vQ2FZaxJpydjSZJKiSW/LJsabFFvV7KgLC5DiLhkBcykhQj8iK9BOaDmQt74nnKdLvceM5xmhaTF+pLekrxEkw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/types@8.41.0': - resolution: {integrity: sha512-9EwxsWdVqh42afLbHP90n2VdHaWU/oWgbH2P0CfcNfdKL7CuKpwMQGjwev56vWu9cSKU7FWSu6r9zck6CVfnag==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/typescript-estree@8.39.0': - resolution: {integrity: sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw==} + '@typescript-eslint/typescript-estree@8.43.0': + resolution: {integrity: sha512-7Vv6zlAhPb+cvEpP06WXXy/ZByph9iL6BQRBDj4kmBsW98AqEeQHlj/13X+sZOrKSo9/rNKH4Ul4f6EICREFdw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.39.0': - resolution: {integrity: sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ==} + '@typescript-eslint/utils@8.43.0': + resolution: {integrity: sha512-S1/tEmkUeeswxd0GGcnwuVQPFWo8NzZTOMxCvw8BX7OMxnNae+i8Tm7REQen/SwUIPoPqfKn7EaZ+YLpiB3k9g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.39.0': - resolution: {integrity: sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==} + '@typescript-eslint/visitor-keys@8.43.0': + resolution: {integrity: sha512-T+S1KqRD4sg/bHfLwrpF/K3gQLBM1n7Rp7OjjikjTEssI2YJzQpi5WXoynOaQ93ERIuq3O8RBTOUYDKszUCEHw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ungap/structured-clone@1.3.0': @@ -2084,8 +2043,8 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} - ai@5.0.41: - resolution: {integrity: sha512-KQxXaohdTEawUGwBeCTORSNqL8otavTxqIUrOVFq1Fb+cFaTFBtxpdvFlBlsYHUNEHCLKEaBrlZso4ucsNgKuw==} + ai@5.0.44: + resolution: {integrity: sha512-l/rdoM4LcRpsRBVvZQBwSU73oNoFGlWj+PcH86QRzxDGJgZqgGItWO0QcKjBNcLDmUjGN1VYd/8J0TAXHJleRQ==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4 @@ -2101,8 +2060,8 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-regex@6.1.0: - resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} ansi-styles@4.3.0: @@ -2113,8 +2072,8 @@ packages: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} anymatch@3.1.3: @@ -2201,8 +2160,8 @@ packages: peerDependencies: '@babel/core': ^7.11.0 - babel-plugin-istanbul@7.0.0: - resolution: {integrity: sha512-C5OzENSx/A+gt7t4VH1I2XsflxyPUmXRFPKBxt33xncdOmq7oROVM3bZv9Ysjjkv8OJYDMa+tKuKMvqU/H3xdw==} + babel-plugin-istanbul@7.0.1: + resolution: {integrity: sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==} engines: {node: '>=12'} babel-plugin-jest-hoist@30.0.1: @@ -2223,8 +2182,12 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - bowser@2.11.0: - resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} + baseline-browser-mapping@2.8.3: + resolution: {integrity: sha512-mcE+Wr2CAhHNWxXN/DdTI+n4gsPc5QpXpWnyCQWiQYIYZX+ZMJ8juXZgjRa/0/YPJo/NSsgW15/YgmI4nbysYw==} + hasBin: true + + bowser@2.12.1: + resolution: {integrity: sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==} brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -2236,8 +2199,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.25.1: - resolution: {integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==} + browserslist@4.26.0: + resolution: {integrity: sha512-P9go2WrP9FiPwLv3zqRD/Uoxo0RSHjzFCiQz7d4vbmwNqQFo9T9WCeP/Qn5EbcKQY6DBbkxEXNcpJOmncNrb7A==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -2279,15 +2242,15 @@ packages: resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} engines: {node: '>=16'} - caniuse-lite@1.0.30001731: - resolution: {integrity: sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==} + caniuse-lite@1.0.30001741: + resolution: {integrity: sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chalk@5.6.0: - resolution: {integrity: sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==} + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} char-regex@1.0.2: @@ -2415,8 +2378,8 @@ packages: supports-color: optional: true - debug@4.4.1: - resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -2427,8 +2390,8 @@ packages: decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} - dedent@1.6.0: - resolution: {integrity: sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==} + dedent@1.7.0: + resolution: {integrity: sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==} peerDependencies: babel-plugin-macros: ^3.1.0 peerDependenciesMeta: @@ -2505,8 +2468,8 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.5.195: - resolution: {integrity: sha512-URclP0iIaDUzqcAyV1v2PgduJ9N0IdXmWsnPzPfelvBmjmZzEy6xJcjb1cXj+TbYqXgtLrjHEoaSIdTYhw4ezg==} + electron-to-chromium@1.5.218: + resolution: {integrity: sha512-uwwdN0TUHs8u6iRgN8vKeWZMRll4gBkz+QMqdS7DDe49uiK68/UX92lFb61oiFPrpYZNeZIqa4bA7O6Aiasnzg==} emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} @@ -2698,9 +2661,9 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - eventsource-parser@3.0.5: - resolution: {integrity: sha512-bSRG85ZrMdmWtm7qkF9He9TNRzc/Bm99gEJMaQoHJ9E6Kv9QBbsldh2oMj7iXmYNEAVvNgvv5vPorG6W+XtBhQ==} - engines: {node: '>=20.0.0'} + eventsource-parser@3.0.6: + resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} + engines: {node: '>=18.0.0'} execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} @@ -2713,10 +2676,6 @@ packages: resolution: {integrity: sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==} engines: {node: '>= 0.8.0'} - expect@30.0.5: - resolution: {integrity: sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - expect@30.1.2: resolution: {integrity: sha512-xvHszRavo28ejws8FpemjhwswGj4w/BetHIL8cU49u4sGyXDw2+p3YbeDbj6xzlxi6kWTjIRSTJ+9sNXPnF0Zg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -2755,8 +2714,9 @@ packages: fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} - fdir@6.4.6: - resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: @@ -2995,8 +2955,8 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-arrayish@0.3.2: - resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + is-arrayish@0.3.4: + resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} is-async-function@2.1.1: resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} @@ -3145,8 +3105,8 @@ packages: resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} engines: {node: '>=10'} - istanbul-reports@3.1.7: - resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} engines: {node: '>=8'} iterator.prototype@1.1.5: @@ -3189,10 +3149,6 @@ packages: ts-node: optional: true - jest-diff@30.0.5: - resolution: {integrity: sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-diff@30.1.2: resolution: {integrity: sha512-4+prq+9J61mOVXCa4Qp8ZjavdxzrWQXrI80GNxP8f4tkI2syPuPrJgdRPZRrfUTRvIoUwcmNLbqEJy9W800+NQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -3226,18 +3182,10 @@ packages: resolution: {integrity: sha512-AoFvJzwxK+4KohH60vRuHaqXfWmeBATFZpzpmzNmYTtmRMiyGPVhkXpBqxUQunw+dQB48bDf4NpUs6ivVbRv1g==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-matcher-utils@30.0.5: - resolution: {integrity: sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-matcher-utils@30.1.2: resolution: {integrity: sha512-7ai16hy4rSbDjvPTuUhuV8nyPBd6EX34HkBsBcBX2lENCuAQ0qKCPb/+lt8OSWUa9WWmGYLy41PrEzkwRwoGZQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-message-util@30.0.5: - resolution: {integrity: sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA==} - engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-message-util@30.1.0: resolution: {integrity: sha512-HizKDGG98cYkWmaLUHChq4iN+oCENohQLb7Z5guBPumYs+/etonmNFlg1Ps6yN9LTPyZn+M+b/9BbnHx3WTMDg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -3309,8 +3257,8 @@ packages: resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} hasBin: true - jose@6.0.12: - resolution: {integrity: sha512-T8xypXs8CpmiIi78k0E+Lk7T2zlK4zDyg+o1CZ4AkOHgDg98ogdP2BeZ61lTFKFyoEwJ9RgAgN+SdM3iPgNonQ==} + jose@6.1.0: + resolution: {integrity: sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==} js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -3480,8 +3428,8 @@ packages: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true - magic-string@0.30.18: - resolution: {integrity: sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==} + magic-string@0.30.19: + resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} @@ -3566,8 +3514,8 @@ packages: engines: {node: ^18 || >=20} hasBin: true - napi-postinstall@0.3.2: - resolution: {integrity: sha512-tWVJxJHmBWLy69PvO96TZMZDrzmw5KeiZBz3RHmiM2XZ9grBJ2WgMAFVVg25nqp3ZjTFUs2Ftw1JhscL3Teliw==} + napi-postinstall@0.3.3: + resolution: {integrity: sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} hasBin: true @@ -3629,8 +3577,8 @@ packages: node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - node-releases@2.0.19: - resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + node-releases@2.0.21: + resolution: {integrity: sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==} normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} @@ -3640,11 +3588,11 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} - nwsapi@2.2.21: - resolution: {integrity: sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA==} + nwsapi@2.2.22: + resolution: {integrity: sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==} - oauth4webapi@3.6.1: - resolution: {integrity: sha512-b39+drVyA4aNUptFOhkkmGWnG/BE7dT29SW/8PVYElqp7j/DBqzm5SS1G+MUD07XlTcBOAG+6Cb/35Cx2kHIuQ==} + oauth4webapi@3.8.1: + resolution: {integrity: sha512-olkZDELNycOWQf9LrsELFq8n05LwJgV8UkrS0cburk6FOwf8GvLam+YB+Uj5Qvryee+vwWOfQVeI5Vm0MVg7SA==} object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} @@ -4062,8 +4010,8 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - simple-swizzle@0.2.2: - resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + simple-swizzle@0.2.4: + resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} sirv@2.0.4: resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} @@ -4147,8 +4095,8 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} strip-bom@3.0.0: @@ -4214,8 +4162,8 @@ packages: tailwindcss@4.1.13: resolution: {integrity: sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==} - tapable@2.2.2: - resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} + tapable@2.2.3: + resolution: {integrity: sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==} engines: {node: '>=6'} tar@7.4.3: @@ -4230,8 +4178,8 @@ packages: resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} engines: {node: '>=18'} - tinyglobby@0.2.14: - resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} tldts-core@6.1.86: @@ -4536,57 +4484,52 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.1.8: + resolution: {integrity: sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==} snapshots: - '@adobe/css-tools@4.4.3': {} + '@adobe/css-tools@4.4.4': {} - '@ai-sdk/gateway@1.0.21(zod@3.25.76)': + '@ai-sdk/gateway@1.0.23(zod@4.1.8)': dependencies: '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.8(zod@3.25.76) - zod: 3.25.76 + '@ai-sdk/provider-utils': 3.0.9(zod@4.1.8) + zod: 4.1.8 - '@ai-sdk/openai@2.0.28(zod@3.25.76)': + '@ai-sdk/openai@2.0.30(zod@4.1.8)': dependencies: '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.8(zod@3.25.76) - zod: 3.25.76 + '@ai-sdk/provider-utils': 3.0.9(zod@4.1.8) + zod: 4.1.8 - '@ai-sdk/provider-utils@3.0.8(zod@3.25.76)': + '@ai-sdk/provider-utils@3.0.9(zod@4.1.8)': dependencies: '@ai-sdk/provider': 2.0.0 '@standard-schema/spec': 1.0.0 - eventsource-parser: 3.0.5 - zod: 3.25.76 + eventsource-parser: 3.0.6 + zod: 4.1.8 '@ai-sdk/provider@2.0.0': dependencies: json-schema: 0.4.0 - '@ai-sdk/rsc@1.0.41(react@19.1.1)(zod@3.25.76)': + '@ai-sdk/rsc@1.0.44(react@19.1.1)(zod@4.1.8)': dependencies: '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.8(zod@3.25.76) - ai: 5.0.41(zod@3.25.76) + '@ai-sdk/provider-utils': 3.0.9(zod@4.1.8) + ai: 5.0.44(zod@4.1.8) jsondiffpatch: 0.6.0 react: 19.1.1 optionalDependencies: - zod: 3.25.76 + zod: 4.1.8 '@alloc/quick-lru@5.2.0': {} - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.12 - '@jridgewell/trace-mapping': 0.3.29 - '@asamuzakjp/css-color@3.2.0': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-color-parser': 3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 lru-cache: 10.4.3 @@ -4594,8 +4537,8 @@ snapshots: '@auth/core@0.40.0': dependencies: '@panva/hkdf': 1.2.1 - jose: 6.0.12 - oauth4webapi: 3.6.1 + jose: 6.1.0 + oauth4webapi: 3.8.1 preact: 10.24.3 preact-render-to-string: 6.5.11(preact@10.24.3) @@ -4616,7 +4559,7 @@ snapshots: '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 '@aws-sdk/types': 3.887.0 - '@aws-sdk/util-locate-window': 3.804.0 + '@aws-sdk/util-locate-window': 3.873.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 @@ -4626,7 +4569,7 @@ snapshots: '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 '@aws-sdk/types': 3.887.0 - '@aws-sdk/util-locate-window': 3.804.0 + '@aws-sdk/util-locate-window': 3.873.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 @@ -4758,9 +4701,9 @@ snapshots: '@aws-sdk/xml-builder': 3.887.0 '@smithy/core': 3.11.0 '@smithy/node-config-provider': 4.2.1 - '@smithy/property-provider': 4.1.0 + '@smithy/property-provider': 4.1.1 '@smithy/protocol-http': 5.2.1 - '@smithy/signature-v4': 5.1.3 + '@smithy/signature-v4': 5.2.1 '@smithy/smithy-client': 4.6.1 '@smithy/types': 4.5.0 '@smithy/util-base64': 4.1.0 @@ -4774,7 +4717,7 @@ snapshots: dependencies: '@aws-sdk/core': 3.887.0 '@aws-sdk/types': 3.887.0 - '@smithy/property-provider': 4.1.0 + '@smithy/property-provider': 4.1.1 '@smithy/types': 4.5.0 tslib: 2.8.1 @@ -4784,7 +4727,7 @@ snapshots: '@aws-sdk/types': 3.887.0 '@smithy/fetch-http-handler': 5.2.1 '@smithy/node-http-handler': 4.2.1 - '@smithy/property-provider': 4.1.0 + '@smithy/property-provider': 4.1.1 '@smithy/protocol-http': 5.2.1 '@smithy/smithy-client': 4.6.1 '@smithy/types': 4.5.0 @@ -4801,9 +4744,9 @@ snapshots: '@aws-sdk/credential-provider-web-identity': 3.887.0 '@aws-sdk/nested-clients': 3.887.0 '@aws-sdk/types': 3.887.0 - '@smithy/credential-provider-imds': 4.1.0 - '@smithy/property-provider': 4.1.0 - '@smithy/shared-ini-file-loader': 4.1.0 + '@smithy/credential-provider-imds': 4.1.1 + '@smithy/property-provider': 4.1.1 + '@smithy/shared-ini-file-loader': 4.1.1 '@smithy/types': 4.5.0 tslib: 2.8.1 transitivePeerDependencies: @@ -4818,9 +4761,9 @@ snapshots: '@aws-sdk/credential-provider-sso': 3.887.0 '@aws-sdk/credential-provider-web-identity': 3.887.0 '@aws-sdk/types': 3.887.0 - '@smithy/credential-provider-imds': 4.1.0 - '@smithy/property-provider': 4.1.0 - '@smithy/shared-ini-file-loader': 4.1.0 + '@smithy/credential-provider-imds': 4.1.1 + '@smithy/property-provider': 4.1.1 + '@smithy/shared-ini-file-loader': 4.1.1 '@smithy/types': 4.5.0 tslib: 2.8.1 transitivePeerDependencies: @@ -4830,8 +4773,8 @@ snapshots: dependencies: '@aws-sdk/core': 3.887.0 '@aws-sdk/types': 3.887.0 - '@smithy/property-provider': 4.1.0 - '@smithy/shared-ini-file-loader': 4.1.0 + '@smithy/property-provider': 4.1.1 + '@smithy/shared-ini-file-loader': 4.1.1 '@smithy/types': 4.5.0 tslib: 2.8.1 @@ -4841,8 +4784,8 @@ snapshots: '@aws-sdk/core': 3.887.0 '@aws-sdk/token-providers': 3.887.0 '@aws-sdk/types': 3.887.0 - '@smithy/property-provider': 4.1.0 - '@smithy/shared-ini-file-loader': 4.1.0 + '@smithy/property-provider': 4.1.1 + '@smithy/shared-ini-file-loader': 4.1.1 '@smithy/types': 4.5.0 tslib: 2.8.1 transitivePeerDependencies: @@ -4853,7 +4796,7 @@ snapshots: '@aws-sdk/core': 3.887.0 '@aws-sdk/nested-clients': 3.887.0 '@aws-sdk/types': 3.887.0 - '@smithy/property-provider': 4.1.0 + '@smithy/property-provider': 4.1.1 '@smithy/types': 4.5.0 tslib: 2.8.1 transitivePeerDependencies: @@ -4927,7 +4870,7 @@ snapshots: '@smithy/core': 3.11.0 '@smithy/node-config-provider': 4.2.1 '@smithy/protocol-http': 5.2.1 - '@smithy/signature-v4': 5.1.3 + '@smithy/signature-v4': 5.2.1 '@smithy/smithy-client': 4.6.1 '@smithy/types': 4.5.0 '@smithy/util-config-provider': 4.1.0 @@ -5020,7 +4963,7 @@ snapshots: '@aws-sdk/middleware-sdk-s3': 3.887.0 '@aws-sdk/types': 3.887.0 '@smithy/protocol-http': 5.2.1 - '@smithy/signature-v4': 5.1.3 + '@smithy/signature-v4': 5.2.1 '@smithy/types': 4.5.0 tslib: 2.8.1 @@ -5029,8 +4972,8 @@ snapshots: '@aws-sdk/core': 3.887.0 '@aws-sdk/nested-clients': 3.887.0 '@aws-sdk/types': 3.887.0 - '@smithy/property-provider': 4.1.0 - '@smithy/shared-ini-file-loader': 4.1.0 + '@smithy/property-provider': 4.1.1 + '@smithy/shared-ini-file-loader': 4.1.1 '@smithy/types': 4.5.0 tslib: 2.8.1 transitivePeerDependencies: @@ -5060,7 +5003,7 @@ snapshots: '@smithy/types': 4.5.0 tslib: 2.8.1 - '@aws-sdk/util-locate-window@3.804.0': + '@aws-sdk/util-locate-window@3.873.0': dependencies: tslib: 2.8.1 @@ -5068,7 +5011,7 @@ snapshots: dependencies: '@aws-sdk/types': 3.887.0 '@smithy/types': 4.5.0 - bowser: 2.11.0 + bowser: 2.12.1 tslib: 2.8.1 '@aws-sdk/util-user-agent-node@3.887.0': @@ -5092,41 +5035,41 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.0': {} + '@babel/compat-data@7.28.4': {} - '@babel/core@7.28.0': + '@babel/core@7.28.4': dependencies: - '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.0 + '@babel/generator': 7.28.3 '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0) - '@babel/helpers': 7.28.2 - '@babel/parser': 7.28.0 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.4 '@babel/template': 7.27.2 - '@babel/traverse': 7.28.0 - '@babel/types': 7.28.2 + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 + '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.1 + debug: 4.4.3 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/generator@7.28.0': + '@babel/generator@7.28.3': dependencies: - '@babel/parser': 7.28.0 - '@babel/types': 7.28.2 - '@jridgewell/gen-mapping': 0.3.12 - '@jridgewell/trace-mapping': 0.3.29 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 '@babel/helper-compilation-targets@7.27.2': dependencies: - '@babel/compat-data': 7.28.0 + '@babel/compat-data': 7.28.4 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.25.1 + browserslist: 4.26.0 lru-cache: 5.1.1 semver: 6.3.1 @@ -5134,17 +5077,17 @@ snapshots: '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.28.0 - '@babel/types': 7.28.2 + '@babel/traverse': 7.28.4 + '@babel/types': 7.28.4 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)': + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.4 '@babel/helper-module-imports': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.0 + '@babel/traverse': 7.28.4 transitivePeerDependencies: - supports-color @@ -5156,121 +5099,121 @@ snapshots: '@babel/helper-validator-option@7.27.1': {} - '@babel/helpers@7.28.2': + '@babel/helpers@7.28.4': dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.2 + '@babel/types': 7.28.4 - '@babel/parser@7.28.0': + '@babel/parser@7.28.4': dependencies: - '@babel/types': 7.28.2 + '@babel/types': 7.28.4 - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.0)': + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.0)': + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.0)': + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.0)': + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.0)': + '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.0)': + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.0)': + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.0)': + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.0)': + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.0)': + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.0)': + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.0)': + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.0)': + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.0)': + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.0)': + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.0)': + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.0)': + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.4)': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.4 '@babel/helper-plugin-utils': 7.27.1 - '@babel/runtime@7.28.2': {} + '@babel/runtime@7.28.4': {} '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.0 - '@babel/types': 7.28.2 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 - '@babel/traverse@7.28.0': + '@babel/traverse@7.28.4': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.0 + '@babel/generator': 7.28.3 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.0 + '@babel/parser': 7.28.4 '@babel/template': 7.27.2 - '@babel/types': 7.28.2 - debug: 4.4.1 + '@babel/types': 7.28.4 + debug: 4.4.3 transitivePeerDependencies: - supports-color - '@babel/types@7.28.2': + '@babel/types@7.28.4': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 @@ -5281,16 +5224,16 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@csstools/color-helpers@5.0.2': {} + '@csstools/color-helpers@5.1.0': {} '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 - '@csstools/css-color-parser@3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': dependencies: - '@csstools/color-helpers': 5.0.2 + '@csstools/color-helpers': 5.1.0 '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-tokenizer': 3.0.4 @@ -5303,28 +5246,23 @@ snapshots: '@discoveryjs/json-ext@0.5.7': {} - '@emnapi/core@1.4.5': + '@emnapi/core@1.5.0': dependencies: - '@emnapi/wasi-threads': 1.0.4 + '@emnapi/wasi-threads': 1.1.0 tslib: 2.8.1 optional: true - '@emnapi/runtime@1.4.5': + '@emnapi/runtime@1.5.0': dependencies: tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.0.4': + '@emnapi/wasi-threads@1.1.0': dependencies: tslib: 2.8.1 optional: true - '@eslint-community/eslint-utils@4.7.0(eslint@9.35.0(jiti@2.5.1))': - dependencies: - eslint: 9.35.0(jiti@2.5.1) - eslint-visitor-keys: 3.4.3 - - '@eslint-community/eslint-utils@4.8.0(eslint@9.35.0(jiti@2.5.1))': + '@eslint-community/eslint-utils@4.9.0(eslint@9.35.0(jiti@2.5.1))': dependencies: eslint: 9.35.0(jiti@2.5.1) eslint-visitor-keys: 3.4.3 @@ -5334,7 +5272,7 @@ snapshots: '@eslint/config-array@0.21.0': dependencies: '@eslint/object-schema': 2.1.6 - debug: 4.4.1 + debug: 4.4.3 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -5348,7 +5286,7 @@ snapshots: '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 - debug: 4.4.1 + debug: 4.4.3 espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 @@ -5374,14 +5312,14 @@ snapshots: dependencies: '@floating-ui/utils': 0.2.10 - '@floating-ui/dom@1.7.3': + '@floating-ui/dom@1.7.4': dependencies: '@floating-ui/core': 1.7.3 '@floating-ui/utils': 0.2.10 - '@floating-ui/react-dom@2.1.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@floating-ui/react-dom@2.1.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@floating-ui/dom': 1.7.3 + '@floating-ui/dom': 1.7.4 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) @@ -5389,15 +5327,13 @@ snapshots: '@humanfs/core@0.19.1': {} - '@humanfs/node@0.16.6': + '@humanfs/node@0.16.7': dependencies: '@humanfs/core': 0.19.1 - '@humanwhocodes/retry': 0.3.1 + '@humanwhocodes/retry': 0.4.3 '@humanwhocodes/module-importer@1.0.1': {} - '@humanwhocodes/retry@0.3.1': {} - '@humanwhocodes/retry@0.4.3': {} '@img/sharp-darwin-arm64@0.34.3': @@ -5474,7 +5410,7 @@ snapshots: '@img/sharp-wasm32@0.34.3': dependencies: - '@emnapi/runtime': 1.4.5 + '@emnapi/runtime': 1.5.0 optional: true '@img/sharp-win32-arm64@0.34.3': @@ -5490,7 +5426,7 @@ snapshots: dependencies: string-width: 5.1.2 string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 + strip-ansi: 7.1.2 strip-ansi-cjs: strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 @@ -5512,13 +5448,13 @@ snapshots: '@jest/console@30.1.2': dependencies: '@jest/types': 30.0.5 - '@types/node': 24.3.1 + '@types/node': 24.3.3 chalk: 4.1.2 jest-message-util: 30.1.0 jest-util: 30.0.5 slash: 3.0.0 - '@jest/core@30.1.3(ts-node@10.9.2(@types/node@24.3.1)(typescript@5.9.2))': + '@jest/core@30.1.3(ts-node@10.9.2(@types/node@24.3.3)(typescript@5.9.2))': dependencies: '@jest/console': 30.1.2 '@jest/pattern': 30.0.1 @@ -5526,14 +5462,14 @@ snapshots: '@jest/test-result': 30.1.3 '@jest/transform': 30.1.2 '@jest/types': 30.0.5 - '@types/node': 24.3.1 + '@types/node': 24.3.3 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 4.3.0 exit-x: 0.2.2 graceful-fs: 4.2.11 jest-changed-files: 30.0.5 - jest-config: 30.1.3(@types/node@24.3.1)(ts-node@10.9.2(@types/node@24.3.1)(typescript@5.9.2)) + jest-config: 30.1.3(@types/node@24.3.3)(ts-node@10.9.2(@types/node@24.3.3)(typescript@5.9.2)) jest-haste-map: 30.1.0 jest-message-util: 30.1.0 jest-regex-util: 30.0.1 @@ -5562,7 +5498,7 @@ snapshots: '@jest/fake-timers': 30.1.2 '@jest/types': 30.0.5 '@types/jsdom': 21.1.7 - '@types/node': 24.3.1 + '@types/node': 24.3.3 jest-mock: 30.0.5 jest-util: 30.0.5 jsdom: 26.1.0 @@ -5571,13 +5507,9 @@ snapshots: dependencies: '@jest/fake-timers': 30.1.2 '@jest/types': 30.0.5 - '@types/node': 24.3.1 + '@types/node': 24.3.3 jest-mock: 30.0.5 - '@jest/expect-utils@30.0.5': - dependencies: - '@jest/get-type': 30.0.1 - '@jest/expect-utils@30.1.2': dependencies: '@jest/get-type': 30.1.0 @@ -5593,13 +5525,11 @@ snapshots: dependencies: '@jest/types': 30.0.5 '@sinonjs/fake-timers': 13.0.5 - '@types/node': 24.3.1 + '@types/node': 24.3.3 jest-message-util: 30.1.0 jest-mock: 30.0.5 jest-util: 30.0.5 - '@jest/get-type@30.0.1': {} - '@jest/get-type@30.1.0': {} '@jest/globals@30.1.2': @@ -5613,7 +5543,7 @@ snapshots: '@jest/pattern@30.0.1': dependencies: - '@types/node': 24.3.1 + '@types/node': 24.3.3 jest-regex-util: 30.0.1 '@jest/reporters@30.1.3': @@ -5623,8 +5553,8 @@ snapshots: '@jest/test-result': 30.1.3 '@jest/transform': 30.1.2 '@jest/types': 30.0.5 - '@jridgewell/trace-mapping': 0.3.29 - '@types/node': 24.3.1 + '@jridgewell/trace-mapping': 0.3.31 + '@types/node': 24.3.3 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit-x: 0.2.2 @@ -5634,7 +5564,7 @@ snapshots: istanbul-lib-instrument: 6.0.3 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 - istanbul-reports: 3.1.7 + istanbul-reports: 3.2.0 jest-message-util: 30.1.0 jest-util: 30.0.5 jest-worker: 30.1.0 @@ -5646,7 +5576,7 @@ snapshots: '@jest/schemas@30.0.5': dependencies: - '@sinclair/typebox': 0.34.38 + '@sinclair/typebox': 0.34.41 '@jest/snapshot-utils@30.1.2': dependencies: @@ -5657,7 +5587,7 @@ snapshots: '@jest/source-map@30.0.1': dependencies: - '@jridgewell/trace-mapping': 0.3.29 + '@jridgewell/trace-mapping': 0.3.31 callsites: 3.1.0 graceful-fs: 4.2.11 @@ -5677,10 +5607,10 @@ snapshots: '@jest/transform@30.1.2': dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.4 '@jest/types': 30.0.5 - '@jridgewell/trace-mapping': 0.3.29 - babel-plugin-istanbul: 7.0.0 + '@jridgewell/trace-mapping': 0.3.31 + babel-plugin-istanbul: 7.0.1 chalk: 4.1.2 convert-source-map: 2.0.0 fast-json-stable-stringify: 2.1.0 @@ -5701,41 +5631,39 @@ snapshots: '@jest/schemas': 30.0.5 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 24.3.1 + '@types/node': 24.3.3 '@types/yargs': 17.0.33 chalk: 4.1.2 - '@jridgewell/gen-mapping@0.3.12': + '@jridgewell/gen-mapping@0.3.13': dependencies: - '@jridgewell/sourcemap-codec': 1.5.4 - '@jridgewell/trace-mapping': 0.3.29 + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/remapping@2.3.5': dependencies: - '@jridgewell/gen-mapping': 0.3.12 - '@jridgewell/trace-mapping': 0.3.29 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/sourcemap-codec@1.5.4': {} - '@jridgewell/sourcemap-codec@1.5.5': {} - '@jridgewell/trace-mapping@0.3.29': + '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.4 + '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping@0.3.9': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.4 + '@jridgewell/sourcemap-codec': 1.5.5 '@napi-rs/wasm-runtime@0.2.12': dependencies: - '@emnapi/core': 1.4.5 - '@emnapi/runtime': 1.4.5 - '@tybys/wasm-util': 0.10.0 + '@emnapi/core': 1.5.0 + '@emnapi/runtime': 1.5.0 + '@tybys/wasm-util': 0.10.1 optional: true '@next/bundle-analyzer@15.5.3': @@ -5943,7 +5871,7 @@ snapshots: '@radix-ui/react-popper@1.2.8(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@floating-ui/react-dom': 2.1.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@floating-ui/react-dom': 2.1.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1) '@radix-ui/react-context': 1.1.2(@types/react@19.1.12)(react@19.1.1) @@ -6095,7 +6023,7 @@ snapshots: '@rushstack/eslint-patch@1.12.0': {} - '@sinclair/typebox@0.34.38': {} + '@sinclair/typebox@0.34.41': {} '@sinonjs/commons@3.0.1': dependencies: @@ -6141,14 +6069,6 @@ snapshots: tslib: 2.8.1 uuid: 9.0.1 - '@smithy/credential-provider-imds@4.1.0': - dependencies: - '@smithy/node-config-provider': 4.2.1 - '@smithy/property-provider': 4.1.0 - '@smithy/types': 4.5.0 - '@smithy/url-parser': 4.1.1 - tslib: 2.8.1 - '@smithy/credential-provider-imds@4.1.1': dependencies: '@smithy/node-config-provider': 4.2.1 @@ -6290,11 +6210,6 @@ snapshots: '@smithy/types': 4.5.0 tslib: 2.8.1 - '@smithy/property-provider@4.1.0': - dependencies: - '@smithy/types': 4.5.0 - tslib: 2.8.1 - '@smithy/property-provider@4.1.1': dependencies: '@smithy/types': 4.5.0 @@ -6320,17 +6235,12 @@ snapshots: dependencies: '@smithy/types': 4.5.0 - '@smithy/shared-ini-file-loader@4.1.0': - dependencies: - '@smithy/types': 4.5.0 - tslib: 2.8.1 - '@smithy/shared-ini-file-loader@4.1.1': dependencies: '@smithy/types': 4.5.0 tslib: 2.8.1 - '@smithy/signature-v4@5.1.3': + '@smithy/signature-v4@5.2.1': dependencies: '@smithy/is-array-buffer': 4.1.0 '@smithy/protocol-http': 5.2.1 @@ -6394,7 +6304,7 @@ snapshots: '@smithy/property-provider': 4.1.1 '@smithy/smithy-client': 4.6.1 '@smithy/types': 4.5.0 - bowser: 2.11.0 + bowser: 2.12.1 tslib: 2.8.1 '@smithy/util-defaults-mode-node@4.1.1': @@ -6463,8 +6373,8 @@ snapshots: '@stylistic/eslint-plugin@5.3.1(eslint@9.35.0(jiti@2.5.1))': dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.35.0(jiti@2.5.1)) - '@typescript-eslint/types': 8.41.0 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.35.0(jiti@2.5.1)) + '@typescript-eslint/types': 8.43.0 eslint: 9.35.0(jiti@2.5.1) eslint-visitor-keys: 4.2.1 espree: 10.4.0 @@ -6481,7 +6391,7 @@ snapshots: enhanced-resolve: 5.18.3 jiti: 2.5.1 lightningcss: 1.30.1 - magic-string: 0.30.18 + magic-string: 0.30.19 source-map-js: 1.2.1 tailwindcss: 4.1.13 @@ -6550,7 +6460,7 @@ snapshots: '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.27.1 - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 '@types/aria-query': 5.0.4 aria-query: 5.3.0 dom-accessibility-api: 0.5.16 @@ -6560,7 +6470,7 @@ snapshots: '@testing-library/jest-dom@6.8.0': dependencies: - '@adobe/css-tools': 4.4.3 + '@adobe/css-tools': 4.4.4 aria-query: 5.3.2 css.escape: 1.5.1 dom-accessibility-api: 0.6.3 @@ -6569,7 +6479,7 @@ snapshots: '@testing-library/react@16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 '@testing-library/dom': 10.4.1 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) @@ -6585,7 +6495,7 @@ snapshots: '@tsconfig/node16@1.0.4': {} - '@tybys/wasm-util@0.10.0': + '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 optional: true @@ -6594,24 +6504,24 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.28.0 - '@babel/types': 7.28.2 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.28.0 '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.28.2 + '@babel/types': 7.28.4 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.28.0 - '@babel/types': 7.28.2 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.28.2 + '@babel/types': 7.28.4 '@types/culori@4.0.1': {} @@ -6631,12 +6541,12 @@ snapshots: '@types/jest@30.0.0': dependencies: - expect: 30.0.5 + expect: 30.1.2 pretty-format: 30.0.5 '@types/jsdom@21.1.7': dependencies: - '@types/node': 24.3.1 + '@types/node': 24.3.3 '@types/tough-cookie': 4.0.5 parse5: 7.3.0 @@ -6644,13 +6554,13 @@ snapshots: '@types/json5@0.0.29': {} - '@types/node@24.3.1': + '@types/node@24.3.3': dependencies: undici-types: 7.10.0 '@types/pg@8.15.5': dependencies: - '@types/node': 24.3.1 + '@types/node': 24.3.3 pg-protocol: 1.10.3 pg-types: 2.2.0 @@ -6678,14 +6588,14 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.39.0(@typescript-eslint/parser@8.39.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/eslint-plugin@8.43.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.39.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/scope-manager': 8.39.0 - '@typescript-eslint/type-utils': 8.39.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/utils': 8.39.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.39.0 + '@typescript-eslint/parser': 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/scope-manager': 8.43.0 + '@typescript-eslint/type-utils': 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/utils': 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.43.0 eslint: 9.35.0(jiti@2.5.1) graphemer: 1.4.0 ignore: 7.0.5 @@ -6695,59 +6605,57 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.39.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)': dependencies: - '@typescript-eslint/scope-manager': 8.39.0 - '@typescript-eslint/types': 8.39.0 - '@typescript-eslint/typescript-estree': 8.39.0(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.39.0 - debug: 4.4.1 + '@typescript-eslint/scope-manager': 8.43.0 + '@typescript-eslint/types': 8.43.0 + '@typescript-eslint/typescript-estree': 8.43.0(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.43.0 + debug: 4.4.3 eslint: 9.35.0(jiti@2.5.1) typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.39.0(typescript@5.9.2)': + '@typescript-eslint/project-service@8.43.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.39.0(typescript@5.9.2) - '@typescript-eslint/types': 8.41.0 - debug: 4.4.1 + '@typescript-eslint/tsconfig-utils': 8.43.0(typescript@5.9.2) + '@typescript-eslint/types': 8.43.0 + debug: 4.4.3 typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.39.0': + '@typescript-eslint/scope-manager@8.43.0': dependencies: - '@typescript-eslint/types': 8.39.0 - '@typescript-eslint/visitor-keys': 8.39.0 + '@typescript-eslint/types': 8.43.0 + '@typescript-eslint/visitor-keys': 8.43.0 - '@typescript-eslint/tsconfig-utils@8.39.0(typescript@5.9.2)': + '@typescript-eslint/tsconfig-utils@8.43.0(typescript@5.9.2)': dependencies: typescript: 5.9.2 - '@typescript-eslint/type-utils@8.39.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/type-utils@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)': dependencies: - '@typescript-eslint/types': 8.39.0 - '@typescript-eslint/typescript-estree': 8.39.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.39.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) - debug: 4.4.1 + '@typescript-eslint/types': 8.43.0 + '@typescript-eslint/typescript-estree': 8.43.0(typescript@5.9.2) + '@typescript-eslint/utils': 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) + debug: 4.4.3 eslint: 9.35.0(jiti@2.5.1) ts-api-utils: 2.1.0(typescript@5.9.2) typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.39.0': {} + '@typescript-eslint/types@8.43.0': {} - '@typescript-eslint/types@8.41.0': {} - - '@typescript-eslint/typescript-estree@8.39.0(typescript@5.9.2)': + '@typescript-eslint/typescript-estree@8.43.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/project-service': 8.39.0(typescript@5.9.2) - '@typescript-eslint/tsconfig-utils': 8.39.0(typescript@5.9.2) - '@typescript-eslint/types': 8.39.0 - '@typescript-eslint/visitor-keys': 8.39.0 - debug: 4.4.1 + '@typescript-eslint/project-service': 8.43.0(typescript@5.9.2) + '@typescript-eslint/tsconfig-utils': 8.43.0(typescript@5.9.2) + '@typescript-eslint/types': 8.43.0 + '@typescript-eslint/visitor-keys': 8.43.0 + debug: 4.4.3 fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 @@ -6757,20 +6665,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.39.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/utils@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2)': dependencies: - '@eslint-community/eslint-utils': 4.8.0(eslint@9.35.0(jiti@2.5.1)) - '@typescript-eslint/scope-manager': 8.39.0 - '@typescript-eslint/types': 8.39.0 - '@typescript-eslint/typescript-estree': 8.39.0(typescript@5.9.2) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.35.0(jiti@2.5.1)) + '@typescript-eslint/scope-manager': 8.43.0 + '@typescript-eslint/types': 8.43.0 + '@typescript-eslint/typescript-estree': 8.43.0(typescript@5.9.2) eslint: 9.35.0(jiti@2.5.1) typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.39.0': + '@typescript-eslint/visitor-keys@8.43.0': dependencies: - '@typescript-eslint/types': 8.39.0 + '@typescript-eslint/types': 8.43.0 eslint-visitor-keys: 4.2.1 '@ungap/structured-clone@1.3.0': {} @@ -6847,9 +6755,9 @@ snapshots: dependencies: uncrypto: 0.1.3 - '@vercel/analytics@1.5.0(next@15.5.3(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)': + '@vercel/analytics@1.5.0(next@15.5.3(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)': optionalDependencies: - next: 15.5.3(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + next: 15.5.3(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: 19.1.1 '@vercel/blob@1.1.1': @@ -6860,9 +6768,9 @@ snapshots: throttleit: 2.1.0 undici: 5.29.0 - '@vercel/speed-insights@1.2.0(next@15.5.3(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)': + '@vercel/speed-insights@1.2.0(next@15.5.3(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)': optionalDependencies: - next: 15.5.3(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + next: 15.5.3(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: 19.1.1 acorn-jsx@5.3.2(acorn@8.15.0): @@ -6877,13 +6785,13 @@ snapshots: agent-base@7.1.4: {} - ai@5.0.41(zod@3.25.76): + ai@5.0.44(zod@4.1.8): dependencies: - '@ai-sdk/gateway': 1.0.21(zod@3.25.76) + '@ai-sdk/gateway': 1.0.23(zod@4.1.8) '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.8(zod@3.25.76) + '@ai-sdk/provider-utils': 3.0.9(zod@4.1.8) '@opentelemetry/api': 1.9.0 - zod: 3.25.76 + zod: 4.1.8 ajv@6.12.6: dependencies: @@ -6898,7 +6806,7 @@ snapshots: ansi-regex@5.0.1: {} - ansi-regex@6.1.0: {} + ansi-regex@6.2.2: {} ansi-styles@4.3.0: dependencies: @@ -6906,7 +6814,7 @@ snapshots: ansi-styles@5.2.0: {} - ansi-styles@6.2.1: {} + ansi-styles@6.2.3: {} anymatch@3.1.3: dependencies: @@ -7014,20 +6922,20 @@ snapshots: axobject-query@4.1.0: {} - babel-jest@30.1.2(@babel/core@7.28.0): + babel-jest@30.1.2(@babel/core@7.28.4): dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.4 '@jest/transform': 30.1.2 '@types/babel__core': 7.20.5 - babel-plugin-istanbul: 7.0.0 - babel-preset-jest: 30.0.1(@babel/core@7.28.0) + babel-plugin-istanbul: 7.0.1 + babel-preset-jest: 30.0.1(@babel/core@7.28.4) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 transitivePeerDependencies: - supports-color - babel-plugin-istanbul@7.0.0: + babel-plugin-istanbul@7.0.1: dependencies: '@babel/helper-plugin-utils': 7.27.1 '@istanbuljs/load-nyc-config': 1.1.0 @@ -7040,37 +6948,39 @@ snapshots: babel-plugin-jest-hoist@30.0.1: dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.2 + '@babel/types': 7.28.4 '@types/babel__core': 7.20.5 - babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.0): + babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.4): dependencies: - '@babel/core': 7.28.0 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.0) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.0) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.0) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.0) - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.0) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.0) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.0) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.0) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.0) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.0) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.0) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.0) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.0) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.0) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.0) + '@babel/core': 7.28.4 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.4) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.4) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.4) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.4) + '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.4) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.4) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.4) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.4) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.4) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.4) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.4) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.4) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.4) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.4) - babel-preset-jest@30.0.1(@babel/core@7.28.0): + babel-preset-jest@30.0.1(@babel/core@7.28.4): dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.4 babel-plugin-jest-hoist: 30.0.1 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.0) + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.4) balanced-match@1.0.2: {} - bowser@2.11.0: {} + baseline-browser-mapping@2.8.3: {} + + bowser@2.12.1: {} brace-expansion@1.1.12: dependencies: @@ -7085,12 +6995,13 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.25.1: + browserslist@4.26.0: dependencies: - caniuse-lite: 1.0.30001731 - electron-to-chromium: 1.5.195 - node-releases: 2.0.19 - update-browserslist-db: 1.1.3(browserslist@4.25.1) + baseline-browser-mapping: 2.8.3 + caniuse-lite: 1.0.30001741 + electron-to-chromium: 1.5.218 + node-releases: 2.0.21 + update-browserslist-db: 1.1.3(browserslist@4.26.0) bser@2.1.1: dependencies: @@ -7130,14 +7041,14 @@ snapshots: camelcase@8.0.0: {} - caniuse-lite@1.0.30001731: {} + caniuse-lite@1.0.30001741: {} chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - chalk@5.6.0: {} + chalk@5.6.2: {} char-regex@1.0.2: {} @@ -7182,7 +7093,7 @@ snapshots: color-string@1.9.1: dependencies: color-name: 1.1.4 - simple-swizzle: 0.2.2 + simple-swizzle: 0.2.4 color@4.2.3: dependencies: @@ -7257,13 +7168,13 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.4.1: + debug@4.4.3: dependencies: ms: 2.1.3 decimal.js@10.6.0: {} - dedent@1.6.0: {} + dedent@1.7.0: {} deep-is@0.1.4: {} @@ -7329,7 +7240,7 @@ snapshots: eastasianwidth@0.2.0: {} - electron-to-chromium@1.5.195: {} + electron-to-chromium@1.5.218: {} emittery@0.13.1: {} @@ -7340,7 +7251,7 @@ snapshots: enhanced-resolve@5.18.3: dependencies: graceful-fs: 4.2.11 - tapable: 2.2.2 + tapable: 2.2.3 entities@4.5.0: {} @@ -7461,12 +7372,12 @@ snapshots: dependencies: '@next/eslint-plugin-next': 15.5.3 '@rushstack/eslint-patch': 1.12.0 - '@typescript-eslint/eslint-plugin': 8.39.0(@typescript-eslint/parser@8.39.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/parser': 8.39.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/eslint-plugin': 8.43.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/parser': 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) eslint: 9.35.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.35.0(jiti@2.5.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.39.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-react: 7.37.5(eslint@9.35.0(jiti@2.5.1)) eslint-plugin-react-hooks: 5.2.0(eslint@9.35.0(jiti@2.5.1)) @@ -7488,30 +7399,30 @@ snapshots: eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.35.0(jiti@2.5.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 - debug: 4.4.1 + debug: 4.4.3 eslint: 9.35.0(jiti@2.5.1) get-tsconfig: 4.10.1 is-bun-module: 2.0.0 stable-hash: 0.0.5 - tinyglobby: 0.2.14 + tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.39.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.39.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.39.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/parser': 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) eslint: 9.35.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.35.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.39.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -7522,7 +7433,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.35.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.39.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.35.0(jiti@2.5.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -7534,7 +7445,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.39.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) + '@typescript-eslint/parser': 8.43.0(eslint@9.35.0(jiti@2.5.1))(typescript@5.9.2) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -7596,7 +7507,7 @@ snapshots: eslint@9.35.0(jiti@2.5.1): dependencies: - '@eslint-community/eslint-utils': 4.8.0(eslint@9.35.0(jiti@2.5.1)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.35.0(jiti@2.5.1)) '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.21.0 '@eslint/config-helpers': 0.3.1 @@ -7604,7 +7515,7 @@ snapshots: '@eslint/eslintrc': 3.3.1 '@eslint/js': 9.35.0 '@eslint/plugin-kit': 0.3.5 - '@humanfs/node': 0.16.6 + '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 @@ -7612,7 +7523,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.1 + debug: 4.4.3 escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -7656,7 +7567,7 @@ snapshots: esutils@2.0.3: {} - eventsource-parser@3.0.5: {} + eventsource-parser@3.0.6: {} execa@5.1.1: dependencies: @@ -7674,15 +7585,6 @@ snapshots: exit-x@0.2.2: {} - expect@30.0.5: - dependencies: - '@jest/expect-utils': 30.0.5 - '@jest/get-type': 30.0.1 - jest-matcher-utils: 30.0.5 - jest-message-util: 30.0.5 - jest-mock: 30.0.5 - jest-util: 30.0.5 - expect@30.1.2: dependencies: '@jest/expect-utils': 30.1.2 @@ -7730,7 +7632,7 @@ snapshots: dependencies: bser: 2.1.1 - fdir@6.4.6(picomatch@4.0.3): + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -7914,14 +7816,14 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 - debug: 4.4.1 + debug: 4.4.3 transitivePeerDependencies: - supports-color https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 - debug: 4.4.1 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -7970,7 +7872,7 @@ snapshots: is-arrayish@0.2.1: {} - is-arrayish@0.3.2: {} + is-arrayish@0.3.4: {} is-async-function@2.1.1: dependencies: @@ -8099,8 +8001,8 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.28.0 - '@babel/parser': 7.28.0 + '@babel/core': 7.28.4 + '@babel/parser': 7.28.4 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 7.7.2 @@ -8115,13 +8017,13 @@ snapshots: istanbul-lib-source-maps@5.0.6: dependencies: - '@jridgewell/trace-mapping': 0.3.29 - debug: 4.4.1 + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.3 istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color - istanbul-reports@3.1.7: + istanbul-reports@3.2.0: dependencies: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 @@ -8153,10 +8055,10 @@ snapshots: '@jest/expect': 30.1.2 '@jest/test-result': 30.1.3 '@jest/types': 30.0.5 - '@types/node': 24.3.1 + '@types/node': 24.3.3 chalk: 4.1.2 co: 4.6.0 - dedent: 1.6.0 + dedent: 1.7.0 is-generator-fn: 2.1.0 jest-each: 30.1.0 jest-matcher-utils: 30.1.2 @@ -8173,15 +8075,15 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@30.1.3(@types/node@24.3.1)(ts-node@10.9.2(@types/node@24.3.1)(typescript@5.9.2)): + jest-cli@30.1.3(@types/node@24.3.3)(ts-node@10.9.2(@types/node@24.3.3)(typescript@5.9.2)): dependencies: - '@jest/core': 30.1.3(ts-node@10.9.2(@types/node@24.3.1)(typescript@5.9.2)) + '@jest/core': 30.1.3(ts-node@10.9.2(@types/node@24.3.3)(typescript@5.9.2)) '@jest/test-result': 30.1.3 '@jest/types': 30.0.5 chalk: 4.1.2 exit-x: 0.2.2 import-local: 3.2.0 - jest-config: 30.1.3(@types/node@24.3.1)(ts-node@10.9.2(@types/node@24.3.1)(typescript@5.9.2)) + jest-config: 30.1.3(@types/node@24.3.3)(ts-node@10.9.2(@types/node@24.3.3)(typescript@5.9.2)) jest-util: 30.0.5 jest-validate: 30.1.0 yargs: 17.7.2 @@ -8192,14 +8094,14 @@ snapshots: - supports-color - ts-node - jest-config@30.1.3(@types/node@24.3.1)(ts-node@10.9.2(@types/node@24.3.1)(typescript@5.9.2)): + jest-config@30.1.3(@types/node@24.3.3)(ts-node@10.9.2(@types/node@24.3.3)(typescript@5.9.2)): dependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.4 '@jest/get-type': 30.1.0 '@jest/pattern': 30.0.1 '@jest/test-sequencer': 30.1.3 '@jest/types': 30.0.5 - babel-jest: 30.1.2(@babel/core@7.28.0) + babel-jest: 30.1.2(@babel/core@7.28.4) chalk: 4.1.2 ci-info: 4.3.0 deepmerge: 4.3.1 @@ -8219,19 +8121,12 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 24.3.1 - ts-node: 10.9.2(@types/node@24.3.1)(typescript@5.9.2) + '@types/node': 24.3.3 + ts-node: 10.9.2(@types/node@24.3.3)(typescript@5.9.2) transitivePeerDependencies: - babel-plugin-macros - supports-color - jest-diff@30.0.5: - dependencies: - '@jest/diff-sequences': 30.0.1 - '@jest/get-type': 30.0.1 - chalk: 4.1.2 - pretty-format: 30.0.5 - jest-diff@30.1.2: dependencies: '@jest/diff-sequences': 30.0.1 @@ -8256,7 +8151,7 @@ snapshots: '@jest/environment': 30.1.2 '@jest/environment-jsdom-abstract': 30.1.2(jsdom@26.1.0) '@types/jsdom': 21.1.7 - '@types/node': 24.3.1 + '@types/node': 24.3.3 jsdom: 26.1.0 transitivePeerDependencies: - bufferutil @@ -8268,7 +8163,7 @@ snapshots: '@jest/environment': 30.1.2 '@jest/fake-timers': 30.1.2 '@jest/types': 30.0.5 - '@types/node': 24.3.1 + '@types/node': 24.3.3 jest-mock: 30.0.5 jest-util: 30.0.5 jest-validate: 30.1.0 @@ -8276,7 +8171,7 @@ snapshots: jest-haste-map@30.1.0: dependencies: '@jest/types': 30.0.5 - '@types/node': 24.3.1 + '@types/node': 24.3.3 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -8293,13 +8188,6 @@ snapshots: '@jest/get-type': 30.1.0 pretty-format: 30.0.5 - jest-matcher-utils@30.0.5: - dependencies: - '@jest/get-type': 30.0.1 - chalk: 4.1.2 - jest-diff: 30.0.5 - pretty-format: 30.0.5 - jest-matcher-utils@30.1.2: dependencies: '@jest/get-type': 30.1.0 @@ -8307,18 +8195,6 @@ snapshots: jest-diff: 30.1.2 pretty-format: 30.0.5 - jest-message-util@30.0.5: - dependencies: - '@babel/code-frame': 7.27.1 - '@jest/types': 30.0.5 - '@types/stack-utils': 2.0.3 - chalk: 4.1.2 - graceful-fs: 4.2.11 - micromatch: 4.0.8 - pretty-format: 30.0.5 - slash: 3.0.0 - stack-utils: 2.0.6 - jest-message-util@30.1.0: dependencies: '@babel/code-frame': 7.27.1 @@ -8334,7 +8210,7 @@ snapshots: jest-mock@30.0.5: dependencies: '@jest/types': 30.0.5 - '@types/node': 24.3.1 + '@types/node': 24.3.3 jest-util: 30.0.5 jest-pnp-resolver@1.2.3(jest-resolve@30.1.3): @@ -8368,7 +8244,7 @@ snapshots: '@jest/test-result': 30.1.3 '@jest/transform': 30.1.2 '@jest/types': 30.0.5 - '@types/node': 24.3.1 + '@types/node': 24.3.3 chalk: 4.1.2 emittery: 0.13.1 exit-x: 0.2.2 @@ -8397,7 +8273,7 @@ snapshots: '@jest/test-result': 30.1.3 '@jest/transform': 30.1.2 '@jest/types': 30.0.5 - '@types/node': 24.3.1 + '@types/node': 24.3.3 chalk: 4.1.2 cjs-module-lexer: 2.1.0 collect-v8-coverage: 1.0.2 @@ -8417,17 +8293,17 @@ snapshots: jest-snapshot@30.1.2: dependencies: - '@babel/core': 7.28.0 - '@babel/generator': 7.28.0 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0) - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.0) - '@babel/types': 7.28.2 + '@babel/core': 7.28.4 + '@babel/generator': 7.28.3 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.4) + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.4) + '@babel/types': 7.28.4 '@jest/expect-utils': 30.1.2 '@jest/get-type': 30.1.0 '@jest/snapshot-utils': 30.1.2 '@jest/transform': 30.1.2 '@jest/types': 30.0.5 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.0) + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.4) chalk: 4.1.2 expect: 30.1.2 graceful-fs: 4.2.11 @@ -8444,7 +8320,7 @@ snapshots: jest-util@30.0.5: dependencies: '@jest/types': 30.0.5 - '@types/node': 24.3.1 + '@types/node': 24.3.3 chalk: 4.1.2 ci-info: 4.3.0 graceful-fs: 4.2.11 @@ -8463,7 +8339,7 @@ snapshots: dependencies: '@jest/test-result': 30.1.3 '@jest/types': 30.0.5 - '@types/node': 24.3.1 + '@types/node': 24.3.3 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -8472,18 +8348,18 @@ snapshots: jest-worker@30.1.0: dependencies: - '@types/node': 24.3.1 + '@types/node': 24.3.3 '@ungap/structured-clone': 1.3.0 jest-util: 30.0.5 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@30.1.3(@types/node@24.3.1)(ts-node@10.9.2(@types/node@24.3.1)(typescript@5.9.2)): + jest@30.1.3(@types/node@24.3.3)(ts-node@10.9.2(@types/node@24.3.3)(typescript@5.9.2)): dependencies: - '@jest/core': 30.1.3(ts-node@10.9.2(@types/node@24.3.1)(typescript@5.9.2)) + '@jest/core': 30.1.3(ts-node@10.9.2(@types/node@24.3.3)(typescript@5.9.2)) '@jest/types': 30.0.5 import-local: 3.2.0 - jest-cli: 30.1.3(@types/node@24.3.1)(ts-node@10.9.2(@types/node@24.3.1)(typescript@5.9.2)) + jest-cli: 30.1.3(@types/node@24.3.3)(ts-node@10.9.2(@types/node@24.3.3)(typescript@5.9.2)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -8493,7 +8369,7 @@ snapshots: jiti@2.5.1: {} - jose@6.0.12: {} + jose@6.1.0: {} js-tokens@4.0.0: {} @@ -8515,7 +8391,7 @@ snapshots: http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.21 + nwsapi: 2.2.22 parse5: 7.3.0 rrweb-cssom: 0.8.0 saxes: 6.0.0 @@ -8554,7 +8430,7 @@ snapshots: jsondiffpatch@0.6.0: dependencies: '@types/diff-match-patch': 1.0.36 - chalk: 5.6.0 + chalk: 5.6.2 diff-match-patch: 1.0.5 jsx-ast-utils@3.3.5: @@ -8650,7 +8526,7 @@ snapshots: lz-string@1.5.0: {} - magic-string@0.30.18: + magic-string@0.30.19: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -8713,14 +8589,14 @@ snapshots: nanoid@5.1.5: {} - napi-postinstall@0.3.2: {} + napi-postinstall@0.3.3: {} natural-compare@1.4.0: {} - next-auth@5.0.0-beta.29(next@15.5.3(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1): + next-auth@5.0.0-beta.29(next@15.5.3(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1): dependencies: '@auth/core': 0.40.0 - next: 15.5.3(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + next: 15.5.3(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: 19.1.1 next-themes@0.4.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1): @@ -8728,15 +8604,15 @@ snapshots: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - next@15.5.3(@babel/core@7.28.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + next@15.5.3(@babel/core@7.28.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: '@next/env': 15.5.3 '@swc/helpers': 0.5.15 - caniuse-lite: 1.0.30001731 + caniuse-lite: 1.0.30001741 postcss: 8.4.31 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - styled-jsx: 5.1.6(@babel/core@7.28.0)(react@19.1.1) + styled-jsx: 5.1.6(@babel/core@7.28.4)(react@19.1.1) optionalDependencies: '@next/swc-darwin-arm64': 15.5.3 '@next/swc-darwin-x64': 15.5.3 @@ -8758,7 +8634,7 @@ snapshots: node-int64@0.4.0: {} - node-releases@2.0.19: {} + node-releases@2.0.21: {} normalize-path@3.0.0: {} @@ -8766,9 +8642,9 @@ snapshots: dependencies: path-key: 3.1.1 - nwsapi@2.2.21: {} + nwsapi@2.2.22: {} - oauth4webapi@3.6.1: {} + oauth4webapi@3.8.1: {} object-assign@4.1.1: {} @@ -9226,9 +9102,9 @@ snapshots: signal-exit@4.1.0: {} - simple-swizzle@0.2.2: + simple-swizzle@0.2.4: dependencies: - is-arrayish: 0.3.2 + is-arrayish: 0.3.4 sirv@2.0.4: dependencies: @@ -9282,7 +9158,7 @@ snapshots: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 - strip-ansi: 7.1.0 + strip-ansi: 7.1.2 string.prototype.includes@2.0.1: dependencies: @@ -9338,9 +9214,9 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.0: + strip-ansi@7.1.2: dependencies: - ansi-regex: 6.1.0 + ansi-regex: 6.2.2 strip-bom@3.0.0: {} @@ -9356,12 +9232,12 @@ snapshots: strnum@2.1.1: {} - styled-jsx@5.1.6(@babel/core@7.28.0)(react@19.1.1): + styled-jsx@5.1.6(@babel/core@7.28.4)(react@19.1.1): dependencies: client-only: 0.0.1 react: 19.1.1 optionalDependencies: - '@babel/core': 7.28.0 + '@babel/core': 7.28.4 supports-color@7.2.0: dependencies: @@ -9387,7 +9263,7 @@ snapshots: tailwindcss@4.1.13: {} - tapable@2.2.2: {} + tapable@2.2.3: {} tar@7.4.3: dependencies: @@ -9406,9 +9282,9 @@ snapshots: throttleit@2.1.0: {} - tinyglobby@0.2.14: + tinyglobby@0.2.15: dependencies: - fdir: 6.4.6(picomatch@4.0.3) + fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 tldts-core@6.1.86: {} @@ -9443,14 +9319,14 @@ snapshots: dependencies: sax: 1.2.4 - ts-node@10.9.2(@types/node@24.3.1)(typescript@5.9.2): + ts-node@10.9.2(@types/node@24.3.3)(typescript@5.9.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 24.3.1 + '@types/node': 24.3.3 acorn: 8.15.0 acorn-walk: 8.3.4 arg: 4.1.3 @@ -9532,7 +9408,7 @@ snapshots: unrs-resolver@1.11.1: dependencies: - napi-postinstall: 0.3.2 + napi-postinstall: 0.3.3 optionalDependencies: '@unrs/resolver-binding-android-arm-eabi': 1.11.1 '@unrs/resolver-binding-android-arm64': 1.11.1 @@ -9554,9 +9430,9 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 - update-browserslist-db@1.1.3(browserslist@4.25.1): + update-browserslist-db@1.1.3(browserslist@4.26.0): dependencies: - browserslist: 4.25.1 + browserslist: 4.26.0 escalade: 3.2.0 picocolors: 1.1.1 @@ -9593,7 +9469,7 @@ snapshots: v8-to-istanbul@9.3.0: dependencies: - '@jridgewell/trace-mapping': 0.3.29 + '@jridgewell/trace-mapping': 0.3.31 '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 @@ -9701,9 +9577,9 @@ snapshots: wrap-ansi@8.1.0: dependencies: - ansi-styles: 6.2.1 + ansi-styles: 6.2.3 string-width: 5.1.2 - strip-ansi: 7.1.0 + strip-ansi: 7.1.2 wrappy@1.0.2: {} @@ -9744,4 +9620,4 @@ snapshots: yocto-queue@0.1.0: {} - zod@3.25.76: {} + zod@4.1.8: {} diff --git a/src/admin/AdminAlbumBadge.tsx b/src/admin/AdminAlbumBadge.tsx new file mode 100644 index 00000000..692f146c --- /dev/null +++ b/src/admin/AdminAlbumBadge.tsx @@ -0,0 +1,21 @@ +import AdminBadge from './AdminBadge'; +import { Album } from '@/album'; +import PhotoAlbum from '@/album/PhotoAlbum'; + +export default async function AdminAlbumBadge({ + album, + count, + hideBadge, +}: { + album: Album, + count: number, + hideBadge?: boolean, +}) { + return ( + } + count={count} + hideBadge={hideBadge} + /> + ); +} \ No newline at end of file diff --git a/src/admin/AdminAlbumForm.tsx b/src/admin/AdminAlbumForm.tsx new file mode 100644 index 00000000..8f1f41a8 --- /dev/null +++ b/src/admin/AdminAlbumForm.tsx @@ -0,0 +1,76 @@ +'use client'; + +import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus'; +import Link from 'next/link'; +import { PATH_ADMIN_ALBUMS } from '@/app/path'; +import FieldsetWithStatus from '@/components/FieldsetWithStatus'; +import { ReactNode, useCallback, useMemo, useState} from 'react'; +import { useAppState } from '@/app/AppState'; +import { Album } from '@/album'; +import { ALBUM_FORM_META } from '@/album/form'; +import { parameterize } from '@/utility/string'; +import { updateAlbumAction } from '@/album/actions'; +import clsx from 'clsx/lite'; + +export default function AdminAlbumForm({ + album, + children, +}: { + album: Album + children?: ReactNode +}) { + const { invalidateSwr } = useAppState(); + + const [albumForm, setAlbumForm] = useState(album); + + const isFormValid = useMemo(() => { + return ALBUM_FORM_META.every(({ key, required }) => { + return !required || Boolean(albumForm[key]); + }); + }, [albumForm]); + + const updateAlbum = useCallback((key: keyof Album, value: string) => { + setAlbumForm(form => ({ + ...form, + [key]: value, + ...key === 'title' && { slug: parameterize(value) }, + })); + }, []); + + return ( +
+ {ALBUM_FORM_META + .filter(({ hidden }) => !hidden) + .map(({ key, label, type, readOnly }) => ( + updateAlbum(key, value)} + isModified={albumForm[key] !== album[key]} + readOnly={readOnly} + className={clsx(key === 'description' && '[&_textarea]:h-36')} + />))} + {children} +
+ + Cancel + + + Update + +
+ + ); +} diff --git a/src/admin/AdminAlbumsTable.tsx b/src/admin/AdminAlbumsTable.tsx new file mode 100644 index 00000000..ce9b0953 --- /dev/null +++ b/src/admin/AdminAlbumsTable.tsx @@ -0,0 +1,46 @@ +import FormWithConfirm from '@/components/FormWithConfirm'; +import AdminTable from '@/admin/AdminTable'; +import { Fragment } from 'react'; +import DeleteFormButton from '@/admin/DeleteFormButton'; +import { photoQuantityText } from '@/photo'; +import EditButton from '@/admin/EditButton'; +import { pathForAdminAlbumEdit } from '@/app/path'; +import { clsx } from 'clsx/lite'; +import { getAppText } from '@/i18n/state/server'; +import { Albums } from '@/album'; +import AdminAlbumBadge from './AdminAlbumBadge'; +import { deleteAlbumAction } from '@/album/actions'; + +export default async function AdminAlbumsTable({ + albums, +}: { + albums: Albums +}) { + const appText = await getAppText(); + + return ( + + {albums.map(({ album, count }) => + +
+ +
+
+ + + + + +
+
)} +
+ ); +} diff --git a/src/admin/AdminAppMenu.tsx b/src/admin/AdminAppMenu.tsx index e248c9cc..9d7be0a6 100644 --- a/src/admin/AdminAppMenu.tsx +++ b/src/admin/AdminAppMenu.tsx @@ -1,6 +1,7 @@ 'use client'; import { + PATH_ADMIN_ALBUMS, PATH_ADMIN_CONFIGURATION, PATH_ADMIN_INSIGHTS, PATH_ADMIN_PHOTOS, @@ -32,6 +33,7 @@ import SwitcherItemMenu from '@/components/switcher/SwitcherItemMenu'; import { MoreMenuSection } from '@/components/more/MoreMenu'; import { FiXSquare } from 'react-icons/fi'; import { useSelectPhotosState } from './select/SelectPhotosState'; +import IconAlbum from '@/components/icons/IconAlbum'; export default function AdminAppMenu({ isOpen, @@ -44,6 +46,7 @@ export default function AdminAppMenu({ photosCountTotal = 0, photosCountNeedSync = 0, uploadsCount = 0, + albumsCount = 0, tagsCount = 0, recipesCount = 0, isLoadingAdminData, @@ -83,8 +86,8 @@ export default function AdminAppMenu({ label: appText.admin.uploadPlural, annotation: `${uploadsCount}`, icon: , href: PATH_ADMIN_UPLOADS, }); @@ -122,6 +125,17 @@ export default function AdminAppMenu({ href: PATH_ADMIN_PHOTOS, }); } + if (albumsCount) { + items.push({ + label: appText.admin.manageAlbums, + annotation: `${albumsCount}`, + icon: , + href: PATH_ADMIN_ALBUMS, + }); + } if (tagsCount) { items.push({ label: appText.admin.manageTags, @@ -186,6 +200,7 @@ export default function AdminAppMenu({ photosCountTotal, recipesCount, showAppInsightsLink, + albumsCount, tagsCount, uploadsCount, ]); diff --git a/src/admin/AdminBadge.tsx b/src/admin/AdminBadge.tsx new file mode 100644 index 00000000..a2138c10 --- /dev/null +++ b/src/admin/AdminBadge.tsx @@ -0,0 +1,42 @@ +import { photoLabelForCount } from '@/photo'; +import { clsx } from 'clsx/lite'; +import Badge from '@/components/Badge'; +import { getAppText } from '@/i18n/state/server'; +import { ReactNode } from 'react'; + +export default async function AdminBadge({ + entity, + count, + hideBadge, + className, +}: { + entity: ReactNode, + count: number, + hideBadge?: boolean, + className?: string, +}) { + const appText = await getAppText(); + + const renderBadgeContent = () => +
*>*:first-child]:items-center', + className, + )}> + {entity} +
+ {count} + +   + {photoLabelForCount(count, appText)} + +
+
; + + return ( + hideBadge + ? renderBadgeContent() + : {renderBadgeContent()} + ); +} \ No newline at end of file diff --git a/src/admin/AdminNav.tsx b/src/admin/AdminNav.tsx index f51bd725..d8d071af 100644 --- a/src/admin/AdminNav.tsx +++ b/src/admin/AdminNav.tsx @@ -6,6 +6,7 @@ import { getUniqueTagsCached, } from '@/photo/cache'; import { + PATH_ADMIN_ALBUMS, PATH_ADMIN_PHOTOS, PATH_ADMIN_RECIPES, PATH_ADMIN_TAGS, @@ -13,11 +14,13 @@ import { } from '@/app/path'; import AdminNavClient from './AdminNavClient'; import { getAppText } from '@/i18n/state/server'; +import { getAlbumsWithMeta } from '@/album/query'; export default async function AdminNav() { const [ countPhotos, countUploads, + countAlbums, countTags, countRecipes, mostRecentPhotoUpdateTime, @@ -31,6 +34,8 @@ export default async function AdminNav() { console.error(`Error getting blob upload urls: ${e}`); return 0; }), + getAlbumsWithMeta().then(albums => albums.length) + .catch(() => 0), getUniqueTagsCached().then(tags => tags.length) .catch(() => 0), getUniqueRecipesCached().then(recipes => recipes.length) @@ -56,6 +61,13 @@ export default async function AdminNav() { count: countUploads, }); } + // Albums + if (countAlbums > 0) { items.push({ + label: appText.category.albumPlural, + href: PATH_ADMIN_ALBUMS, + count: countAlbums, + }); } + // Tags if (countTags > 0) { items.push({ label: appText.category.tagPlural, diff --git a/src/admin/AdminRecipeBadge.tsx b/src/admin/AdminRecipeBadge.tsx index 80ca2b2d..dbf55973 100644 --- a/src/admin/AdminRecipeBadge.tsx +++ b/src/admin/AdminRecipeBadge.tsx @@ -19,7 +19,7 @@ export default async function AdminRecipeBadge({
- +
{count} diff --git a/src/admin/AdminTagBadge.tsx b/src/admin/AdminTagBadge.tsx index cb3a93b1..435457b5 100644 --- a/src/admin/AdminTagBadge.tsx +++ b/src/admin/AdminTagBadge.tsx @@ -1,10 +1,7 @@ import PhotoTag from '@/tag/PhotoTag'; -import { photoLabelForCount } from '@/photo'; -import { clsx } from 'clsx/lite'; import PhotoFavs from '@/tag/PhotoFavs'; import { isTagFavs } from '@/tag'; -import Badge from '@/components/Badge'; -import { getAppText } from '@/i18n/state/server'; +import AdminBadge from './AdminBadge'; export default async function AdminTagBadge({ tag, @@ -15,30 +12,14 @@ export default async function AdminTagBadge({ count: number, hideBadge?: boolean, }) { - const appText = await getAppText(); - - const renderBadgeContent = () => -
*>*:first-child]:items-center', - isTagFavs(tag) && 'translate-y-[0.5px]', - )}> - {isTagFavs(tag) - ? - : } -
- {count} - -   - {photoLabelForCount(count, appText)} - -
-
; - return ( - hideBadge - ? renderBadgeContent() - : {renderBadgeContent()} + + : } + count={count} + hideBadge={hideBadge} + /> ); } \ No newline at end of file diff --git a/src/admin/AdminTagTable.tsx b/src/admin/AdminTagsTable.tsx similarity index 97% rename from src/admin/AdminTagTable.tsx rename to src/admin/AdminTagsTable.tsx index 260a7676..3483bb84 100644 --- a/src/admin/AdminTagTable.tsx +++ b/src/admin/AdminTagsTable.tsx @@ -11,7 +11,7 @@ import { clsx } from 'clsx/lite'; import AdminTagBadge from './AdminTagBadge'; import { getAppText } from '@/i18n/state/server'; -export default async function AdminTagTable({ +export default async function AdminTagsTable({ tags, }: { tags: Tags diff --git a/src/admin/actions.ts b/src/admin/actions.ts index 37186bda..725166e0 100644 --- a/src/admin/actions.ts +++ b/src/admin/actions.ts @@ -12,11 +12,12 @@ import { getUniqueTags, getUniqueRecipes, getPhotosInNeedOfUpdateCount, -} from '@/photo/db/query'; +} from '@/photo/query'; import { getGitHubMetaForCurrentApp, indicatorStatusForSignificantInsights, } from './insights'; +import { getAlbumsWithMeta } from '@/album/query'; export type AdminData = Awaited>; @@ -28,6 +29,7 @@ export const getAdminDataAction = async () => photosCountNeedSync, codeMeta, uploadsCount, + albumsCount, tagsCount, recipesCount, ] = await Promise.all([ @@ -45,6 +47,9 @@ export const getAdminDataAction = async () => console.error(`Error getting blob upload urls: ${e}`); return 0; }), + getAlbumsWithMeta() + .then(albums => albums.length) + .catch(() => 0), getUniqueTags() .then(tags => tags.length) .catch(() => 0), @@ -71,6 +76,7 @@ export const getAdminDataAction = async () => photosCountNeedSync, photosCountTotal, uploadsCount, + albumsCount, tagsCount, recipesCount, insightsIndicatorStatus, diff --git a/src/admin/insights/AdminAppInsights.tsx b/src/admin/insights/AdminAppInsights.tsx index 688d3a0e..e7b6bce5 100644 --- a/src/admin/insights/AdminAppInsights.tsx +++ b/src/admin/insights/AdminAppInsights.tsx @@ -7,7 +7,7 @@ import { getUniqueRecipes, getUniqueTags, getPhotosInNeedOfUpdateCount, -} from '@/photo/db/query'; +} from '@/photo/query'; import AdminAppInsightsClient from './AdminAppInsightsClient'; import { getAllInsights, getGitHubMetaForCurrentApp } from '.'; import { USED_DEPRECATED_ENV_VARS } from '@/app/config'; diff --git a/src/admin/insights/AdminAppInsightsClient.tsx b/src/admin/insights/AdminAppInsightsClient.tsx index 3f30db8b..a759d2eb 100644 --- a/src/admin/insights/AdminAppInsightsClient.tsx +++ b/src/admin/insights/AdminAppInsightsClient.tsx @@ -2,7 +2,7 @@ import ScoreCard from '@/components/ScoreCard'; import ScoreCardRow from '@/components/ScoreCardRow'; -import { dateRangeForPhotos } from '@/photo'; +import { formattedDateRangeForPhotos } from '@/photo'; import { FaArrowRight, FaCircleInfo, FaRegCalendar } from 'react-icons/fa6'; import { MdAspectRatio } from 'react-icons/md'; import { PiWarningBold } from 'react-icons/pi'; @@ -125,7 +125,8 @@ export default function AdminAppInsightsClient({ noStaticOptimization, } = insights; - const { descriptionWithSpaces } = dateRangeForPhotos(undefined, dateRange); + const { descriptionWithSpaces } = + formattedDateRangeForPhotos(undefined, dateRange); const branchLink = diff --git a/src/album/AlbumHeader.tsx b/src/album/AlbumHeader.tsx new file mode 100644 index 00000000..626497d8 --- /dev/null +++ b/src/album/AlbumHeader.tsx @@ -0,0 +1,91 @@ +import { Photo, PhotoDateRangePostgres } from '@/photo'; +import PhotoHeader from '@/photo/PhotoHeader'; +import { + AI_CONTENT_GENERATION_ENABLED, + SHOW_CATEGORY_IMAGE_HOVERS, +} from '@/app/config'; +import { getAppText } from '@/i18n/state/server'; +import { Album, descriptionForAlbumPhotos } from '.'; +import { safelyParseFormattedHtml } from '@/utility/html'; +import PhotoAlbum from './PhotoAlbum'; +import PhotoTag from '@/tag/PhotoTag'; +import IconTag from '@/components/icons/IconTag'; +import MaskedScroll from '@/components/MaskedScroll'; + +export default async function AlbumHeader({ + album, + photos, + tags = [], + selectedPhoto, + indexNumber, + count, + dateRange, + showAlbumMeta, +}: { + album: Album + photos: Photo[] + tags?: string[] + selectedPhoto?: Photo + indexNumber?: number + count?: number + dateRange?: PhotoDateRangePostgres + showAlbumMeta?: boolean +}) { + const appText = await getAppText(); + return ( + } + entityDescription={descriptionForAlbumPhotos( + photos, + appText, + undefined, + count, + )} + photos={photos} + selectedPhoto={selectedPhoto} + indexNumber={indexNumber} + count={count} + dateRange={dateRange} + richContent={showAlbumMeta + ?
+ {album.subhead && +
+ {album.subhead} +
} + {tags.length > 0 && + + + {tags.map(tag => ( + + ))} + } + {album.description && +
} +
+ : undefined} + hasAiTextGeneration={AI_CONTENT_GENERATION_ENABLED} + includeShareButton + /> + ); +} diff --git a/src/album/AlbumImageResponse.tsx b/src/album/AlbumImageResponse.tsx new file mode 100644 index 00000000..403c963a --- /dev/null +++ b/src/album/AlbumImageResponse.tsx @@ -0,0 +1,46 @@ +import type { Photo } from '../photo'; +import ImageCaption from '@/image-response/components/ImageCaption'; +import ImagePhotoGrid from '@/image-response/components/ImagePhotoGrid'; +import ImageContainer from '@/image-response/components/ImageContainer'; +import type { NextImageSize } from '@/platforms/next-image'; +import { Album } from '.'; +import IconAlbum from '@/components/icons/IconAlbum'; + +export default function AlbumImageResponse({ + album, + photos, + width, + height, + fontFamily, +}: { + album: Album, + photos: Photo[] + width: NextImageSize + height: number + fontFamily: string +}) { + return ( + + + , + title: album.title.toLocaleUpperCase(), + }} /> + + ); +} diff --git a/src/album/AlbumOverview.tsx b/src/album/AlbumOverview.tsx new file mode 100644 index 00000000..5fbde1a1 --- /dev/null +++ b/src/album/AlbumOverview.tsx @@ -0,0 +1,38 @@ +import { Photo, PhotoDateRangePostgres } from '@/photo'; +import PhotoGridContainer from '@/photo/PhotoGridContainer'; +import { Album } from '.'; +import AlbumHeader from './AlbumHeader'; + +export default function AlbumOverview({ + album, + photos, + tags, + count, + dateRange, + animateOnFirstLoadOnly, +}: { + album: Album, + photos: Photo[], + tags: string[], + count: number, + dateRange?: PhotoDateRangePostgres, + animateOnFirstLoadOnly?: boolean, +}) { + return ( + , + animateOnFirstLoadOnly, + }} /> + ); +} diff --git a/src/album/AlbumShareModal.tsx b/src/album/AlbumShareModal.tsx new file mode 100644 index 00000000..422e5980 --- /dev/null +++ b/src/album/AlbumShareModal.tsx @@ -0,0 +1,26 @@ +import { absolutePathForAlbum } from '@/app/path'; +import { PhotoSetAttributes } from '../category'; +import ShareModal from '@/share/ShareModal'; +import { useAppText } from '@/i18n/state/client'; +import AlbumOGTile from '@/tag/AlbumOGTile'; +import { Album, shareTextForAlbum } from '.'; + +export default function AlbumShareModal({ + album, + photos, + count, + dateRange, +}: { + album: Album +} & PhotoSetAttributes) { + const appText = useAppText(); + return ( + + + + ); +}; diff --git a/src/album/FieldsetAlbum.tsx b/src/album/FieldsetAlbum.tsx new file mode 100644 index 00000000..894289a6 --- /dev/null +++ b/src/album/FieldsetAlbum.tsx @@ -0,0 +1,19 @@ +import { ComponentProps } from 'react'; +import FieldsetWithStatus from '@/components/FieldsetWithStatus'; +import { Albums } from '.'; +import { convertAlbumsToAnnotatedTags } from './form'; + +export default function FieldsetAlbum({ + albumOptions, + ...props +}: { + albumOptions: Albums +} & ComponentProps) { + return ( + + ); +} diff --git a/src/album/PhotoAlbum.tsx b/src/album/PhotoAlbum.tsx new file mode 100644 index 00000000..571f4ada --- /dev/null +++ b/src/album/PhotoAlbum.tsx @@ -0,0 +1,27 @@ +'use client'; + +import { pathForAlbum } from '@/app/path'; +import EntityLink, { EntityLinkExternalProps } from + '@/components/entity/EntityLink'; +import IconAlbum from '@/components/icons/IconAlbum'; +import { Album } from '.'; +import useCategoryCounts from '@/category/useCategoryCounts'; + +export default function PhotoAlbum({ + album, + ...props +}: { + album: Album +} & EntityLinkExternalProps) { + const { getAlbumCount } = useCategoryCounts(); + return ( + } + hoverCount={props.hoverCount ?? getAlbumCount(album)} + /> + ); +} diff --git a/src/album/actions.ts b/src/album/actions.ts new file mode 100644 index 00000000..95419811 --- /dev/null +++ b/src/album/actions.ts @@ -0,0 +1,23 @@ +'use server'; + +import { runAuthenticatedAdminServerAction } from '@/auth/server'; +import { deleteAlbum, updateAlbum } from './query'; +import { revalidateAllKeysAndPaths } from '@/photo/cache'; +import { redirect } from 'next/navigation'; +import { PATH_ADMIN_ALBUMS } from '@/app/path'; +import { convertFormDataToAlbum } from './form'; + +export const updateAlbumAction = async (formData: FormData) => + runAuthenticatedAdminServerAction(async () => { + const album = convertFormDataToAlbum(formData); + await updateAlbum(album); + revalidateAllKeysAndPaths(); + redirect(PATH_ADMIN_ALBUMS); + }); + +export const deleteAlbumAction = async (formData: FormData) => + runAuthenticatedAdminServerAction(async () => { + const albumId = formData.get('album') as string; + await deleteAlbum(albumId); + revalidateAllKeysAndPaths(); + }); diff --git a/src/album/data.ts b/src/album/data.ts new file mode 100644 index 00000000..632b0a94 --- /dev/null +++ b/src/album/data.ts @@ -0,0 +1,16 @@ +import { getPhotosMetaCached } from '@/photo/cache'; +import { Album } from '.'; +import { getPhotos } from '@/photo/query'; + +export const getPhotosAlbumDataCached = ({ + album, + limit, +}: { + album: Album, + limit?: number, +}) => + Promise.all([ + getPhotos({ album, limit }), + getPhotosMetaCached({ album }), + ]); + diff --git a/src/album/form.ts b/src/album/form.ts new file mode 100644 index 00000000..6820f4af --- /dev/null +++ b/src/album/form.ts @@ -0,0 +1,52 @@ +import { AnnotatedTag, FieldSetType } from '@/photo/form'; +import { Album, Albums } from '.'; +import { formatCount, formatCountDescriptive } from '@/utility/string'; + +export const ALBUM_FORM_META: { + key: keyof Album + label?: string + type: FieldSetType + required?: boolean + readOnly?: boolean + hidden?: boolean +}[] = [ + { key: 'id', type: 'text', readOnly: true }, + { key: 'title', type: 'text', required: true }, + { key: 'slug', type: 'text', required: true, readOnly: true }, + { key: 'subhead', type: 'text' }, + { key: 'description', type: 'textarea' }, + { key: 'locationName', label: 'location name', type: 'text', hidden: true }, + { key: 'latitude', type: 'text', hidden: true }, + { key: 'longitude', type: 'text', hidden: true }, +]; + +export const convertFormDataToAlbum = (formData: FormData): Album => { + return { + id: formData.get('id') as string, + title: formData.get('title') as string, + slug: formData.get('slug') as string, + subhead: formData.get('subhead') as string, + description: formData.get('description') as string, + locationName: formData.get('locationName') as string, + latitude: formData.get('latitude') + ? parseFloat(formData.get('latitude') as string) + : undefined, + longitude: formData.get('longitude') + ? parseFloat(formData.get('longitude') as string) + : undefined, + }; +}; + +export const convertAlbumsToAnnotatedTags = ( + albums: Albums = [], +): AnnotatedTag[] => + albums + .sort((a, b) => a.album.title.localeCompare(b.album.title)) + .map(({ album, count }) => ({ + value: album.title, + annotation: formatCount(count), + annotationAria: formatCountDescriptive(count), + })); + +export const getAlbumTitlesFromFormData = (formData: FormData) => + formData.get('albums')?.toString().split(',').filter(Boolean) ?? []; diff --git a/src/album/index.ts b/src/album/index.ts new file mode 100644 index 00000000..e850ac01 --- /dev/null +++ b/src/album/index.ts @@ -0,0 +1,85 @@ +import { absolutePathForAlbum, absolutePathForAlbumImage } from '@/app/path'; +import { CategoryQueryMeta } from '@/category'; +import { AppTextState } from '@/i18n/state'; +import { + descriptionForPhotoSet, + Photo, + PhotoDateRangePostgres, + photoQuantityText, +} from '@/photo'; +import camelcaseKeys from 'camelcase-keys'; + +export interface Album { + id: string + title: string + slug: string + subhead?: string + description?: string + locationName?: string + latitude?: number + longitude?: number +} + +type AlbumWithMeta = { + album: Album +} & CategoryQueryMeta; + +export type Albums = AlbumWithMeta[]; + +export type AlbumOrAlbumSlug = Album | string; + +export const parseAlbumFromDb = (album: any): Album => + camelcaseKeys(album); + +export const titleForAlbum = ( + album: Album, + photos:Photo[] = [], + appText: AppTextState, + explicitCount?: number, +) => [ + album.title, + photoQuantityText(explicitCount ?? photos.length, appText), +].join(' '); + +export const shareTextForAlbum = ( + album: Album, + appText: AppTextState, +) => [ + `${appText.category.album}:`, + album.title, +].join(' '); + +export const descriptionForAlbumPhotos = ( + photos: Photo[] = [], + appText: AppTextState, + dateBased?: boolean, + explicitCount?: number, + explicitDateRange?: PhotoDateRangePostgres, +) => + descriptionForPhotoSet( + photos, + appText, + undefined, + dateBased, + explicitCount, + explicitDateRange, + ); + +export const generateMetaForAlbum = ( + album: Album, + photos: Photo[], + appText: AppTextState, + explicitCount?: number, + explicitDateRange?: PhotoDateRangePostgres, +) => ({ + url: absolutePathForAlbum(album), + title: titleForAlbum(album, photos, appText, explicitCount), + description: descriptionForAlbumPhotos( + photos, + appText, + true, + explicitCount, + explicitDateRange, + ), + images: absolutePathForAlbumImage(album), +}); diff --git a/src/album/query.ts b/src/album/query.ts new file mode 100644 index 00000000..bd87611a --- /dev/null +++ b/src/album/query.ts @@ -0,0 +1,124 @@ +import { safelyQuery } from '@/db/query'; +import { sql } from '@/platforms/postgres'; +import { Album, Albums, parseAlbumFromDb } from '.'; + +export const createAlbumsTable = () => + sql` + CREATE TABLE IF NOT EXISTS albums ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + title VARCHAR(255) NOT NULL, + slug VARCHAR(255) UNIQUE NOT NULL, + subhead TEXT, + description TEXT, + location_name VARCHAR(255), + latitude DOUBLE PRECISION, + longitude DOUBLE PRECISION, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP + ) + `; + +export const createAlbumPhotoTable = () => + sql` + CREATE TABLE IF NOT EXISTS album_photo ( + album_id uuid NOT NULL REFERENCES albums(id) ON DELETE CASCADE, + photo_id VARCHAR(8) NOT NULL REFERENCES photos(id) ON DELETE CASCADE, + sort_order SMALLINT NOT NULL DEFAULT 0, + PRIMARY KEY (album_id, photo_id) + ) + `; + +export const insertAlbum = (album: Omit) => + safelyQuery(() => sql` + INSERT INTO albums ( + title, + slug, + subhead, + description, + location_name, + latitude, + longitude + ) VALUES ( + ${album.title}, + ${album.slug}, + ${album.subhead}, + ${album.description}, + ${album.locationName}, + ${album.latitude}, + ${album.longitude} + ) + RETURNING id + `.then(({ rows }) => rows[0]?.id as string) + , 'insertAlbum'); + +export const updateAlbum = (album: Album) => + safelyQuery(() => sql` + UPDATE albums SET + title=${album.title}, + slug=${album.slug}, + subhead=${album.subhead}, + description=${album.description}, + location_name=${album.locationName}, + latitude=${album.latitude}, + longitude=${album.longitude}, + updated_at=${(new Date()).toISOString()} + WHERE id=${album.id} + `, 'updateAlbum'); + +export const getAlbumFromSlug = (slug: string) => + safelyQuery(() => sql` + SELECT * FROM albums WHERE slug=${slug} + `.then(({ rows }) => rows[0] ? parseAlbumFromDb(rows[0]) : undefined) + , 'getAlbum'); + +export const deleteAlbum = (id: string) => + safelyQuery(() => sql` + DELETE FROM albums WHERE id=${id} + `, 'deleteAlbum'); + +export const getAlbumsWithMeta = () => + safelyQuery(() => sql` + SELECT + a.*, + COALESCE(COUNT(ap.photo_id), 0) as count + FROM albums a + LEFT JOIN album_photo ap ON a.id = ap.album_id + GROUP BY a.id + ORDER BY a.created_at DESC + `.then(({ rows }): Albums => rows.map(({ + count, + ...album + }) => ({ + album: parseAlbumFromDb(album), + count: parseInt(count, 10), + lastModified: album.updated_at as Date, + }))) + , 'getAlbumsWithPhotoCounts'); + +export const clearPhotoAlbumIds = (photoId: string) => + safelyQuery(() => sql` + DELETE FROM album_photo WHERE photo_id=${photoId} + `, 'clearPhotoAlbumIds'); + +export const addPhotoAlbumId = (photoId: string, albumId: string) => + safelyQuery(() => sql` + INSERT INTO album_photo (album_id, photo_id) VALUES (${albumId}, ${photoId}) + ON CONFLICT (album_id, photo_id) DO NOTHING + `, 'updateAlbumPhoto'); + +export const getAlbumTitlesForPhoto = (photoId: string) => + safelyQuery(() => sql<{ title: string }>` + SELECT a.title FROM albums a + JOIN album_photo ap ON a.id = ap.album_id + WHERE ap.photo_id=${photoId} + `.then(({ rows }) => rows.map(({ title }) => title)) + , 'getAlbumTitlesForPhoto'); + +export const getTagsForAlbum = (albumId: string) => + safelyQuery(() => sql` + SELECT DISTINCT unnest(p.tags) as tag + FROM photos p + LEFT JOIN album_photo ap ON p.id = ap.photo_id + WHERE album_id=${albumId} + `.then(({ rows }) => rows.map(({ tag }) => tag)) + , 'getTagsForAlbum'); diff --git a/src/album/server.ts b/src/album/server.ts new file mode 100644 index 00000000..8e55a5a4 --- /dev/null +++ b/src/album/server.ts @@ -0,0 +1,30 @@ +import { parameterize } from '@/utility/string'; +import { + addPhotoAlbumId, + clearPhotoAlbumIds, + getAlbumsWithMeta, + insertAlbum, +} from './query'; + +const createAlbumsAndGetIds = async (titles: string[]) => { + const albums = await getAlbumsWithMeta(); + return Promise.all(titles.map(async title => { + const album = albums.find(({ album }) => album.title === title); + if (album) { + return album.album.id; + } else { + const albumInsert = { title, slug: parameterize(title) }; + return insertAlbum(albumInsert); + } + })); +}; + +export const addAlbumTitlesToPhoto = async ( + albumTitles: string[], + photoId: string, + shouldClearPhotoAlbumIds = true, +) => { + const albumIds = await createAlbumsAndGetIds(albumTitles); + if (shouldClearPhotoAlbumIds) { await clearPhotoAlbumIds(photoId); } + await Promise.all(albumIds.map(albumId => addPhotoAlbumId(photoId, albumId))); +}; diff --git a/src/image-response/HomeImageResponse.tsx b/src/app/HomeImageResponse.tsx similarity index 73% rename from src/image-response/HomeImageResponse.tsx rename to src/app/HomeImageResponse.tsx index 8d429654..0781ed1b 100644 --- a/src/image-response/HomeImageResponse.tsx +++ b/src/app/HomeImageResponse.tsx @@ -1,8 +1,8 @@ import { NAV_TITLE } from '@/app/config'; import { Photo } from '../photo'; -import ImageCaption from './components/ImageCaption'; -import ImageContainer from './components/ImageContainer'; -import ImagePhotoGrid from './components/ImagePhotoGrid'; +import ImageCaption from '@/image-response/components/ImageCaption'; +import ImageContainer from '@/image-response/components/ImageContainer'; +import ImagePhotoGrid from '@/image-response/components/ImagePhotoGrid'; import { NextImageSize } from '@/platforms/next-image'; export default function HomeImageResponse({ diff --git a/src/image-response/TemplateImageResponse.tsx b/src/app/TemplateImageResponse.tsx similarity index 97% rename from src/image-response/TemplateImageResponse.tsx rename to src/app/TemplateImageResponse.tsx index 52e61cb0..04257c75 100644 --- a/src/image-response/TemplateImageResponse.tsx +++ b/src/app/TemplateImageResponse.tsx @@ -1,7 +1,7 @@ import { Photo } from '../photo'; import IconFull from '@/components/icons/IconFull'; import IconGrid from '@/components/icons/IconGrid'; -import ImagePhotoGrid from './components/ImagePhotoGrid'; +import ImagePhotoGrid from '../image-response/components/ImagePhotoGrid'; import { NextImageSize } from '@/platforms/next-image'; export default function TemplateImageResponse({ diff --git a/src/app/config.ts b/src/app/config.ts index 7aa5fef0..ca09c312 100644 --- a/src/app/config.ts +++ b/src/app/config.ts @@ -274,6 +274,8 @@ export const SHOW_CAMERAS = CATEGORY_VISIBILITY.includes('cameras'); export const SHOW_LENSES = CATEGORY_VISIBILITY.includes('lenses'); +export const SHOW_ALBUMS = + CATEGORY_VISIBILITY.includes('albums'); export const SHOW_TAGS = CATEGORY_VISIBILITY.includes('tags'); export const SHOW_RECIPES = diff --git a/src/app/path.ts b/src/app/path.ts index ab2bf915..5705e449 100644 --- a/src/app/path.ts +++ b/src/app/path.ts @@ -5,6 +5,7 @@ import { Camera } from '@/camera'; import { parameterize } from '@/utility/string'; import { TAG_PRIVATE } from '@/tag'; import { Lens } from '@/lens'; +import { Album, AlbumOrAlbumSlug } from '@/album'; // Core export const PATH_ROOT = '/'; @@ -43,6 +44,7 @@ export const PATH_FEED_JSON = '/feed.json'; export const PREFIX_PHOTO = '/p'; export const PREFIX_CAMERA = '/shot-on'; export const PREFIX_LENS = '/lens'; +export const PREFIX_ALBUM = '/album'; export const PREFIX_TAG = '/tag'; export const PREFIX_RECIPE = '/recipe'; export const PREFIX_FILM = '/film'; @@ -54,6 +56,7 @@ export const PREFIX_RECENTS = '/recents'; const PATH_PHOTO_DYNAMIC = `${PREFIX_PHOTO}/[photoId]`; const PATH_CAMERA_DYNAMIC = `${PREFIX_CAMERA}/[make]/[model]`; const PATH_LENS_DYNAMIC = `${PREFIX_LENS}/[make]/[model]`; +const PATH_ALBUM_DYNAMIC = `${PREFIX_ALBUM}/[album]`; const PATH_TAG_DYNAMIC = `${PREFIX_TAG}/[tag]`; const PATH_FILM_DYNAMIC = `${PREFIX_FILM}/[film]`; const PATH_FOCAL_LENGTH_DYNAMIC = `${PREFIX_FOCAL_LENGTH}/[focal]`; @@ -65,6 +68,7 @@ const PATH_RECENTS_DYNAMIC = `${PREFIX_RECENTS}/[photoId]`; export const PATH_ADMIN_PHOTOS = `${PATH_ADMIN}/photos`; export const PATH_ADMIN_PHOTOS_UPDATES = `${PATH_ADMIN_PHOTOS}/updates`; export const PATH_ADMIN_UPLOADS = `${PATH_ADMIN}/uploads`; +export const PATH_ADMIN_ALBUMS = `${PATH_ADMIN}/albums`; export const PATH_ADMIN_TAGS = `${PATH_ADMIN}/tags`; export const PATH_ADMIN_RECIPES = `${PATH_ADMIN}/recipes`; export const PATH_ADMIN_CONFIGURATION = `${PATH_ADMIN}/configuration`; @@ -95,6 +99,7 @@ export const PATHS_ADMIN = [ PATH_ADMIN_PHOTOS, PATH_ADMIN_PHOTOS_UPDATES, PATH_ADMIN_UPLOADS, + PATH_ADMIN_ALBUMS, PATH_ADMIN_TAGS, PATH_ADMIN_RECIPES, PATH_ADMIN_INSIGHTS, @@ -111,6 +116,7 @@ export const PATHS_TO_CACHE = [ PATH_PHOTO_DYNAMIC, PATH_CAMERA_DYNAMIC, PATH_LENS_DYNAMIC, + PATH_ALBUM_DYNAMIC, PATH_TAG_DYNAMIC, PATH_FILM_DYNAMIC, PATH_FOCAL_LENGTH_DYNAMIC, @@ -131,6 +137,9 @@ export const pathForAdminUploadUrl = (url: string, title?: string) => export const pathForAdminPhotoEdit = (photo: PhotoOrPhotoId) => `${PATH_ADMIN_PHOTOS}/${getPhotoId(photo)}/${EDIT}`; +export const pathForAdminAlbumEdit = (album: Album) => + `${PATH_ADMIN_ALBUMS}/${album.slug}/${EDIT}`; + export const pathForAdminTagEdit = (tag: string) => `${PATH_ADMIN_TAGS}/${tag}/${EDIT}`; @@ -148,6 +157,7 @@ export const pathForPhoto = ({ year, camera, lens, + album, tag, film, focal, @@ -165,6 +175,8 @@ export const pathForPhoto = ({ prefix = pathForCamera(camera); } else if (lens) { prefix = pathForLens(lens); + } else if (album) { + prefix = pathForAlbum(album); } else if (tag) { prefix = pathForTag(tag); } else if (recipe) { @@ -178,6 +190,9 @@ export const pathForPhoto = ({ return `${prefix}/${getPhotoId(photo)}`; }; +export const pathForYear = (year: string) => + `${PREFIX_YEAR}/${year}`; + export const pathForCamera = ({ make, model }: Camera) => `${PREFIX_CAMERA}/${parameterize(make)}/${parameterize(model)}`; @@ -186,6 +201,9 @@ export const pathForLens = ({ make, model }: Lens) => ? `${PREFIX_LENS}/${parameterize(make)}/${parameterize(model)}` : `${PREFIX_LENS}/${MISSING_FIELD}/${parameterize(model)}`; +export const pathForAlbum = (album: AlbumOrAlbumSlug) => + `${PREFIX_ALBUM}/${typeof album === 'string' ? album : album.slug}`; + export const pathForTag = (tag: string) => `${PREFIX_TAG}/${tag}`; @@ -198,9 +216,6 @@ export const pathForFilm = (film: string) => export const pathForFocalLength = (focal: number) => `${PREFIX_FOCAL_LENGTH}/${focal}mm`; -export const pathForYear = (year: string) => - `${PREFIX_YEAR}/${year}`; - // Image paths const pathForImage = (path: string) => `${path}/${IMAGE}`; @@ -214,6 +229,9 @@ export const pathForCameraImage = (camera: Camera) => export const pathForLensImage = (lens: Lens) => pathForImage(pathForLens(lens)); +export const pathForAlbumImage = (album: Album) => + pathForImage(pathForAlbum(album)); + export const pathForTagImage = (tag: string) => pathForImage(pathForTag(tag)); @@ -259,6 +277,9 @@ export const absolutePathForCamera= (camera: Camera, share?: boolean) => export const absolutePathForLens= (lens: Lens, share?: boolean) => `${getBaseUrl(share)}${pathForLens(lens)}`; + +export const absolutePathForAlbum = (album: Album, share?: boolean) => + `${getBaseUrl(share)}${pathForAlbum(album)}`; export const absolutePathForTag = (tag: string, share?: boolean) => `${getBaseUrl(share)}${pathForTag(tag)}`; @@ -279,31 +300,34 @@ export const absolutePathForRecents = (share?: boolean) => `${getBaseUrl(share)}${PREFIX_RECENTS}`; export const absolutePathForPhotoImage = (photo: PhotoOrPhotoId) => - `${getBaseUrl()}${pathForPhotoImage(photo)}`; + `${absolutePathForPhoto({ photo })}/${IMAGE}`; export const absolutePathForCameraImage= (camera: Camera) => - `${getBaseUrl()}${pathForCameraImage(camera)}`; + `${absolutePathForCamera(camera)}/${IMAGE}`; export const absolutePathForLensImage= (lens: Lens) => - `${getBaseUrl()}${pathForLensImage(lens)}`; + `${absolutePathForLens(lens)}/${IMAGE}`; + +export const absolutePathForAlbumImage = (album: Album) => + `${absolutePathForAlbum(album)}/${IMAGE}`; export const absolutePathForTagImage = (tag: string) => - `${getBaseUrl()}${pathForTagImage(tag)}`; + `${absolutePathForTag(tag)}/${IMAGE}`; export const absolutePathForRecipeImage = (recipe: string) => - `${getBaseUrl()}${pathForRecipeImage(recipe)}`; + `${absolutePathForRecipe(recipe)}/${IMAGE}`; export const absolutePathForFilmImage = (film: string) => - `${getBaseUrl()}${pathForFilmImage(film)}`; + `${absolutePathForFilm(film)}/${IMAGE}`; export const absolutePathForFocalLengthImage = (focal: number) => - `${getBaseUrl()}${pathForFocalLengthImage(focal)}`; + `${absolutePathForFocalLength(focal)}/${IMAGE}`; + +export const absolutePathForYearImage = (year: string) => + `${absolutePathForYear(year)}/${IMAGE}`; -export const absolutePathForYearImage = (year: string, share?: boolean) => - `${getBaseUrl(share)}${pathForYearImage(year)}`; - -export const absolutePathForRecentsImage = (share?: boolean) => - `${getBaseUrl(share)}${pathForRecentsImage()}`; +export const absolutePathForRecentsImage = () => + `${absolutePathForRecents()}/${IMAGE}`; // p/[photoId] export const isPathPhoto = (pathname = '') => @@ -341,6 +365,14 @@ export const isPathLens = (pathname = '') => export const isPathLensPhoto = (pathname = '') => new RegExp(`^${PREFIX_LENS}/[^/]+/[^/]+/[^/]+/?$`).test(pathname); +// album/[album] +export const isPathAlbum = (pathname = '') => + new RegExp(`^${PREFIX_ALBUM}/[^/]+/?$`).test(pathname); + +// album/[album]/[photoId] +export const isPathAlbumPhoto = (pathname = '') => + new RegExp(`^${PREFIX_ALBUM}/[^/]+/[^/]+/?$`).test(pathname); + // tag/[tag] export const isPathTag = (pathname = '') => new RegExp(`^${PREFIX_TAG}/[^/]+/?$`).test(pathname); @@ -417,9 +449,12 @@ export const isPathProtected = (pathname?: string) => checkPathPrefix(pathname, pathForTag(TAG_PRIVATE)) || checkPathPrefix(pathname, PATH_OG); -export const getPathComponents = (pathname = ''): { +export const getPathComponents = ( + pathname = '', +): (Omit & { + album?: string photoId?: string -} & PhotoSetCategory => { +}) => { const photoIdFromPhoto = pathname.match( new RegExp(`^${PREFIX_PHOTO}/([^/]+)`))?.[1]; const photoIdFromCamera = pathname.match( @@ -438,6 +473,8 @@ export const getPathComponents = (pathname = ''): { new RegExp(`^${PREFIX_YEAR}/[^/]+/([^/]+)`))?.[1]; const photoIdFromRecents = pathname.match( new RegExp(`^${PREFIX_RECENTS}/([^/]+)`))?.[1]; + const album = pathname.match( + new RegExp(`^${PREFIX_ALBUM}/([^/]+)`))?.[1]; const tag = pathname.match( new RegExp(`^${PREFIX_TAG}/([^/]+)`))?.[1]; const film = pathname.match( @@ -464,6 +501,7 @@ export const getPathComponents = (pathname = ''): { photoIdFromYear || photoIdFromRecents ), + album, tag, camera, film, @@ -480,6 +518,7 @@ export const getEscapePath = (pathname?: string) => { year, camera, lens, + album, tag, recipe, film, @@ -506,6 +545,8 @@ export const getEscapePath = (pathname?: string) => { return pathForCamera(camera); } else if (lens && isPathLensPhoto(pathname)) { return pathForLens(lens); + } else if (album && isPathAlbumPhoto(pathname)) { + return pathForAlbum(album); } else if (tag && isPathTagPhoto(pathname)) { return pathForTag(tag); } else if (recipe && isPathRecipePhoto(pathname)) { diff --git a/src/app/static.ts b/src/app/static.ts index 4a20acd6..1f3fc365 100644 --- a/src/app/static.ts +++ b/src/app/static.ts @@ -8,8 +8,8 @@ import { STATICALLY_OPTIMIZED_PHOTO_OG_IMAGES, STATICALLY_OPTIMIZED_PHOTOS, } from '@/app/config'; -import { GENERATE_STATIC_PARAMS_LIMIT } from '@/photo/db'; -import { getPublicPhotoIds } from '@/photo/db/query'; +import { GENERATE_STATIC_PARAMS_LIMIT } from '@/db'; +import { getPublicPhotoIds } from '@/photo/query'; import { depluralize, pluralize } from '@/utility/string'; type StaticOutput = 'page' | 'image'; diff --git a/src/camera/CameraHeader.tsx b/src/camera/CameraHeader.tsx index 4736b62c..be20f8e4 100644 --- a/src/camera/CameraHeader.tsx +++ b/src/camera/CameraHeader.tsx @@ -1,4 +1,4 @@ -import { Photo, PhotoDateRange } from '@/photo'; +import { Photo, PhotoDateRangePostgres } from '@/photo'; import PhotoHeader from '@/photo/PhotoHeader'; import { Camera, cameraFromPhoto } from '.'; import PhotoCamera from './PhotoCamera'; @@ -19,7 +19,7 @@ export default async function CameraHeader({ selectedPhoto?: Photo indexNumber?: number count?: number - dateRange?: PhotoDateRange + dateRange?: PhotoDateRangePostgres }) { const appText = await getAppText(); const camera = cameraFromPhoto(photos[0], cameraProp); @@ -30,7 +30,7 @@ export default async function CameraHeader({ entity={} entityDescription={ descriptionForCameraPhotos( diff --git a/src/image-response/CameraImageResponse.tsx b/src/camera/CameraImageResponse.tsx similarity index 86% rename from src/image-response/CameraImageResponse.tsx rename to src/camera/CameraImageResponse.tsx index 6be2440b..95cb1f3a 100644 --- a/src/image-response/CameraImageResponse.tsx +++ b/src/camera/CameraImageResponse.tsx @@ -1,7 +1,7 @@ import { Photo } from '../photo'; -import ImageCaption from './components/ImageCaption'; -import ImagePhotoGrid from './components/ImagePhotoGrid'; -import ImageContainer from './components/ImageContainer'; +import ImageCaption from '@/image-response/components/ImageCaption'; +import ImagePhotoGrid from '@/image-response/components/ImagePhotoGrid'; +import ImageContainer from '@/image-response/components/ImageContainer'; import { Camera, cameraFromPhoto, diff --git a/src/camera/CameraOGTile.tsx b/src/camera/CameraOGTile.tsx index 239b2117..93e3b793 100644 --- a/src/camera/CameraOGTile.tsx +++ b/src/camera/CameraOGTile.tsx @@ -1,6 +1,6 @@ 'use client'; -import { Photo, PhotoDateRange } from '@/photo'; +import { Photo, PhotoDateRangePostgres } from '@/photo'; import { pathForCamera, pathForCameraImage } from '@/app/path'; import OGTile, { OGTilePropsCore } from '@/components/og/OGTile'; import { Camera } from '.'; @@ -17,7 +17,7 @@ export default function CameraOGTile({ camera: Camera photos: Photo[] count?: number - dateRange?: PhotoDateRange + dateRange?: PhotoDateRangePostgres } & OGTilePropsCore) { const appText = useAppText(); return ( diff --git a/src/camera/CameraOverview.tsx b/src/camera/CameraOverview.tsx index 2293996a..c807337f 100644 --- a/src/camera/CameraOverview.tsx +++ b/src/camera/CameraOverview.tsx @@ -1,4 +1,4 @@ -import { Photo, PhotoDateRange } from '@/photo'; +import { Photo, PhotoDateRangePostgres } from '@/photo'; import { Camera, createCameraKey } from '.'; import CameraHeader from './CameraHeader'; import PhotoGridContainer from '@/photo/PhotoGridContainer'; @@ -13,7 +13,7 @@ export default function CameraOverview({ camera: Camera, photos: Photo[], count: number, - dateRange?: PhotoDateRange, + dateRange?: PhotoDateRangePostgres, animateOnFirstLoadOnly?: boolean, }) { return ( diff --git a/src/camera/PhotoCamera.tsx b/src/camera/PhotoCamera.tsx index 8a6ec2bc..4fc08c61 100644 --- a/src/camera/PhotoCamera.tsx +++ b/src/camera/PhotoCamera.tsx @@ -8,6 +8,7 @@ import EntityLink, { } from '@/components/entity/EntityLink'; import IconCamera from '@/components/icons/IconCamera'; import { isCameraApple } from '@/platforms/apple'; +import useCategoryCounts from '@/category/useCategoryCounts'; export default function PhotoCamera({ camera, @@ -17,6 +18,8 @@ export default function PhotoCamera({ camera: Camera hideAppleIcon?: boolean } & EntityLinkExternalProps) { + const { getCameraCount } = useCategoryCounts(); + const isApple = isCameraApple(camera); const showAppleIcon = !hideAppleIcon && isApple; @@ -25,7 +28,7 @@ export default function PhotoCamera({ {...props} label={formatCameraText(camera)} path={pathForCamera(camera)} - hoverPhotoQueryOptions={{ camera }} + hoverQueryOptions={{ camera }} icon={showAppleIcon ? } + hoverCount={props.hoverCount ?? getCameraCount(camera)} /> ); } diff --git a/src/camera/meta.ts b/src/camera/meta.ts index 27cc9e56..5f3de57d 100644 --- a/src/camera/meta.ts +++ b/src/camera/meta.ts @@ -1,6 +1,6 @@ import { Photo, - PhotoDateRange, + PhotoDateRangePostgres, descriptionForPhotoSet, photoQuantityText, } from '@/photo'; @@ -41,7 +41,7 @@ export const descriptionForCameraPhotos = ( appText: AppTextState, dateBased?: boolean, explicitCount?: number, - explicitDateRange?: PhotoDateRange, + explicitDateRange?: PhotoDateRangePostgres, ) => descriptionForPhotoSet( photos, @@ -57,7 +57,7 @@ export const generateMetaForCamera = ( photos: Photo[], appText: AppTextState, explicitCount?: number, - explicitDateRange?: PhotoDateRange, + explicitDateRange?: PhotoDateRangePostgres, ) => ({ url: absolutePathForCamera(camera), title: titleForCamera(camera, photos, appText, explicitCount), diff --git a/src/category/data.ts b/src/category/data.ts index dc284227..38a2a03e 100644 --- a/src/category/data.ts +++ b/src/category/data.ts @@ -7,7 +7,7 @@ import { getUniqueRecipes, getUniqueTags, getUniqueYears, -} from '@/photo/db/query'; +} from '@/photo/query'; import { SHOW_FILMS, SHOW_FOCAL_LENGTHS, @@ -17,11 +17,13 @@ import { SHOW_TAGS, SHOW_YEARS, SHOW_RECENTS, + SHOW_ALBUMS, } from '@/app/config'; import { createLensKey } from '@/lens'; import { sortTagsByCount } from '@/tag'; import { sortCategoriesByCount } from '@/category'; import { sortFocalLengths } from '@/focal'; +import { getAlbumsWithMeta } from '@/album/query'; type CategoryData = Awaited>; @@ -34,6 +36,7 @@ export const NULL_CATEGORY_DATA: CategoryData = { recipes: [], films: [], focalLengths: [], + albums: [], }; export const getDataForCategories = () => Promise.all([ @@ -80,6 +83,10 @@ export const getDataForCategories = () => Promise.all([ .then(sortFocalLengths) .catch(() => []) : undefined, + SHOW_ALBUMS + ? getAlbumsWithMeta() + .catch(() => []) + : undefined, ]).then(([ recents = [], years = [], @@ -89,6 +96,7 @@ export const getDataForCategories = () => Promise.all([ recipes = [], films = [], focalLengths = [], + albums = [], ]) => ({ recents, years, @@ -98,6 +106,7 @@ export const getDataForCategories = () => Promise.all([ recipes, films, focalLengths, + albums, })); export const getCountsForCategories = async () => { @@ -106,6 +115,7 @@ export const getCountsForCategories = async () => { years, cameras, lenses, + albums, tags, recipes, films, @@ -120,6 +130,10 @@ export const getCountsForCategories = async () => { acc[year.year] = year.count; return acc; }, {} as Record), + albums: albums.reduce((acc, { album, count }) => { + acc[album.slug] = count; + return acc; + }, {} as Record), cameras: cameras.reduce((acc, camera) => { acc[camera.cameraKey] = camera.count; return acc; diff --git a/src/category/index.ts b/src/category/index.ts index d1818c79..1081fee9 100644 --- a/src/category/index.ts +++ b/src/category/index.ts @@ -1,4 +1,4 @@ -import { Photo, PhotoDateRange } from '../photo'; +import { Photo, PhotoDateRangePostgres } from '../photo'; import { Camera, Cameras } from '@/camera'; import { Films } from '@/film'; import { Lens, Lenses } from '@/lens'; @@ -6,14 +6,16 @@ import { Tags } from '@/tag'; import { FocalLengths } from '@/focal'; import { Recipes } from '@/recipe'; import { Recents } from '@/recents'; -import { Years } from '@/years'; +import { Years } from '@/year'; import { parseCommaSeparatedKeyString } from '@/utility/key'; +import { Album, Albums } from '@/album'; export const CATEGORY_KEYS = [ 'recents', 'years', 'cameras', 'lenses', + 'albums', 'tags', 'recipes', 'films', @@ -26,6 +28,7 @@ export type CategoryKeys = CategoryKey[]; export const DEFAULT_CATEGORY_KEYS: CategoryKeys = [ 'recents', + 'albums', 'tags', 'cameras', 'lenses', @@ -55,6 +58,7 @@ export interface PhotoSetCategory { year?: string camera?: Camera lens?: Lens + album?: Album tag?: string recipe?: string film?: string @@ -62,20 +66,21 @@ export interface PhotoSetCategory { } export interface PhotoSetCategories { + recents: Recents + years: Years cameras: Cameras lenses: Lenses + albums: Albums tags: Tags recipes: Recipes films: Films focalLengths: FocalLengths - years: Years - recents: Recents } export interface PhotoSetAttributes { photos: Photo[] count?: number - dateRange?: PhotoDateRange + dateRange?: PhotoDateRangePostgres } export const sortCategoryByCount = ( diff --git a/src/category/useCategoryCounts.ts b/src/category/useCategoryCounts.ts index 43abf15c..d6f65ee9 100644 --- a/src/category/useCategoryCounts.ts +++ b/src/category/useCategoryCounts.ts @@ -2,10 +2,18 @@ import { createCameraKey, Camera } from '@/camera'; import { createLensKey, Lens } from '@/lens'; import { useCallback } from 'react'; import { useAppState } from '@/app/AppState'; +import { Album } from '@/album'; export default function useCategoryCounts() { const { categoriesWithCounts } = useAppState(); + const recentsCount = categoriesWithCounts?.recents[0] ?? 0; + + const getYearsCount = useCallback((year: string) => { + const yearCounts = categoriesWithCounts?.years ?? {}; + return yearCounts[year]; + }, [categoriesWithCounts]); + const getCameraCount = useCallback((camera: Camera) => { const cameraCounts = categoriesWithCounts?.cameras ?? {}; return cameraCounts[createCameraKey(camera)]; @@ -16,6 +24,11 @@ export default function useCategoryCounts() { return lensCounts[createLensKey(lens)]; }, [categoriesWithCounts]); + const getAlbumCount = useCallback((album: Album) => { + const albumCounts = categoriesWithCounts?.albums ?? {}; + return albumCounts[album.slug]; + }, [categoriesWithCounts]); + const getTagCount = useCallback((tag: string) => { const tagCounts = categoriesWithCounts?.tags ?? {}; return tagCounts[tag]; @@ -37,8 +50,11 @@ export default function useCategoryCounts() { }, [categoriesWithCounts]); return { + recentsCount, + getYearsCount, getCameraCount, getLensCount, + getAlbumCount, getTagCount, getRecipeCount, getFilmCount, diff --git a/src/category/useCategoryCountsForPhoto.ts b/src/category/useCategoryCountsForPhoto.ts deleted file mode 100644 index b059ffdb..00000000 --- a/src/category/useCategoryCountsForPhoto.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Photo } from '@/photo'; -import useCategoryCounts from './useCategoryCounts'; -import { cameraFromPhoto } from '@/camera'; -import { lensFromPhoto } from '@/lens'; -import { useMemo } from 'react'; - -export default function useCategoryCountsForPhoto(photo: Photo) { - const { - getCameraCount, - getLensCount, - getTagCount, - getRecipeCount, - getFilmCount, - getFocalLengthCount, - } = useCategoryCounts(); - - const camera = cameraFromPhoto(photo); - const lens = lensFromPhoto(photo); - - const categoryCounts = useMemo(() => ({ - cameraCount: getCameraCount(camera), - lensCount: getLensCount(lens), - tagCounts: photo.tags.reduce((acc, tag) => { - acc[tag] = getTagCount(tag); - return acc; - }, {} as Record), - recipeCount: photo.recipeTitle ? getRecipeCount(photo.recipeTitle) : 0, - filmCount: photo.film ? getFilmCount(photo.film) : 0, - focalCount: photo.focalLength ? getFocalLengthCount(photo.focalLength) : 0, - }), [ - getCameraCount, - getLensCount, - getRecipeCount, - getFilmCount, - getFocalLengthCount, - getTagCount, - camera, - lens, - photo.tags, - photo.recipeTitle, - photo.film, - photo.focalLength, - ]); - - return categoryCounts; -} diff --git a/src/cmdk/CommandKClient.tsx b/src/cmdk/CommandKClient.tsx index e35ff653..75b80ad6 100644 --- a/src/cmdk/CommandKClient.tsx +++ b/src/cmdk/CommandKClient.tsx @@ -23,6 +23,7 @@ import { PATH_FULL_INFERRED, PATH_GRID_INFERRED, PATH_SIGN_IN, + pathForAlbum, pathForCamera, pathForFilm, pathForFocalLength, @@ -94,6 +95,7 @@ import IconCheck from '@/components/icons/IconCheck'; import { getSortStateFromPath } from '@/photo/sort/path'; import IconSort from '@/components/icons/IconSort'; import { useSelectPhotosState } from '@/admin/select/SelectPhotosState'; +import IconAlbum from '@/components/icons/IconAlbum'; const DIALOG_TITLE = 'Global Command-K Menu'; const DIALOG_DESCRIPTION = 'For searching photos, views, and settings'; @@ -140,6 +142,7 @@ export default function CommandKClient({ years: _years, cameras, lenses, + albums, tags: _tags, recipes, films, @@ -397,6 +400,16 @@ export default function CommandKClient({ path: pathForLens(lens), })), }; + case 'albums': return { + heading: appText.category.albumPlural, + accessory: , + items: albums.map(({ album, count }) => ({ + label: album.title, + annotation: formatCount(count), + annotationAria: formatCountDescriptive(count), + path: pathForAlbum(album), + })), + }; case 'tags': return { heading: appText.category.tagPlural, accessory: : type === 'textarea' ?