From 30b6e02b8394969690a37ee7e0344dcb031ad043 Mon Sep 17 00:00:00 2001 From: Jake Jarvis Date: Tue, 8 Apr 2025 09:38:44 -0400 Subject: [PATCH] backpedal a bit on caching --- app/api/hits/route.ts | 74 ++- app/contact/action.ts | 122 +++++ app/contact/form.tsx | 11 +- app/contact/page.tsx | 85 +--- app/contact/schema.ts | 43 -- app/feed.atom/route.ts | 3 +- app/feed.xml/route.ts | 3 +- app/manifest.ts | 2 + app/page.tsx | 36 +- app/sitemap.ts | 3 +- .../SkipToContent/SkipToContent.module.css | 4 +- components/SkipToContent/SkipToContent.tsx | 2 +- components/Tweet/Tweet.tsx | 2 +- lib/helpers/build-feed.ts | 5 +- lib/helpers/posts.ts | 108 ++--- package.json | 12 +- pnpm-lock.yaml | 437 +++++------------- 17 files changed, 373 insertions(+), 579 deletions(-) create mode 100644 app/contact/action.ts delete mode 100644 app/contact/schema.ts diff --git a/app/api/hits/route.ts b/app/api/hits/route.ts index 24ed3cde..8dc7e2b4 100644 --- a/app/api/hits/route.ts +++ b/app/api/hits/route.ts @@ -2,8 +2,9 @@ import { NextResponse } from "next/server"; import { unstable_cache as cache } from "next/cache"; import redis from "../../../lib/helpers/redis"; -export const GET = async (): Promise< - NextResponse<{ +// cache response from the db +const getData = cache( + async (): Promise<{ total: { hits: number; }; @@ -11,45 +12,42 @@ export const GET = async (): Promise< slug: string; hits: number; }>; - }> -> => { - const { total, pages } = await cache( - async () => { - // get all keys (aka slugs) - const slugs = await redis.scan(0, { - match: "hits:*", - type: "string", - // set an arbitrary yet generous upper limit, just in case... - count: 99, - }); + }> => { + // get all keys (aka slugs) + const slugs = await redis.scan(0, { + match: "hits:*", + type: "string", + // set an arbitrary yet generous upper limit, just in case... + count: 99, + }); - // get the value (number of hits) for each key (the slug of the page) - const values = await redis.mget(...slugs[1]); + // get the value (number of hits) for each key (the slug of the page) + const values = await redis.mget(...slugs[1]); - // pair the slugs with their hit values - const pages = slugs[1].map((slug, index) => ({ - slug: slug.split(":").pop() as string, // remove the "hits:" prefix - hits: parseInt(values[index], 10), - })); + // pair the slugs with their hit values + const pages = slugs[1].map((slug, index) => ({ + slug: slug.split(":").pop() as string, // remove the "hits:" prefix + hits: parseInt(values[index], 10), + })); - // sort descending by hits - pages.sort((a, b) => b.hits - a.hits); + // sort descending by hits + pages.sort((a, b) => b.hits - a.hits); - // calculate total hits - const total = { hits: 0 }; - pages.forEach((page) => { - // add these hits to running tally - total.hits += page.hits; - }); + // calculate total hits + const total = { hits: 0 }; + pages.forEach((page) => { + // add these hits to running tally + total.hits += page.hits; + }); - return { total, pages }; - }, - undefined, - { - revalidate: 1800, // 30 minutes - tags: ["hits"], - } - )(); + return { total, pages }; + }, + undefined, + { + revalidate: 900, // 15 minutes + tags: ["hits"], + } +); - return NextResponse.json({ total, pages }); -}; +export const GET = async (): Promise>>> => + NextResponse.json(await getData()); diff --git a/app/contact/action.ts b/app/contact/action.ts new file mode 100644 index 00000000..150a9165 --- /dev/null +++ b/app/contact/action.ts @@ -0,0 +1,122 @@ +"use server"; + +import { headers } from "next/headers"; +import * as v from "valibot"; +import { Resend } from "resend"; +import * as config from "../../lib/config"; + +const ContactSchema = v.object({ + // TODO: replace duplicate error messages with v.message() when released. see: + // https://valibot.dev/api/message/ + // https://github.com/fabian-hiller/valibot/blob/main/library/src/methods/message/message.ts + name: v.pipe(v.string("Your name is required."), v.trim(), v.nonEmpty("Your name is required.")), + email: v.pipe( + v.string("Your email address is required."), + v.trim(), + v.nonEmpty("Your email address is required."), + v.email("Invalid email address.") + ), + message: v.pipe( + v.string("A message is required."), + v.trim(), + v.nonEmpty("A message is required."), + v.minLength(10, "Your message must be at least 10 characters.") + ), + "cf-turnstile-response": v.pipe( + // token wasn't submitted at _all_, most likely a direct POST request by a spam bot + v.string("Shoo, bot."), + // form submitted properly but token was missing, might be a forgetful human + v.nonEmpty("Just do the stinkin CAPTCHA, human! 🤖"), + // very rudimentary length check based on Cloudflare's docs + // https://developers.cloudflare.com/turnstile/troubleshooting/testing/ + v.minLength("XXXX.DUMMY.TOKEN.XXXX".length), + // "A Turnstile token can have up to 2048 characters." + // https://developers.cloudflare.com/turnstile/get-started/server-side-validation/ + v.maxLength(2048), + v.readonly() + ), +}); + +export type ContactInput = v.InferInput; + +export type ContactState = { + success: boolean; + message: string; + errors?: v.FlatErrors["nested"]; +}; + +export const send = async (state: ContactState, payload: FormData): Promise => { + // TODO: remove after debugging why automated spam entries are causing 500 errors + console.debug("[contact form] received payload:", payload); + + if (!process.env.RESEND_API_KEY) { + throw new Error("[contact form] 'RESEND_API_KEY' is not set."); + } + + try { + const data = v.safeParse(ContactSchema, Object.fromEntries(payload)); + + if (!data.success) { + return { + success: false, + message: "Please make sure all fields are filled in.", + errors: v.flatten(data.issues).nested, + }; + } + + // try to get the client IP (for turnstile accuracy, not logging!) but no biggie if we can't + let remoteip; + try { + remoteip = (await headers()).get("x-forwarded-for"); + } catch {} // eslint-disable-line no-empty + + // validate captcha + const turnstileResponse = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + secret: process.env.TURNSTILE_SECRET_KEY || "1x0000000000000000000000000000000AA", + response: data.output["cf-turnstile-response"], + remoteip, + }), + cache: "no-store", + }); + + if (!turnstileResponse || !turnstileResponse.ok) { + throw new Error(`[contact form] turnstile validation failed: ${turnstileResponse.status}`); + } + + const turnstileData = (await turnstileResponse.json()) as { success: boolean }; + + if (!turnstileData.success) { + return { + success: false, + message: "Did you complete the CAPTCHA? (If you're human, that is...)", + }; + } + + if (!process.env.RESEND_FROM_EMAIL) { + // https://resend.com/docs/api-reference/emails/send-email + console.warn("[contact form] 'RESEND_FROM_EMAIL' is not set, falling back to onboarding@resend.dev."); + } + + // send email + const resend = new Resend(process.env.RESEND_API_KEY!); + await resend.emails.send({ + from: `${data.output.name} <${process.env.RESEND_FROM_EMAIL || "onboarding@resend.dev"}>`, + replyTo: `${data.output.name} <${data.output.email}>`, + to: [config.authorEmail], + subject: `[${config.siteName}] Contact Form Submission`, + text: data.output.message, + }); + + return { success: true, message: "Thanks! You should hear from me soon." }; + } catch (error) { + console.error("[contact form] fatal error:", error); + + return { + success: false, + message: "Internal server error. Please try again later or shoot me an email.", + }; + } +}; diff --git a/app/contact/form.tsx b/app/contact/form.tsx index b92dd867..7329656e 100644 --- a/app/contact/form.tsx +++ b/app/contact/form.tsx @@ -6,16 +6,13 @@ import Turnstile from "react-turnstile"; import clsx from "clsx"; import { CheckIcon, XIcon } from "lucide-react"; import Link from "../../components/Link"; -import type { ContactInput, ContactState } from "./schema"; + +import { send, type ContactState, type ContactInput } from "./action"; import styles from "./form.module.css"; -const ContactForm = ({ - serverAction, -}: { - serverAction: (state: ContactState, payload: FormData) => Promise; -}) => { - const [formState, formAction, pending] = useActionState(serverAction, { +const ContactForm = () => { + const [formState, formAction, pending] = useActionState(send, { success: false, message: "", }); diff --git a/app/contact/page.tsx b/app/contact/page.tsx index 1479dd31..d04ec94b 100644 --- a/app/contact/page.tsx +++ b/app/contact/page.tsx @@ -1,13 +1,8 @@ -import { headers } from "next/headers"; -import * as v from "valibot"; -import { Resend } from "resend"; import PageTitle from "../../components/PageTitle"; import Link from "../../components/Link"; import { addMetadata } from "../../lib/helpers/metadata"; -import * as config from "../../lib/config"; import ContactForm from "./form"; -import ContactSchema, { type ContactState } from "./schema"; export const metadata = addMetadata({ title: "Contact Me", @@ -17,84 +12,6 @@ export const metadata = addMetadata({ }, }); -const send = async (state: ContactState, payload: FormData): Promise => { - "use server"; - - // TODO: remove after debugging why automated spam entries are causing 500 errors - console.debug("[contact form] received payload:", payload); - - if (!process.env.RESEND_API_KEY) { - throw new Error("[contact form] 'RESEND_API_KEY' is not set."); - } - - try { - const data = v.safeParse(ContactSchema, Object.fromEntries(payload)); - - if (!data.success) { - return { - success: false, - message: "Please make sure all fields are filled in.", - errors: v.flatten(data.issues).nested, - }; - } - - // try to get the client IP (for turnstile accuracy, not logging!) but no biggie if we can't - let remoteip; - try { - remoteip = (await headers()).get("x-forwarded-for"); - } catch {} // eslint-disable-line no-empty - - // validate captcha - const turnstileResponse = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - secret: process.env.TURNSTILE_SECRET_KEY || "1x0000000000000000000000000000000AA", - response: data.output["cf-turnstile-response"], - remoteip, - }), - cache: "no-store", - }); - - if (!turnstileResponse || !turnstileResponse.ok) { - throw new Error(`[contact form] turnstile validation failed: ${turnstileResponse.status}`); - } - - const turnstileData = (await turnstileResponse.json()) as { success: boolean }; - - if (!turnstileData.success) { - return { - success: false, - message: "Did you complete the CAPTCHA? (If you're human, that is...)", - }; - } - - if (!process.env.RESEND_FROM_EMAIL) { - // https://resend.com/docs/api-reference/emails/send-email - console.warn("[contact form] 'RESEND_FROM_EMAIL' is not set, falling back to onboarding@resend.dev."); - } - - // send email - const resend = new Resend(process.env.RESEND_API_KEY!); - await resend.emails.send({ - from: `${data.output.name} <${process.env.RESEND_FROM_EMAIL || "onboarding@resend.dev"}>`, - replyTo: `${data.output.name} <${data.output.email}>`, - to: [config.authorEmail], - subject: `[${config.siteName}] Contact Form Submission`, - text: data.output.message, - }); - - return { success: true, message: "Thanks! You should hear from me soon." }; - } catch (error) { - console.error("[contact form] fatal error:", error); - - return { - success: false, - message: "Internal server error. Please try again later or shoot me an email.", - }; - } -}; - const Page = () => { return (
{ .

- +
); }; diff --git a/app/contact/schema.ts b/app/contact/schema.ts deleted file mode 100644 index 6c9d80ec..00000000 --- a/app/contact/schema.ts +++ /dev/null @@ -1,43 +0,0 @@ -import * as v from "valibot"; - -export const ContactSchema = v.object({ - // TODO: replace duplicate error messages with v.message() when released. see: - // https://valibot.dev/api/message/ - // https://github.com/fabian-hiller/valibot/blob/main/library/src/methods/message/message.ts - name: v.pipe(v.string("Your name is required."), v.trim(), v.nonEmpty("Your name is required.")), - email: v.pipe( - v.string("Your email address is required."), - v.trim(), - v.nonEmpty("Your email address is required."), - v.email("Invalid email address.") - ), - message: v.pipe( - v.string("A message is required."), - v.trim(), - v.nonEmpty("A message is required."), - v.minLength(10, "Your message must be at least 10 characters.") - ), - "cf-turnstile-response": v.pipe( - // token wasn't submitted at _all_, most likely a direct POST request by a spam bot - v.string("Shoo, bot."), - // form submitted properly but token was missing, might be a forgetful human - v.nonEmpty("Just do the stinkin CAPTCHA, human! 🤖"), - // very rudimentary length check based on Cloudflare's docs - // https://developers.cloudflare.com/turnstile/troubleshooting/testing/ - v.minLength("XXXX.DUMMY.TOKEN.XXXX".length), - // "A Turnstile token can have up to 2048 characters." - // https://developers.cloudflare.com/turnstile/get-started/server-side-validation/ - v.maxLength(2048), - v.readonly() - ), -}); - -export type ContactInput = v.InferInput; - -export type ContactState = { - success: boolean; - message: string; - errors?: v.FlatErrors["nested"]; -}; - -export default ContactSchema; diff --git a/app/feed.atom/route.ts b/app/feed.atom/route.ts index 6ef1bb29..a660fc29 100644 --- a/app/feed.atom/route.ts +++ b/app/feed.atom/route.ts @@ -1,3 +1,4 @@ +import { NextResponse } from "next/server"; import { buildFeed } from "../../lib/helpers/build-feed"; export const dynamic = "force-static"; @@ -5,7 +6,7 @@ export const dynamic = "force-static"; export const GET = async () => { const feed = await buildFeed(); - return new Response(feed.atom1(), { + return new NextResponse(feed.atom1(), { headers: { "content-type": "application/atom+xml; charset=utf-8", }, diff --git a/app/feed.xml/route.ts b/app/feed.xml/route.ts index 586f6291..341c9b45 100644 --- a/app/feed.xml/route.ts +++ b/app/feed.xml/route.ts @@ -1,3 +1,4 @@ +import { NextResponse } from "next/server"; import { buildFeed } from "../../lib/helpers/build-feed"; export const dynamic = "force-static"; @@ -5,7 +6,7 @@ export const dynamic = "force-static"; export const GET = async () => { const feed = await buildFeed(); - return new Response(feed.rss2(), { + return new NextResponse(feed.rss2(), { headers: { "content-type": "application/rss+xml; charset=utf-8", }, diff --git a/app/manifest.ts b/app/manifest.ts index e4071664..f8e1ec50 100644 --- a/app/manifest.ts +++ b/app/manifest.ts @@ -1,6 +1,8 @@ import * as config from "../lib/config"; import type { MetadataRoute } from "next"; +export const dynamic = "force-static"; + const manifest = (): MetadataRoute.Manifest => { return { name: config.siteName, diff --git a/app/page.tsx b/app/page.tsx index 5e3572bf..7d1229fa 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,3 +1,4 @@ +import clsx from "clsx"; import hash from "@emotion/hash"; import { rgba } from "polished"; import { LockIcon } from "lucide-react"; @@ -9,26 +10,35 @@ import styles from "./page.module.css"; const Link = ({ lightColor, darkColor, + className, children, ...rest }: ComponentPropsWithoutRef & { - lightColor: string; - darkColor: string; + lightColor?: string; + darkColor?: string; }) => { - const uniqueId = hash(`${lightColor},${darkColor}`); + if (lightColor && darkColor) { + const uniqueId = hash(`${lightColor},${darkColor}`); + + return ( + + {children} + + + + ); + } return ( - + {children} - - ); }; diff --git a/app/sitemap.ts b/app/sitemap.ts index a7a60174..499b2ba8 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -36,7 +36,8 @@ const sitemap = async (): Promise => { }); }); - (await getFrontMatter()).forEach((post) => { + const frontmatter = await getFrontMatter(); + frontmatter.forEach((post) => { routes.push({ url: post.permalink, // pull lastModified from front matter date diff --git a/components/SkipToContent/SkipToContent.module.css b/components/SkipToContent/SkipToContent.module.css index 3f39dbb1..4c0617f7 100644 --- a/components/SkipToContent/SkipToContent.module.css +++ b/components/SkipToContent/SkipToContent.module.css @@ -1,7 +1,7 @@ /* accessible invisibility stuff pulled from @reach/skip-nav: https://github.com/reach/reach-ui/blob/main/packages/skip-nav/styles.css */ -.skipNav { +.hidden { border: 0; clip: rect(0 0 0 0); height: 1px; @@ -12,7 +12,7 @@ https://github.com/reach/reach-ui/blob/main/packages/skip-nav/styles.css */ position: absolute; } -.skipNav:focus { +.hidden:focus { padding: 1rem; position: fixed; top: 10px; diff --git a/components/SkipToContent/SkipToContent.tsx b/components/SkipToContent/SkipToContent.tsx index e8673590..f647b019 100644 --- a/components/SkipToContent/SkipToContent.tsx +++ b/components/SkipToContent/SkipToContent.tsx @@ -4,7 +4,7 @@ const skipNavId = "skip-nav"; export const SkipToContentLink = () => { return ( - + Skip to content ); diff --git a/components/Tweet/Tweet.tsx b/components/Tweet/Tweet.tsx index 1f084825..86870407 100644 --- a/components/Tweet/Tweet.tsx +++ b/components/Tweet/Tweet.tsx @@ -12,7 +12,7 @@ export type TweetProps = Omit, "t className?: string; }; -const fetchTweet = cache(async (id: string) => _fetchTweet(id), undefined, { +const fetchTweet = cache(_fetchTweet, undefined, { revalidate: false, // cache indefinitely tags: ["tweet"], }); diff --git a/lib/helpers/build-feed.ts b/lib/helpers/build-feed.ts index 37213c3a..aa5af46e 100644 --- a/lib/helpers/build-feed.ts +++ b/lib/helpers/build-feed.ts @@ -1,4 +1,3 @@ -import { cache } from "react"; import { Feed } from "feed"; import { getFrontMatter, getContent } from "./posts"; import * as config from "../config"; @@ -11,7 +10,7 @@ import ogImage from "../../app/opengraph-image.jpg"; * Returns a `Feed` object, which can then be processed with `feed.rss2()`, `feed.atom1()`, or `feed.json1()`. * @see https://github.com/jpmonette/feed#example */ -export const buildFeed = cache(async (): Promise => { +export const buildFeed = async (): Promise => { const feed = new Feed({ id: BASE_URL, link: BASE_URL, @@ -62,4 +61,4 @@ export const buildFeed = cache(async (): Promise => { }); return feed; -}); +}; diff --git a/lib/helpers/posts.ts b/lib/helpers/posts.ts index 1b980388..7eccb521 100644 --- a/lib/helpers/posts.ts +++ b/lib/helpers/posts.ts @@ -1,4 +1,3 @@ -import { cache } from "react"; import path from "path"; import fs from "fs/promises"; import glob from "fast-glob"; @@ -20,7 +19,7 @@ export type FrontMatter = { }; /** Use filesystem to get a simple list of all post slugs */ -export const getSlugs = cache(async (): Promise => { +export const getSlugs = async (): Promise => { // list all .mdx files in POSTS_DIR const mdxFiles = await glob("*/index.mdx", { cwd: path.join(process.cwd(), POSTS_DIR), @@ -31,7 +30,7 @@ export const getSlugs = cache(async (): Promise => { const slugs = mdxFiles.map((fileName) => fileName.replace(/\/index\.mdx$/, "")); return slugs; -}); +}; // overloaded to return either the front matter of a single post or ALL posts export const getFrontMatter: { @@ -43,64 +42,65 @@ export const getFrontMatter: { * Parses and returns the front matter of a given slug, or undefined if the slug does not exist */ (slug: string): Promise; -} = cache( +} = async ( // eslint-disable-next-line @typescript-eslint/no-explicit-any - async (slug?: any): Promise => { - if (typeof slug === "string") { - try { - const { frontmatter } = await import(`../../${POSTS_DIR}/${slug}/index.mdx`); + slug?: any +): // eslint-disable-next-line @typescript-eslint/no-explicit-any +Promise => { + if (typeof slug === "string") { + try { + const { frontmatter } = await import(`../../${POSTS_DIR}/${slug}/index.mdx`); - // process markdown title to html... - const htmlTitle = await unified() - .use(remarkParse) - .use(remarkSmartypants) - .use(remarkHtml, { - sanitize: { - // allow *very* limited markdown to be used in post titles - tagNames: ["code", "em", "strong"], - }, - }) - .process(frontmatter.title) - .then((result) => result.toString().trim()); + // process markdown title to html... + const htmlTitle = await unified() + .use(remarkParse) + .use(remarkSmartypants) + .use(remarkHtml, { + sanitize: { + // allow *very* limited markdown to be used in post titles + tagNames: ["code", "em", "strong"], + }, + }) + .process(frontmatter.title) + .then((result) => result.toString().trim()); - // ...and then (sketchily) remove said html for a plaintext version: - // https://css-tricks.com/snippets/javascript/strip-html-tags-in-javascript/ - const title = decode(htmlTitle.replace(/<[^>]*>/g, "")); + // ...and then (sketchily) remove said html for a plaintext version: + // https://css-tricks.com/snippets/javascript/strip-html-tags-in-javascript/ + const title = decode(htmlTitle.replace(/<[^>]*>/g, "")); - return { - ...(frontmatter as Partial), - // plain title without html or markdown syntax: - title, - // stylized title with limited html tags: - htmlTitle, - slug, - // validate/normalize the date string provided from front matter - date: new Date(frontmatter.date).toISOString(), - permalink: `${BASE_URL}/${POSTS_DIR}/${slug}`, - } as FrontMatter; - } catch (error) { - console.error(`Failed to load front matter for post with slug "${slug}":`, error); - return undefined; - } + return { + ...(frontmatter as Partial), + // plain title without html or markdown syntax: + title, + // stylized title with limited html tags: + htmlTitle, + slug, + // validate/normalize the date string provided from front matter + date: new Date(frontmatter.date).toISOString(), + permalink: `${BASE_URL}/${POSTS_DIR}/${slug}`, + } as FrontMatter; + } catch (error) { + console.error(`Failed to load front matter for post with slug "${slug}":`, error); + return undefined; } - - if (!slug) { - // concurrently fetch the front matter of each post - const slugs = await getSlugs(); - const posts = await Promise.all(slugs.map(getFrontMatter)); - - // sort the results reverse chronologically and return - return posts.sort( - (post1, post2) => new Date(post2!.date).getTime() - new Date(post1!.date).getTime() - ) as FrontMatter[]; - } - - throw new Error(`getFrontMatter() called with invalid argument.`); } -); + + if (!slug) { + // concurrently fetch the front matter of each post + const slugs = await getSlugs(); + const posts = await Promise.all(slugs.map(getFrontMatter)); + + // sort the results reverse chronologically and return + return posts.sort( + (post1, post2) => new Date(post2!.date).getTime() - new Date(post1!.date).getTime() + ) as FrontMatter[]; + } + + throw new Error(`getFrontMatter() called with invalid argument.`); +}; /** Returns the content of a post with very limited processing to include in RSS feeds */ -export const getContent = cache(async (slug: string): Promise => { +export const getContent = async (slug: string): Promise => { try { // TODO: also remove MDX-related syntax (e.g. import/export statements) const content = await unified() @@ -138,4 +138,4 @@ export const getContent = cache(async (slug: string): Promise=20.x" }, - "packageManager": "pnpm@10.7.1+sha512.2d92c86b7928dc8284f53494fb4201f983da65f0fb4f0d40baafa5cf628fa31dae3e5968f12466f17df7e97310e30f343a648baea1b9b350685dafafffdf5808", + "packageManager": "pnpm@10.8.0+sha512.0e82714d1b5b43c74610193cb20734897c1d00de89d0e18420aebc5977fa13d780a9cb05734624e81ebd81cc876cd464794850641c48b9544326b5622ca29971", "cacheDirectories": [ "node_modules", ".next/cache" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 64d6d99b..daeaeb0a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,11 +27,11 @@ importers: specifier: ^3.1.0 version: 3.1.0(@types/react@19.1.0)(react@19.1.0) '@next/bundle-analyzer': - specifier: 15.3.0-canary.41 - version: 15.3.0-canary.41 + specifier: 15.3.0-canary.43 + version: 15.3.0-canary.43 '@next/mdx': - specifier: 15.3.0-canary.41 - version: 15.3.0-canary.41(@mdx-js/loader@3.1.0(acorn@8.14.1))(@mdx-js/react@3.1.0(@types/react@19.1.0)(react@19.1.0)) + specifier: 15.3.0-canary.43 + version: 15.3.0-canary.43(@mdx-js/loader@3.1.0(acorn@8.14.1))(@mdx-js/react@3.1.0(@types/react@19.1.0)(react@19.1.0)) '@octokit/graphql': specifier: ^8.2.1 version: 8.2.1 @@ -58,7 +58,7 @@ importers: version: 4.2.2 geist: specifier: ^1.3.1 - version: 1.3.1(next@15.3.0-canary.41(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-e993439-20250405)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) + version: 1.3.1(next@15.3.0-canary.43(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-e993439-20250405)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) html-entities: specifier: ^2.6.0 version: 2.6.0 @@ -69,8 +69,8 @@ importers: specifier: ^3.0.1 version: 3.0.1 next: - specifier: 15.3.0-canary.41 - version: 15.3.0-canary.41(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-e993439-20250405)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + specifier: 15.3.0-canary.43 + version: 15.3.0-canary.43(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-e993439-20250405)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) polished: specifier: ^4.3.1 version: 4.3.1 @@ -193,8 +193,8 @@ importers: specifier: ^9.24.0 version: 9.24.0 eslint-config-next: - specifier: 15.3.0-canary.41 - version: 15.3.0-canary.41(eslint@9.24.0)(typescript@5.8.3) + specifier: 15.3.0-canary.43 + version: 15.3.0-canary.43(eslint@9.24.0)(typescript@5.8.3) eslint-config-prettier: specifier: ^10.1.1 version: 10.1.1(eslint@9.24.0) @@ -251,8 +251,8 @@ importers: version: 5.8.3 optionalDependencies: sharp: - specifier: ^0.34.0 - version: 0.34.0 + specifier: ^0.34.1 + version: 0.34.1 packages: @@ -482,65 +482,33 @@ packages: resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} engines: {node: '>=18.18'} - '@img/sharp-darwin-arm64@0.33.5': - resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + '@img/sharp-darwin-arm64@0.34.1': + resolution: {integrity: sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [darwin] - '@img/sharp-darwin-arm64@0.34.0': - resolution: {integrity: sha512-BLT8CQ234EOJFN4NCAkZUkJr2lyXavD+aQH/Jc2skPqAJTMjKeH2BUulaZNkd4MJ9hcCicTdupcbCRg4bto0Ow==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [darwin] - - '@img/sharp-darwin-x64@0.33.5': - resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + '@img/sharp-darwin-x64@0.34.1': + resolution: {integrity: sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] - '@img/sharp-darwin-x64@0.34.0': - resolution: {integrity: sha512-FZLxjWEtz+QbxZbtFb+f6AbD47/M9k6GuZ9dedTFdsgI9HwUMvyinxFMAeyP1fJZkJBw999Ht5Cus4sqpFlBPg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-darwin-arm64@1.0.4': - resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} - cpu: [arm64] - os: [darwin] - '@img/sharp-libvips-darwin-arm64@1.1.0': resolution: {integrity: sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==} cpu: [arm64] os: [darwin] - '@img/sharp-libvips-darwin-x64@1.0.4': - resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} - cpu: [x64] - os: [darwin] - '@img/sharp-libvips-darwin-x64@1.1.0': resolution: {integrity: sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==} cpu: [x64] os: [darwin] - '@img/sharp-libvips-linux-arm64@1.0.4': - resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} - cpu: [arm64] - os: [linux] - '@img/sharp-libvips-linux-arm64@1.1.0': resolution: {integrity: sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linux-arm@1.0.5': - resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} - cpu: [arm] - os: [linux] - '@img/sharp-libvips-linux-arm@1.1.0': resolution: {integrity: sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==} cpu: [arm] @@ -551,148 +519,75 @@ packages: cpu: [ppc64] os: [linux] - '@img/sharp-libvips-linux-s390x@1.0.4': - resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} - cpu: [s390x] - os: [linux] - '@img/sharp-libvips-linux-s390x@1.1.0': resolution: {integrity: sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==} cpu: [s390x] os: [linux] - '@img/sharp-libvips-linux-x64@1.0.4': - resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} - cpu: [x64] - os: [linux] - '@img/sharp-libvips-linux-x64@1.1.0': resolution: {integrity: sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==} cpu: [x64] os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': - resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} - cpu: [arm64] - os: [linux] - '@img/sharp-libvips-linuxmusl-arm64@1.1.0': resolution: {integrity: sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==} cpu: [arm64] os: [linux] - '@img/sharp-libvips-linuxmusl-x64@1.0.4': - resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} - cpu: [x64] - os: [linux] - '@img/sharp-libvips-linuxmusl-x64@1.1.0': resolution: {integrity: sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==} cpu: [x64] os: [linux] - '@img/sharp-linux-arm64@0.33.5': - resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + '@img/sharp-linux-arm64@0.34.1': + resolution: {integrity: sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linux-arm64@0.34.0': - resolution: {integrity: sha512-fpvIy7rPdTegqthhUNAaQikg8CzNUGxuf7VTIs5HEQllCTL322rBDuGHVoH/pZ6Qms9enHe++DsUoG/Ux93E1A==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - - '@img/sharp-linux-arm@0.33.5': - resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + '@img/sharp-linux-arm@0.34.1': + resolution: {integrity: sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] - '@img/sharp-linux-arm@0.34.0': - resolution: {integrity: sha512-MfbqXi4zdy0CsSONwESFzrdpzcNSN66qbt8a7CdesOFfZHmlPXgC+xOy+2SLYn6+MFi/06qngGRIje7vfAV/5Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm] - os: [linux] - - '@img/sharp-linux-s390x@0.33.5': - resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + '@img/sharp-linux-s390x@0.34.1': + resolution: {integrity: sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] - '@img/sharp-linux-s390x@0.34.0': - resolution: {integrity: sha512-04jdT+VCZIqj0RoTEpWXh0lErZC9prhkxEZWrQdGt1MZ368SlvXpKkXCD4Ww5ISc3LexBmfnAW/+ErUmD9sRPQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [s390x] - os: [linux] - - '@img/sharp-linux-x64@0.33.5': - resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + '@img/sharp-linux-x64@0.34.1': + resolution: {integrity: sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-linux-x64@0.34.0': - resolution: {integrity: sha512-Y98V1d5vh8RIpf+pUb7U9a0SGzfPa7x7KPXsqtvb7i52L7HXAMv5U0aaOdnnf/CAqVUVaTJajINJ3KyrLcwByQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - - '@img/sharp-linuxmusl-arm64@0.33.5': - resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + '@img/sharp-linuxmusl-arm64@0.34.1': + resolution: {integrity: sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] - '@img/sharp-linuxmusl-arm64@0.34.0': - resolution: {integrity: sha512-pmsehGlQIOlAQ8lgtDxpGInXXMAV6JrFwoJ0Ib9PpsVYuwFM+Soa9mVZMfsTO+u9dBhCMEn2AP3mRUljgpGYvQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - - '@img/sharp-linuxmusl-x64@0.33.5': - resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + '@img/sharp-linuxmusl-x64@0.34.1': + resolution: {integrity: sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] - '@img/sharp-linuxmusl-x64@0.34.0': - resolution: {integrity: sha512-t80LMHorxyKGIPWIX3Qyamg72vj/TGYLyOvzjvkywvNmlQurgHu3ZI2aZnUc5YQlrKPOovnwkVmTEbH+YllQ5Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - - '@img/sharp-wasm32@0.33.5': - resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + '@img/sharp-wasm32@0.34.1': + resolution: {integrity: sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [wasm32] - '@img/sharp-wasm32@0.34.0': - resolution: {integrity: sha512-oI6xsOqLHhRA3LSZb07KW3dMAmo1PpyAxwdHkuiC5+N8HzodpqXusOtzBEXKeFG8Za5ycry0xLYsu7hG5aUxoQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [wasm32] - - '@img/sharp-win32-ia32@0.33.5': - resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + '@img/sharp-win32-ia32@0.34.1': + resolution: {integrity: sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] - '@img/sharp-win32-ia32@0.34.0': - resolution: {integrity: sha512-ofcDYsjJJ1zya9s/GCnXjbFIhTw5/gRVr+SivAGPMXmAml/rLLyDu/HtWntvhiacnL4VYvtgMFw/B2Zz/kgoWQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ia32] - os: [win32] - - '@img/sharp-win32-x64@0.33.5': - resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [win32] - - '@img/sharp-win32-x64@0.34.0': - resolution: {integrity: sha512-S0X+Uty7Qe6tBfTigFEInchNsGYM/uRjuF1ixi8mLubMfTClmbnVIMxR2/cD5I5Z1m6lHP5D6ASneM3qsF3KFA==} + '@img/sharp-win32-x64@0.34.1': + resolution: {integrity: sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [win32] @@ -753,17 +648,17 @@ packages: '@napi-rs/wasm-runtime@0.2.8': resolution: {integrity: sha512-OBlgKdX7gin7OIq4fadsjpg+cp2ZphvAIKucHsNfTdJiqdOmOEwQd/bHi0VwNrcw5xpBJyUw6cK/QilCqy1BSg==} - '@next/bundle-analyzer@15.3.0-canary.41': - resolution: {integrity: sha512-fdxfhf38ShtOI8Wz4h77HwA4HxvcblVrXM1/VzkexE6IZCBalB2rWZsldS30TAGCkXGCZAThHkvTTPuvvbqDcw==} + '@next/bundle-analyzer@15.3.0-canary.43': + resolution: {integrity: sha512-SxNOtoAIDPjbOVkg3BM9br6wP5LkSAox6crIxKjuPBjLTDTOH9crPdy0iCP2gDInbuVfc2XTKOvHEHub40Mblw==} - '@next/env@15.3.0-canary.41': - resolution: {integrity: sha512-M+gqSE/plVT1JEsgyyrstLLWtF1PY0CP80dRxP4qRd7LvI8qJYTLJrFuLojt/WQTdTaCHOOg0F1cmZld+QuT9A==} + '@next/env@15.3.0-canary.43': + resolution: {integrity: sha512-ogryUGufxhtidxr/42NY6+ADlvxt9Hq6Q/DOMS/20vGuePZ70wO6Z5m1RP3Q397RGgpe1gSWyYdlg+Mt7H4KeQ==} - '@next/eslint-plugin-next@15.3.0-canary.41': - resolution: {integrity: sha512-roDi0s4nmZ/nQh5BFtPXraFwyzOGZVbZ+1KKxAGjfxes01FRQ9TS0DaUKT4+/So9X8gT55ZXkVdTclcLqZjnHQ==} + '@next/eslint-plugin-next@15.3.0-canary.43': + resolution: {integrity: sha512-q3SklwAtay8QH7mpgkpgukFqffUx+TRHN2CMy9yN96MUbJEWXn0DjMjHXeaW2LF+UnYsq7mfTRKT+/S74t+Qtg==} - '@next/mdx@15.3.0-canary.41': - resolution: {integrity: sha512-a6b476AsqnXCojAmhG/O1y+4DoZt8l/hwFmbgrfH2jag3csdZ7ekquDpfzSH5v1yAHgiAyDlFCWCEsR+cbZCmw==} + '@next/mdx@15.3.0-canary.43': + resolution: {integrity: sha512-dxkxJdvRrWc6gjFUIk+YYUlnO/ONZP1/FVTvb2Y6AlDK7ThPy3PheNmHUQTrhzh7bMq5QFGINC7dVpEHnYyg0A==} peerDependencies: '@mdx-js/loader': '>=0.15.0' '@mdx-js/react': '>=0.15.0' @@ -773,50 +668,50 @@ packages: '@mdx-js/react': optional: true - '@next/swc-darwin-arm64@15.3.0-canary.41': - resolution: {integrity: sha512-p6soiDBtZTzkvhHmmKqag2xo26N77yZvvACmULieLBOqmgz4kbH7GoVW7OetRJcJ4J+3tyWNkJTX17Y2/SwZlg==} + '@next/swc-darwin-arm64@15.3.0-canary.43': + resolution: {integrity: sha512-eXrs8CyIBgdnMTNjku+h/xm61gmRCcKAT+tM2CjpEXbEqXBux5hRIakOk5kJJDu2fA2P3pzQGt5PRD1hg4srXA==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@15.3.0-canary.41': - resolution: {integrity: sha512-6PwzOqeJn0D8QHB/gJAZ4Ypl9RDMcaqDIKsvWdojXSYVplVxJ9bRrfl+k3MQLV1jBeUSx2IRFRng00/z3iBaxw==} + '@next/swc-darwin-x64@15.3.0-canary.43': + resolution: {integrity: sha512-iRGvblEh/b2grxkkp9pT+yea9EzGNM4tLyUZoCzkejkU2jMLsn2DH6h3bQwCfEYZL3YFGsYmVISrVCOVi8LeMw==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@15.3.0-canary.41': - resolution: {integrity: sha512-Q02F1gYR6jAzbbJRKeCBfGVKqFs3kKWS8PxEOYGCY4sROmFrrtel2fmLiKbdUOWpZ7Uhrij+fqzFXS413W9xhQ==} + '@next/swc-linux-arm64-gnu@15.3.0-canary.43': + resolution: {integrity: sha512-IqknHGNxpL03uIutIuv7FPjGHuD/AnJVC5exi5g+C7P3f6JVvOjFLS264eqi91tVCXhN2LpcKNGwTlK81bJVVg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@15.3.0-canary.41': - resolution: {integrity: sha512-XLsAUQyUGszbbGAoJMiMLeJKUSRfLb7PGqrguVBYDX6QQph7siE2be78DvU0nh1CtWJrCyLeyDCLUw7UDdLCUg==} + '@next/swc-linux-arm64-musl@15.3.0-canary.43': + resolution: {integrity: sha512-FbO0dnFsNe3f1LWTn4vyXTWTSrZdwNRnURYExSQ+0AINHphNfwKQNrPqVLrapQ9CAOCOz8R5p9Kf1++IsH3JJQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@15.3.0-canary.41': - resolution: {integrity: sha512-4peIAKvJQh633m6vMA6Y+PSuRO3LoW41KKF7MgOgdbsKSXFzY4GAMmItrubMke4FvCyO7raI0Dv0gJjER6dogA==} + '@next/swc-linux-x64-gnu@15.3.0-canary.43': + resolution: {integrity: sha512-MAaLEm8eO6Xir3YC3xLYDHDjLGogAAGRrxhuflvaqAtGQZ6NIMG4YjvAyramYTq/SwrUIDobggKxdQLtu8/pPQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@15.3.0-canary.41': - resolution: {integrity: sha512-HLq8CkS66WKr96cijy7RhHkuJxRbS7I0oVcbZmL00Lr0t/kO6OPyGRlazjyMQEQgcEsny/4LAvYZL/ZzZMTltg==} + '@next/swc-linux-x64-musl@15.3.0-canary.43': + resolution: {integrity: sha512-gdwF79/EQjY3zgcolO0jlDe0yfII9tXyXQeqL+uvzA8gZT5FpH0KkwSWzxj8EUswWzZcprbDa87sq8H0Eo+whw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@15.3.0-canary.41': - resolution: {integrity: sha512-gcY2HlPBuqLGXZ6lEmn0e/T5ae5XUJLP9Rn1UHcuze/RXpVTMEL5VkJw2LOj4VhJpcKs1G5zm7GzfwrjtDd/zw==} + '@next/swc-win32-arm64-msvc@15.3.0-canary.43': + resolution: {integrity: sha512-5WYne3jvo1478kUfe901wFxvPMdC8tRKundKIgU5Upe1HafMMS7ymm1hQ7CUpp3/1vY/R1TV1oKHHJfqDubiNg==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@15.3.0-canary.41': - resolution: {integrity: sha512-Ay4afan1UBLu3QjWEZeC2bTpIXLbQDrSsZykRBcpGaQUq113OtM5pMhmPni1qLBtXTIHdLVTgeJjh4duKfZICQ==} + '@next/swc-win32-x64-msvc@15.3.0-canary.43': + resolution: {integrity: sha512-xE3WZhwjb91eezecVsmXn/OtdISfMsIfS3t0ZXsS/+bMvO/LZLdcVBtl0Zy5yR+XJyKfXXmwpdYbL6WH4dGuQg==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1629,8 +1524,8 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} - eslint-config-next@15.3.0-canary.41: - resolution: {integrity: sha512-c+2+MtxW3iGxbpAzINdKGjrEtOQCO4tVF3bjYfzwqIPz8eqrlkroFn4ok1B04Dz9H03jvrmYdbMKXfmpyX4vzg==} + eslint-config-next@15.3.0-canary.43: + resolution: {integrity: sha512-RL8kWM1EH+5/Yg31u6HwMJOjxxYiStcO9KtgQ2e4ajKqeE3xfNbZ20BLBW7uQgzSYHpU1wXiXaYqUqo7NUbRhw==} peerDependencies: eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 typescript: '>=3.3.1' @@ -2724,8 +2619,8 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - next@15.3.0-canary.41: - resolution: {integrity: sha512-jQANaTx8vXRyMUlwOlv4nB2t6rWvQ5K/mXE/lcm4GY1NOcMdOODZEsRFuxr2F/DrUHSGwnHl/RXu+2RKFfNulQ==} + next@15.3.0-canary.43: + resolution: {integrity: sha512-am6xpZIx2P0VJ26N7K2CImmznYUP65XS0e0nkYtypWf/RiMsScwmCqrA4qrEK9u/tiPlA+583IcQPos9yKLg1Q==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: @@ -3272,12 +3167,8 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} - sharp@0.33.5: - resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - - sharp@0.34.0: - resolution: {integrity: sha512-l7K33wCojhluT82RQXKm3X/y9Y6yBioJ4GaOlGT67yDv8bXZcU3aOlxUM0W1zUUKQjOoIh3VcfQEKHVW9AyijQ==} + sharp@0.34.1: + resolution: {integrity: sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} shebang-command@2.0.0: @@ -4090,157 +3981,82 @@ snapshots: '@humanwhocodes/retry@0.4.2': {} - '@img/sharp-darwin-arm64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.0.4 - optional: true - - '@img/sharp-darwin-arm64@0.34.0': + '@img/sharp-darwin-arm64@0.34.1': optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.1.0 optional: true - '@img/sharp-darwin-x64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.0.4 - optional: true - - '@img/sharp-darwin-x64@0.34.0': + '@img/sharp-darwin-x64@0.34.1': optionalDependencies: '@img/sharp-libvips-darwin-x64': 1.1.0 optional: true - '@img/sharp-libvips-darwin-arm64@1.0.4': - optional: true - '@img/sharp-libvips-darwin-arm64@1.1.0': optional: true - '@img/sharp-libvips-darwin-x64@1.0.4': - optional: true - '@img/sharp-libvips-darwin-x64@1.1.0': optional: true - '@img/sharp-libvips-linux-arm64@1.0.4': - optional: true - '@img/sharp-libvips-linux-arm64@1.1.0': optional: true - '@img/sharp-libvips-linux-arm@1.0.5': - optional: true - '@img/sharp-libvips-linux-arm@1.1.0': optional: true '@img/sharp-libvips-linux-ppc64@1.1.0': optional: true - '@img/sharp-libvips-linux-s390x@1.0.4': - optional: true - '@img/sharp-libvips-linux-s390x@1.1.0': optional: true - '@img/sharp-libvips-linux-x64@1.0.4': - optional: true - '@img/sharp-libvips-linux-x64@1.1.0': optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': - optional: true - '@img/sharp-libvips-linuxmusl-arm64@1.1.0': optional: true - '@img/sharp-libvips-linuxmusl-x64@1.0.4': - optional: true - '@img/sharp-libvips-linuxmusl-x64@1.1.0': optional: true - '@img/sharp-linux-arm64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.0.4 - optional: true - - '@img/sharp-linux-arm64@0.34.0': + '@img/sharp-linux-arm64@0.34.1': optionalDependencies: '@img/sharp-libvips-linux-arm64': 1.1.0 optional: true - '@img/sharp-linux-arm@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.0.5 - optional: true - - '@img/sharp-linux-arm@0.34.0': + '@img/sharp-linux-arm@0.34.1': optionalDependencies: '@img/sharp-libvips-linux-arm': 1.1.0 optional: true - '@img/sharp-linux-s390x@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.0.4 - optional: true - - '@img/sharp-linux-s390x@0.34.0': + '@img/sharp-linux-s390x@0.34.1': optionalDependencies: '@img/sharp-libvips-linux-s390x': 1.1.0 optional: true - '@img/sharp-linux-x64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.0.4 - optional: true - - '@img/sharp-linux-x64@0.34.0': + '@img/sharp-linux-x64@0.34.1': optionalDependencies: '@img/sharp-libvips-linux-x64': 1.1.0 optional: true - '@img/sharp-linuxmusl-arm64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 - optional: true - - '@img/sharp-linuxmusl-arm64@0.34.0': + '@img/sharp-linuxmusl-arm64@0.34.1': optionalDependencies: '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 optional: true - '@img/sharp-linuxmusl-x64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 - optional: true - - '@img/sharp-linuxmusl-x64@0.34.0': + '@img/sharp-linuxmusl-x64@0.34.1': optionalDependencies: '@img/sharp-libvips-linuxmusl-x64': 1.1.0 optional: true - '@img/sharp-wasm32@0.33.5': + '@img/sharp-wasm32@0.34.1': dependencies: '@emnapi/runtime': 1.4.0 optional: true - '@img/sharp-wasm32@0.34.0': - dependencies: - '@emnapi/runtime': 1.4.0 + '@img/sharp-win32-ia32@0.34.1': optional: true - '@img/sharp-win32-ia32@0.33.5': - optional: true - - '@img/sharp-win32-ia32@0.34.0': - optional: true - - '@img/sharp-win32-x64@0.33.5': - optional: true - - '@img/sharp-win32-x64@0.34.0': + '@img/sharp-win32-x64@0.34.1': optional: true '@isaacs/cliui@8.0.2': @@ -4334,48 +4150,48 @@ snapshots: '@tybys/wasm-util': 0.9.0 optional: true - '@next/bundle-analyzer@15.3.0-canary.41': + '@next/bundle-analyzer@15.3.0-canary.43': dependencies: webpack-bundle-analyzer: 4.10.1 transitivePeerDependencies: - bufferutil - utf-8-validate - '@next/env@15.3.0-canary.41': {} + '@next/env@15.3.0-canary.43': {} - '@next/eslint-plugin-next@15.3.0-canary.41': + '@next/eslint-plugin-next@15.3.0-canary.43': dependencies: fast-glob: 3.3.1 - '@next/mdx@15.3.0-canary.41(@mdx-js/loader@3.1.0(acorn@8.14.1))(@mdx-js/react@3.1.0(@types/react@19.1.0)(react@19.1.0))': + '@next/mdx@15.3.0-canary.43(@mdx-js/loader@3.1.0(acorn@8.14.1))(@mdx-js/react@3.1.0(@types/react@19.1.0)(react@19.1.0))': dependencies: source-map: 0.7.4 optionalDependencies: '@mdx-js/loader': 3.1.0(acorn@8.14.1) '@mdx-js/react': 3.1.0(@types/react@19.1.0)(react@19.1.0) - '@next/swc-darwin-arm64@15.3.0-canary.41': + '@next/swc-darwin-arm64@15.3.0-canary.43': optional: true - '@next/swc-darwin-x64@15.3.0-canary.41': + '@next/swc-darwin-x64@15.3.0-canary.43': optional: true - '@next/swc-linux-arm64-gnu@15.3.0-canary.41': + '@next/swc-linux-arm64-gnu@15.3.0-canary.43': optional: true - '@next/swc-linux-arm64-musl@15.3.0-canary.41': + '@next/swc-linux-arm64-musl@15.3.0-canary.43': optional: true - '@next/swc-linux-x64-gnu@15.3.0-canary.41': + '@next/swc-linux-x64-gnu@15.3.0-canary.43': optional: true - '@next/swc-linux-x64-musl@15.3.0-canary.41': + '@next/swc-linux-x64-musl@15.3.0-canary.43': optional: true - '@next/swc-win32-arm64-msvc@15.3.0-canary.41': + '@next/swc-win32-arm64-msvc@15.3.0-canary.43': optional: true - '@next/swc-win32-x64-msvc@15.3.0-canary.41': + '@next/swc-win32-x64-msvc@15.3.0-canary.43': optional: true '@nodelib/fs.scandir@2.1.5': @@ -5286,9 +5102,9 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-next@15.3.0-canary.41(eslint@9.24.0)(typescript@5.8.3): + eslint-config-next@15.3.0-canary.43(eslint@9.24.0)(typescript@5.8.3): dependencies: - '@next/eslint-plugin-next': 15.3.0-canary.41 + '@next/eslint-plugin-next': 15.3.0-canary.43 '@rushstack/eslint-patch': 1.11.0 '@typescript-eslint/eslint-plugin': 8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.24.0)(typescript@5.8.3))(eslint@9.24.0)(typescript@5.8.3) '@typescript-eslint/parser': 8.29.0(eslint@9.24.0)(typescript@5.8.3) @@ -5707,9 +5523,9 @@ snapshots: functions-have-names@1.2.3: {} - geist@1.3.1(next@15.3.0-canary.41(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-e993439-20250405)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)): + geist@1.3.1(next@15.3.0-canary.43(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-e993439-20250405)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)): dependencies: - next: 15.3.0-canary.41(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-e993439-20250405)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next: 15.3.0-canary.43(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-e993439-20250405)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) gensync@1.0.0-beta.2: {} @@ -6842,9 +6658,9 @@ snapshots: natural-compare@1.4.0: {} - next@15.3.0-canary.41(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-e993439-20250405)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + next@15.3.0-canary.43(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-e993439-20250405)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: - '@next/env': 15.3.0-canary.41 + '@next/env': 15.3.0-canary.43 '@swc/counter': 0.1.3 '@swc/helpers': 0.5.15 busboy: 1.6.0 @@ -6854,16 +6670,16 @@ snapshots: react-dom: 19.1.0(react@19.1.0) styled-jsx: 5.1.6(@babel/core@7.26.10)(react@19.1.0) optionalDependencies: - '@next/swc-darwin-arm64': 15.3.0-canary.41 - '@next/swc-darwin-x64': 15.3.0-canary.41 - '@next/swc-linux-arm64-gnu': 15.3.0-canary.41 - '@next/swc-linux-arm64-musl': 15.3.0-canary.41 - '@next/swc-linux-x64-gnu': 15.3.0-canary.41 - '@next/swc-linux-x64-musl': 15.3.0-canary.41 - '@next/swc-win32-arm64-msvc': 15.3.0-canary.41 - '@next/swc-win32-x64-msvc': 15.3.0-canary.41 + '@next/swc-darwin-arm64': 15.3.0-canary.43 + '@next/swc-darwin-x64': 15.3.0-canary.43 + '@next/swc-linux-arm64-gnu': 15.3.0-canary.43 + '@next/swc-linux-arm64-musl': 15.3.0-canary.43 + '@next/swc-linux-x64-gnu': 15.3.0-canary.43 + '@next/swc-linux-x64-musl': 15.3.0-canary.43 + '@next/swc-win32-arm64-msvc': 15.3.0-canary.43 + '@next/swc-win32-x64-msvc': 15.3.0-canary.43 babel-plugin-react-compiler: 19.0.0-beta-e993439-20250405 - sharp: 0.33.5 + sharp: 0.34.1 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -7523,41 +7339,14 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 - sharp@0.33.5: + sharp@0.34.1: dependencies: color: 4.2.3 detect-libc: 2.0.3 semver: 7.7.1 optionalDependencies: - '@img/sharp-darwin-arm64': 0.33.5 - '@img/sharp-darwin-x64': 0.33.5 - '@img/sharp-libvips-darwin-arm64': 1.0.4 - '@img/sharp-libvips-darwin-x64': 1.0.4 - '@img/sharp-libvips-linux-arm': 1.0.5 - '@img/sharp-libvips-linux-arm64': 1.0.4 - '@img/sharp-libvips-linux-s390x': 1.0.4 - '@img/sharp-libvips-linux-x64': 1.0.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 - '@img/sharp-linux-arm': 0.33.5 - '@img/sharp-linux-arm64': 0.33.5 - '@img/sharp-linux-s390x': 0.33.5 - '@img/sharp-linux-x64': 0.33.5 - '@img/sharp-linuxmusl-arm64': 0.33.5 - '@img/sharp-linuxmusl-x64': 0.33.5 - '@img/sharp-wasm32': 0.33.5 - '@img/sharp-win32-ia32': 0.33.5 - '@img/sharp-win32-x64': 0.33.5 - optional: true - - sharp@0.34.0: - dependencies: - color: 4.2.3 - detect-libc: 2.0.3 - semver: 7.7.1 - optionalDependencies: - '@img/sharp-darwin-arm64': 0.34.0 - '@img/sharp-darwin-x64': 0.34.0 + '@img/sharp-darwin-arm64': 0.34.1 + '@img/sharp-darwin-x64': 0.34.1 '@img/sharp-libvips-darwin-arm64': 1.1.0 '@img/sharp-libvips-darwin-x64': 1.1.0 '@img/sharp-libvips-linux-arm': 1.1.0 @@ -7567,15 +7356,15 @@ snapshots: '@img/sharp-libvips-linux-x64': 1.1.0 '@img/sharp-libvips-linuxmusl-arm64': 1.1.0 '@img/sharp-libvips-linuxmusl-x64': 1.1.0 - '@img/sharp-linux-arm': 0.34.0 - '@img/sharp-linux-arm64': 0.34.0 - '@img/sharp-linux-s390x': 0.34.0 - '@img/sharp-linux-x64': 0.34.0 - '@img/sharp-linuxmusl-arm64': 0.34.0 - '@img/sharp-linuxmusl-x64': 0.34.0 - '@img/sharp-wasm32': 0.34.0 - '@img/sharp-win32-ia32': 0.34.0 - '@img/sharp-win32-x64': 0.34.0 + '@img/sharp-linux-arm': 0.34.1 + '@img/sharp-linux-arm64': 0.34.1 + '@img/sharp-linux-s390x': 0.34.1 + '@img/sharp-linux-x64': 0.34.1 + '@img/sharp-linuxmusl-arm64': 0.34.1 + '@img/sharp-linuxmusl-x64': 0.34.1 + '@img/sharp-wasm32': 0.34.1 + '@img/sharp-win32-ia32': 0.34.1 + '@img/sharp-win32-x64': 0.34.1 optional: true shebang-command@2.0.0: