From f9739d955131ccd0e77b889f0dfe7d8a793c8c65 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Mon, 29 Jun 2026 20:31:59 +0300 Subject: [PATCH 01/12] Fix in-progress SE/DE standings sometimes incorrect --- .../tournament-bracket/core/Bracket.test.ts | 193 ++++++++++++++++++ .../core/Bracket/DoubleEliminationBracket.ts | 25 ++- .../core/Bracket/SingleEliminationBracket.ts | 12 +- .../tournament-bracket/core/Bracket/utils.ts | 31 +++ 4 files changed, 247 insertions(+), 14 deletions(-) create mode 100644 app/features/tournament-bracket/core/Bracket/utils.ts diff --git a/app/features/tournament-bracket/core/Bracket.test.ts b/app/features/tournament-bracket/core/Bracket.test.ts index 0566bd5cc..3a85b9553 100644 --- a/app/features/tournament-bracket/core/Bracket.test.ts +++ b/app/features/tournament-bracket/core/Bracket.test.ts @@ -626,3 +626,196 @@ describe("single elimination standings - third place match", () => { ).toBe(4); }); }); + +const reportLowerIdWinner = ( + storage: InMemoryDatabase, + manager: BracketsManager, + matchId: number, +) => { + const match = storage.select("match", matchId); + invariant(match, `match ${matchId} not found`); + const opponent1Lower = match.opponent1.id < match.opponent2.id; + manager.update.match({ + id: matchId, + opponent1: opponent1Lower ? { score: 2, result: "win" } : { score: 0 }, + opponent2: opponent1Lower ? { score: 0 } : { score: 2, result: "win" }, + }); +}; + +const readyMatches = ( + storage: InMemoryDatabase, + predicate: (match: any) => boolean, +) => + storage + .select("match")! + .filter( + (match) => + predicate(match) && + match.opponent1?.id != null && + match.opponent2?.id != null && + match.opponent1.result == null && + match.opponent2.result == null, + ); + +describe("single elimination standings - projected ties", () => { + // Two semifinal losers tie for 3rd (no consolation final). Reports only one + // semifinal so the other is still in progress, mirroring the projected + // standings bug where the finished team is shown one placement too low. + const partialSingleEliminationTournament = () => { + const storage = new InMemoryDatabase(); + const manager = new BracketsManager(storage); + + manager.create({ + name: "SE", + tournamentId: 1, + type: "single_elimination", + seeding: [1, 2, 3, 4], + settings: {}, + }); + + const semifinals = storage + .select("match")! + .filter((match) => match.opponent1?.id && match.opponent2?.id); + invariant(semifinals.length === 2, "Expected two semifinal matches"); + + const decided = semifinals[0]; + const decidedLoserId = Math.max(decided.opponent1.id, decided.opponent2.id); + reportLowerIdWinner(storage, manager, decided.id); + + const tournament = testTournament({ + ctx: { + settings: { + bracketProgression: [ + { + type: "single_elimination", + name: "SE", + requiresCheckIn: false, + settings: {}, + sources: [], + }, + ], + }, + }, + data: manager.get.tournamentData(1), + }); + + return { tournament, decidedLoserId }; + }; + + it("projects a finished semifinal loser as tied 3rd before the other semifinal finishes", () => { + const { tournament, decidedLoserId } = partialSingleEliminationTournament(); + + const standings = tournament.bracketByIdx(0)!.standings; + + expect(standings.find((s) => s.team.id === decidedLoserId)?.placement).toBe( + 3, + ); + }); +}); + +describe("double elimination standings - projected ties", () => { + // 8-team DE: losers round 2 produces the 5th/6th tie. Plays out the whole + // winners bracket and losers round 1, then reports only one of the two + // losers round 2 matches so its loser should already project to tied 5th + // while the sibling match is still unfinished. + const partialDoubleEliminationTournament = () => { + const storage = new InMemoryDatabase(); + const manager = new BracketsManager(storage); + + manager.create({ + name: "DE", + tournamentId: 1, + type: "double_elimination", + seeding: [1, 2, 3, 4, 5, 6, 7, 8], + settings: { grandFinal: "double", seedOrdering: ["natural"] }, + }); + + const groupId = (number: number) => + storage.select("group")!.find((g) => g.number === number)!.id; + const winnersGroupId = groupId(1); + const losersGroupId = groupId(2); + + const losersRoundId = (number: number) => + storage + .select("round")! + .find((r) => r.group_id === losersGroupId && r.number === number)!.id; + + // play out the entire winners bracket so all losers feed in + let winnersReady = readyMatches( + storage, + (m) => m.group_id === winnersGroupId, + ); + while (winnersReady.length) { + for (const match of winnersReady) { + reportLowerIdWinner(storage, manager, match.id); + } + winnersReady = readyMatches( + storage, + (m) => m.group_id === winnersGroupId, + ); + } + + // losers round 1: both matches -> two teams eliminated, tied 7th/8th + for (const match of readyMatches( + storage, + (m) => m.round_id === losersRoundId(1), + )) { + reportLowerIdWinner(storage, manager, match.id); + } + + // losers round 2: report only one of the two matches + const losersRound2 = readyMatches( + storage, + (m) => m.round_id === losersRoundId(2), + ); + invariant(losersRound2.length === 2, "Expected two losers round 2 matches"); + + const decided = losersRound2[0]; + const decidedLoserId = Math.max(decided.opponent1.id, decided.opponent2.id); + const stillPlayingTeamIds = [ + losersRound2[1].opponent1.id, + losersRound2[1].opponent2.id, + ]; + reportLowerIdWinner(storage, manager, decided.id); + + const tournament = testTournament({ + ctx: { + settings: { + bracketProgression: [ + { + type: "double_elimination", + name: "DE", + requiresCheckIn: false, + settings: {}, + sources: [], + }, + ], + }, + }, + data: manager.get.tournamentData(1), + }); + + return { tournament, decidedLoserId, stillPlayingTeamIds }; + }; + + it("projects a finished losers-round-2 loser as tied 5th before the sibling match finishes", () => { + const { tournament, decidedLoserId } = partialDoubleEliminationTournament(); + + const standings = tournament.bracketByIdx(0)!.standings; + + expect(standings.find((s) => s.team.id === decidedLoserId)?.placement).toBe( + 5, + ); + }); + + it("does not yet place teams still playing their losers round 2 match", () => { + const { tournament, stillPlayingTeamIds } = + partialDoubleEliminationTournament(); + + const standings = tournament.bracketByIdx(0)!.standings; + + for (const teamId of stillPlayingTeamIds) { + expect(standings.find((s) => s.team.id === teamId)).toBe(undefined); + } + }); +}); diff --git a/app/features/tournament-bracket/core/Bracket/DoubleEliminationBracket.ts b/app/features/tournament-bracket/core/Bracket/DoubleEliminationBracket.ts index 64b9e42b9..0991e962a 100644 --- a/app/features/tournament-bracket/core/Bracket/DoubleEliminationBracket.ts +++ b/app/features/tournament-bracket/core/Bracket/DoubleEliminationBracket.ts @@ -5,6 +5,7 @@ import type { Round } from "~/modules/brackets-model"; import invariant from "~/utils/invariant"; import type { BracketMapCounts } from "../toMapList"; import { Bracket, type Standing } from "./Bracket"; +import { cumulativeEliminationsByRound } from "./utils"; export class DoubleEliminationBracket extends Bracket { get type(): Tables["TournamentStage"]["type"] { @@ -73,13 +74,13 @@ export class DoubleEliminationBracket extends Bracket { const losersGroupId = this.data.group.find((g) => g.number === 2)?.id; + const losersMatches = this.data.match + .filter((match) => match.group_id === losersGroupId) + .sort((a, b) => a.round_id - b.round_id); + const teams: { id: number; lostAt: number }[] = []; - for (const match of this.data.match - .slice() - .sort((a, b) => a.round_id - b.round_id)) { - if (match.group_id !== losersGroupId) continue; - + for (const match of losersMatches) { if ( match.opponent1?.result !== "win" && match.opponent2?.result !== "win" @@ -97,8 +98,8 @@ export class DoubleEliminationBracket extends Bracket { teams.push({ id: loser.id, lostAt: match.round_id }); } - const teamCountWhoDidntLoseInLosersYet = - this.participantTournamentTeamIds.length - teams.length; + const eliminationsThroughLosersRound = + cumulativeEliminationsByRound(losersMatches); const result: Standing[] = []; for (const roundId of R.unique(teams.map((team) => team.lostAt))) { @@ -107,16 +108,18 @@ export class DoubleEliminationBracket extends Bracket { teamsLostThisRound.push(teams.shift()!); } + const placement = + this.participantTournamentTeamIds.length - + eliminationsThroughLosersRound.get(roundId)! + + 1; + for (const { id: teamId } of teamsLostThisRound) { const team = this.tournament.teamById(teamId); invariant(team, `Team not found for id: ${teamId}`); - const teamsPlacedAbove = - teamCountWhoDidntLoseInLosersYet + teams.length; - result.push({ team, - placement: teamsPlacedAbove + 1, + placement, }); } } diff --git a/app/features/tournament-bracket/core/Bracket/SingleEliminationBracket.ts b/app/features/tournament-bracket/core/Bracket/SingleEliminationBracket.ts index 14f030c55..3cb528cf3 100644 --- a/app/features/tournament-bracket/core/Bracket/SingleEliminationBracket.ts +++ b/app/features/tournament-bracket/core/Bracket/SingleEliminationBracket.ts @@ -5,6 +5,7 @@ import type { Round } from "~/modules/brackets-model"; import invariant from "~/utils/invariant"; import type { BracketMapCounts } from "../toMapList"; import { Bracket, type Standing } from "./Bracket"; +import { cumulativeEliminationsByRound } from "./utils"; export class SingleEliminationBracket extends Bracket { get type(): Tables["TournamentStage"]["type"] { @@ -96,6 +97,8 @@ export class SingleEliminationBracket extends Bracket { const teamCountWhoDidntLoseYet = this.participantTournamentTeamIds.length - teams.length; + const eliminationsThroughRound = cumulativeEliminationsByRound(matches); + const result: Standing[] = []; for (const roundId of R.unique(teams.map((team) => team.lostAt))) { const teamsLostThisRound: { id: number }[] = []; @@ -103,15 +106,18 @@ export class SingleEliminationBracket extends Bracket { teamsLostThisRound.push(teams.shift()!); } + const placement = + this.participantTournamentTeamIds.length - + eliminationsThroughRound.get(roundId)! + + 1; + for (const { id: teamId } of teamsLostThisRound) { const team = this.tournament.teamById(teamId); invariant(team, `Team not found for id: ${teamId}`); - const teamsPlacedAbove = teamCountWhoDidntLoseYet + teams.length; - result.push({ team, - placement: teamsPlacedAbove + 1, + placement, }); } } diff --git a/app/features/tournament-bracket/core/Bracket/utils.ts b/app/features/tournament-bracket/core/Bracket/utils.ts new file mode 100644 index 000000000..234c5b61b --- /dev/null +++ b/app/features/tournament-bracket/core/Bracket/utils.ts @@ -0,0 +1,31 @@ +import * as R from "remeda"; +import type { TournamentManagerDataSet } from "~/modules/brackets-manager/types"; + +/** + * Maps each round_id to the cumulative number of teams eliminated by the end of + * that round, counting one elimination per non-bye match. This is a structural + * property of the bracket that does not depend on which matches have already + * been reported, so teams tied at the same placement resolve to the same + * placement even while some of their round's matches are still in progress. + */ +export function cumulativeEliminationsByRound( + matches: TournamentManagerDataSet["match"], +): Map { + const result = new Map(); + + const roundIds = R.unique(matches.map((match) => match.round_id)).sort( + (a, b) => a - b, + ); + + let cumulativeEliminations = 0; + for (const roundId of roundIds) { + const eliminationsThisRound = matches.filter( + (match) => + match.round_id === roundId && match.opponent1 && match.opponent2, + ).length; + cumulativeEliminations += eliminationsThisRound; + result.set(roundId, cumulativeEliminations); + } + + return result; +} From 9759a896fe38d5ff08c3a757d2342383542ff0d0 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Mon, 29 Jun 2026 20:35:00 +0300 Subject: [PATCH 02/12] SQ match page display IGN in place of username when available --- .../match-page/MatchRosterTab.module.css | 23 ----------- app/components/match-page/MatchRosterTab.tsx | 39 ++++--------------- 2 files changed, 8 insertions(+), 54 deletions(-) diff --git a/app/components/match-page/MatchRosterTab.module.css b/app/components/match-page/MatchRosterTab.module.css index 60755fd37..495b6c0d3 100644 --- a/app/components/match-page/MatchRosterTab.module.css +++ b/app/components/match-page/MatchRosterTab.module.css @@ -122,18 +122,6 @@ gap: var(--s-2); } -.memberNameStack { - display: flex; - flex-direction: column; - line-height: 1.2; -} - -.memberInGameName { - font-size: var(--font-2xs); - color: var(--color-text-high); - font-weight: var(--weight-semi); -} - .memberMenuTrigger { background: none; border: 0; @@ -154,17 +142,6 @@ gap: var(--s-0-5); } -.memberMenuIgn { - font-size: var(--font-2xs); - color: var(--color-text-high); -} - -.memberMenuIgnLabel { - font-weight: var(--weight-bold); - text-transform: uppercase; - font-size: var(--font-3xs); -} - .memberTier { display: flex; justify-content: center; diff --git a/app/components/match-page/MatchRosterTab.tsx b/app/components/match-page/MatchRosterTab.tsx index e696de3a2..1dd5eacc7 100644 --- a/app/components/match-page/MatchRosterTab.tsx +++ b/app/components/match-page/MatchRosterTab.tsx @@ -465,26 +465,12 @@ function RosterMemberLink({ member: RosterTabMember; className?: string; }) { - const { t } = useTranslation(["friends", "q", "user"]); + const { t } = useTranslation(["friends", "q"]); const showNoteItem = member.privateNote !== undefined; - const hasContentBelowName = !!( - member.tier || - typeof member.plusTier === "number" || - (member.weaponPool && member.weaponPool.length > 0) - ); - const showIgnInMenu = hasContentBelowName && !!member.inGameName; - const showIgnUnderName = !hasContentBelowName && !!member.inGameName; - const useMenu = !!member.friendCode || showNoteItem || showIgnInMenu; + const useMenu = !!member.friendCode || showNoteItem; - const nameContent = ( -
- {member.username} - {showIgnUnderName ? ( - {member.inGameName} - ) : null} -
- ); + const nameContent = {member.inGameName ?? member.username}; if (!useMenu) { return ( @@ -495,20 +481,11 @@ function RosterMemberLink({ ); } - const headerContent = - member.friendCode || showIgnInMenu ? ( -
- {member.friendCode ? {`SW-${member.friendCode}`} : null} - {showIgnInMenu ? ( - - - {t("user:ign.short")}: - {" "} - {member.inGameName} - - ) : null} -
- ) : undefined; + const headerContent = member.friendCode ? ( +
+ {`SW-${member.friendCode}`} +
+ ) : undefined; return ( Date: Mon, 29 Jun 2026 20:44:52 +0300 Subject: [PATCH 03/12] Sync can edit tournament perms UI/action perm check --- .../calendar/actions/calendar.new.server.ts | 8 ++++-- .../tournament-admin/routes/to.$id.admin.tsx | 5 +++- .../tournament-bracket/core/Tournament.ts | 25 +++++++++++++++++++ .../tournament-bracket/core/tests/mocks-li.ts | 1 + .../core/tests/mocks-sos.ts | 1 + .../tournament/TournamentRepository.server.ts | 1 + .../tournament/tournament-utils.test.ts | 1 + 7 files changed, 39 insertions(+), 3 deletions(-) diff --git a/app/features/calendar/actions/calendar.new.server.ts b/app/features/calendar/actions/calendar.new.server.ts index dcc32868f..c3f05edf0 100644 --- a/app/features/calendar/actions/calendar.new.server.ts +++ b/app/features/calendar/actions/calendar.new.server.ts @@ -44,6 +44,7 @@ export const action: ActionFunction = async ({ request }) => { const isEditing = Boolean(data.eventToEditId); const isAddingTournament = data.toToolsEnabled; + const isTournamentAdder = user.roles.includes("TOURNAMENT_ADDER"); const organizationId = data.organizationId ? Number(data.organizationId) : null; @@ -52,7 +53,7 @@ export const action: ActionFunction = async ({ request }) => { await validateOrganization({ userId: user.id, organizationId, - isTournamentAdder: user.roles.includes("TOURNAMENT_ADDER"), + isTournamentAdder, }); } else if (!isEditing) { requireRole( @@ -141,7 +142,10 @@ export const action: ActionFunction = async ({ request }) => { "Tournament has already started", ); - errorToastIfFalsy(tournament.isAdmin(user), "Not authorized"); + errorToastIfFalsy( + tournament.canEditEventInfo(user, { isTournamentAdder }), + "Not authorized", + ); // once published, a tournament can't be flipped back to draft if (!tournament.isDraft) { diff --git a/app/features/tournament-admin/routes/to.$id.admin.tsx b/app/features/tournament-admin/routes/to.$id.admin.tsx index 6fc336aeb..11f9934e9 100644 --- a/app/features/tournament-admin/routes/to.$id.admin.tsx +++ b/app/features/tournament-admin/routes/to.$id.admin.tsx @@ -23,6 +23,7 @@ import { Redirect } from "~/components/Redirect"; import { DANGEROUS_CAN_ACCESS_DEV_CONTROLS } from "~/features/admin/core/dev-controls"; import { useUser } from "~/features/auth/core/user"; import { useTournament } from "~/features/tournament/routes/to.$id"; +import { useHasRole } from "~/modules/permissions/hooks"; import { calendarEventPage, tournamentAdminPage, @@ -42,6 +43,7 @@ export default function TournamentAdminLayout() { const tournament = useTournament(); const outletContext = useOutletContext(); const user = useUser(); + const isTournamentAdder = useHasRole("TOURNAMENT_ADDER"); const location = useLocation(); const showReopen = Boolean( @@ -73,7 +75,8 @@ export default function TournamentAdminLayout() { return (
- {tournament.isAdmin(user) && !tournament.hasStarted ? ( + {tournament.canEditEventInfo(user, { isTournamentAdder }) && + !tournament.hasStarted ? (
member.userId === user.id && member.role === "ADMIN", + ); + + return Boolean( + isOrganizationAdmin && + (isTournamentAdder || this.ctx.organization?.isEstablished), + ); + } + /** Checks if the given user is an organizer of the tournament. */ isOrganizer(user: OptionalIdObject) { if (!user) return false; diff --git a/app/features/tournament-bracket/core/tests/mocks-li.ts b/app/features/tournament-bracket/core/tests/mocks-li.ts index 5c2f5dc27..9d25cc332 100644 --- a/app/features/tournament-bracket/core/tests/mocks-li.ts +++ b/app/features/tournament-bracket/core/tests/mocks-li.ts @@ -6923,6 +6923,7 @@ export const LOW_INK_DECEMBER_2024 = (): TournamentData => ({ id: 3, name: "Inkling Performance Labs", slug: "inkling-performance-labs", + isEstablished: 1, logoUrl: "fZrToLQrkqV3UZkdgwp0Q-1722263644749.webp", series: [], members: [ diff --git a/app/features/tournament-bracket/core/tests/mocks-sos.ts b/app/features/tournament-bracket/core/tests/mocks-sos.ts index d638f3c0f..794d8b3f8 100644 --- a/app/features/tournament-bracket/core/tests/mocks-sos.ts +++ b/app/features/tournament-bracket/core/tests/mocks-sos.ts @@ -2026,6 +2026,7 @@ export const SWIM_OR_SINK_167 = ( id: 3, name: "Inkling Performance Labs", slug: "inkling-performance-labs", + isEstablished: 1, logoUrl: "fZrToLQrkqV3UZkdgwp0Q-1722263644749.webp", series: [], members: [ diff --git a/app/features/tournament/TournamentRepository.server.ts b/app/features/tournament/TournamentRepository.server.ts index e05e664e1..de27605bf 100644 --- a/app/features/tournament/TournamentRepository.server.ts +++ b/app/features/tournament/TournamentRepository.server.ts @@ -85,6 +85,7 @@ export async function findById(id: number) { "TournamentOrganization.id", "TournamentOrganization.name", "TournamentOrganization.slug", + "TournamentOrganization.isEstablished", concatUserSubmittedImagePrefix( innerEb.ref("UserSubmittedImage.url"), ).as("logoUrl"), diff --git a/app/features/tournament/tournament-utils.test.ts b/app/features/tournament/tournament-utils.test.ts index bd3a83c88..0a9f2da7d 100644 --- a/app/features/tournament/tournament-utils.test.ts +++ b/app/features/tournament/tournament-utils.test.ts @@ -782,6 +782,7 @@ describe("tournamentNameParts", () => { id: 1, name: "Sendou's Tournaments", slug: "sendou", + isEstablished: 1, logoUrl: null, members: [], series: [{ name: "In The Zone" }], From 17a2ba1c6f8546531059c7d34fc5d459fd5718a8 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Mon, 29 Jun 2026 21:17:05 +0300 Subject: [PATCH 04/12] Fix E2E tests --- e2e/org.spec.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/e2e/org.spec.ts b/e2e/org.spec.ts index 81ab0462a..7c81b1767 100644 --- a/e2e/org.spec.ts +++ b/e2e/org.spec.ts @@ -70,6 +70,17 @@ test.describe("Tournament Organization", () => { .selectOption("ADMIN"); await submit(page); + // Establish the organization so its admins can edit tournament event info + await navigate({ page, url }); + await page.getByRole("tab", { name: "Admin" }).click(); + const isEstablishedForm = createFormHelpers( + page, + updateIsEstablishedSchema, + ); + await waitForPOSTResponse(page, () => + isEstablishedForm.check("isEstablished"), + ); + // 3. As the promoted user, verify edit controls are visible and page can be accessed await impersonate(page, NZAP_TEST_ID); await navigate({ From a925ea21e422a08c3190ff25c7619b05cddeeb5e Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Wed, 1 Jul 2026 19:35:01 +0300 Subject: [PATCH 05/12] Increase max org count 3->5 --- .../tournament-organization-constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/features/tournament-organization/tournament-organization-constants.ts b/app/features/tournament-organization/tournament-organization-constants.ts index 85d88bdeb..536e70132 100644 --- a/app/features/tournament-organization/tournament-organization-constants.ts +++ b/app/features/tournament-organization/tournament-organization-constants.ts @@ -5,5 +5,5 @@ export const TOURNAMENT_ORGANIZATION = { DESCRIPTION_MAX_LENGTH: 1_000, BAN_REASON_MAX_LENGTH: 200, MAX_BANNED_USERS: 100, - MAX_MEMBER_OF_COUNT: 3, + MAX_MEMBER_OF_COUNT: 5, }; From 31d910cca41ad8b88a7b12cfd5069c0b8c37a9d5 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Wed, 1 Jul 2026 19:35:13 +0300 Subject: [PATCH 06/12] Fix breadcrumb nav icon hrefs broken --- app/components/layout/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/components/layout/index.tsx b/app/components/layout/index.tsx index 6cbf2ff18..e1c634261 100644 --- a/app/components/layout/index.tsx +++ b/app/components/layout/index.tsx @@ -605,7 +605,8 @@ function PageIcon({ crumb }: { crumb: Breadcrumb }) { return null; } - const isExternal = crumb.imgPath.includes("."); + const lastPathSegment = crumb.imgPath.split("/").pop() ?? ""; + const isExternal = lastPathSegment.includes("."); const iconClass = clsx(styles.pageIcon, "rounded"); return ( From 4c12adafd08199140d9c5bb6e9864fb92d99ca93 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Wed, 1 Jul 2026 19:38:59 +0300 Subject: [PATCH 07/12] Make searching orgs, weapons less fussy --- app/db/sql.ts | 9 ++++ ...TournamentOrganizationRepository.server.ts | 8 +++- app/modules/in-game-lists/utils.test.ts | 47 +++++++++++++++++++ app/modules/in-game-lists/utils.ts | 6 ++- 4 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 app/modules/in-game-lists/utils.test.ts diff --git a/app/db/sql.ts b/app/db/sql.ts index 7974f3fdc..7f3f2b3a8 100644 --- a/app/db/sql.ts +++ b/app/db/sql.ts @@ -37,6 +37,15 @@ sql.pragma("mmap_size = 3221225472"); // connections; pair with a periodic `PRAGMA optimize;` (see OptimizeDatabase routine) sql.pragma("optimize = 0x10002"); +// Strips diacritics so accent-insensitive name searches are possible +// (e.g. "cafe" matches "Café"). Combined with LIKE's built-in ASCII +// case-insensitivity this also folds case for the resulting latin letters. +sql.function("unaccent", { deterministic: true }, (value) => + typeof value === "string" + ? value.normalize("NFD").replace(/\p{M}/gu, "") + : value, +); + export const db = new Kysely({ dialect: new SqliteDialect({ database: sql, diff --git a/app/features/tournament-organization/TournamentOrganizationRepository.server.ts b/app/features/tournament-organization/TournamentOrganizationRepository.server.ts index 16eca1d31..f5be8ed33 100644 --- a/app/features/tournament-organization/TournamentOrganizationRepository.server.ts +++ b/app/features/tournament-organization/TournamentOrganizationRepository.server.ts @@ -200,7 +200,13 @@ export function searchByName({ "avatarUrl", ), ]) - .where("TournamentOrganization.name", "like", `%${query}%`) + .where(({ eb, ref }) => + eb( + sql`unaccent(${ref("TournamentOrganization.name")})`, + "like", + sql`unaccent(${`%${query}%`})`, + ), + ) .orderBy("TournamentOrganization.name", "asc") .limit(limit) .execute(); diff --git a/app/modules/in-game-lists/utils.test.ts b/app/modules/in-game-lists/utils.test.ts new file mode 100644 index 000000000..aa63d6c92 --- /dev/null +++ b/app/modules/in-game-lists/utils.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, test } from "vitest"; +import type { MainWeaponId } from "./types"; +import { filterWeapon } from "./utils"; + +describe("filterWeapon", () => { + const sBlast = { type: "MAIN" as const, id: 260 as MainWeaponId }; + + test("matches ignoring hyphens (e.g. 's blast' finds 'S-BLAST')", () => { + expect( + filterWeapon({ + weapon: sBlast, + weaponName: "S-BLAST '92", + searchTerm: "s blast", + }), + ).toBe(true); + }); + + test("matches with the hyphen still present", () => { + expect( + filterWeapon({ + weapon: sBlast, + weaponName: "S-BLAST '92", + searchTerm: "s-blast", + }), + ).toBe(true); + }); + + test("matches ignoring case", () => { + expect( + filterWeapon({ + weapon: sBlast, + weaponName: "S-BLAST '92", + searchTerm: "SBLAST", + }), + ).toBe(true); + }); + + test("does not match unrelated weapon", () => { + expect( + filterWeapon({ + weapon: sBlast, + weaponName: "S-BLAST '92", + searchTerm: "splattershot", + }), + ).toBe(false); + }); +}); diff --git a/app/modules/in-game-lists/utils.ts b/app/modules/in-game-lists/utils.ts index 2a5333a3c..1f60aab1e 100644 --- a/app/modules/in-game-lists/utils.ts +++ b/app/modules/in-game-lists/utils.ts @@ -8,7 +8,11 @@ export function isAbility(value: string): value is Ability { } const normalizeTerm = (term: string): string => { - return term.trim().toLocaleLowerCase(); + return term + .normalize("NFD") + .replace(/\p{M}/gu, "") + .replace(/[^\p{L}\p{N}]/gu, "") + .toLocaleLowerCase(); }; export function filterWeapon({ From 0c5a55fcdf527fe561c0179b17930106e91ab2ca Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Wed, 1 Jul 2026 19:48:41 +0300 Subject: [PATCH 08/12] Map planner special weapon ranges --- app/features/build-analyzer/analyzer-types.ts | 17 ++++ app/features/build-analyzer/core/utils.ts | 8 ++ .../build-analyzer/data/weapon-params.ts | 28 ++++++ .../SpecialRangeVisualization.module.css | 60 ++++++++++++ .../components/SpecialRangeVisualization.tsx | 85 +++++++++++++++++ .../core/special-weapon-range.test.ts | 57 ++++++++++++ .../core/special-weapon-range.ts | 88 ++++++++++++++++++ .../comp-analyzer/core/weapon-range.ts | 8 +- .../routes/comp-analyzer.all-ranges.tsx | 5 + .../map-planner/components/Planner.tsx | 40 +++++++- scripts/create-analyzer-json.ts | 92 +++++++++++++++++++ 11 files changed, 480 insertions(+), 8 deletions(-) create mode 100644 app/features/comp-analyzer/components/SpecialRangeVisualization.module.css create mode 100644 app/features/comp-analyzer/components/SpecialRangeVisualization.tsx create mode 100644 app/features/comp-analyzer/core/special-weapon-range.test.ts create mode 100644 app/features/comp-analyzer/core/special-weapon-range.ts diff --git a/app/features/build-analyzer/analyzer-types.ts b/app/features/build-analyzer/analyzer-types.ts index c99c2d960..4ca6dc007 100644 --- a/app/features/build-analyzer/analyzer-types.ts +++ b/app/features/build-analyzer/analyzer-types.ts @@ -188,6 +188,23 @@ export type SpecialWeaponParams = SpecialWeaponParamsObject[SpecialWeaponId] & { BumpDamage?: number; JumpDamage?: number; TickDamage?: number; + + // Map planner range circle params (populated by scripts/create-analyzer-json.ts). + /** Effect radius for area specials (Big Bubbler, Ink Storm, ...) */ + Range_Radius?: number; + /** Straight-flight range for projectile specials with a fixed distance (Inkjet) */ + Range_Distance?: number; + /** Outer blast radius drawn around a projectile special's impact */ + Range_BlastRadius?: number; + /** Projectile trajectory params (Trizooka, Crab Tank); see comp-analyzer weapon-range */ + Range_SpawnSpeed?: number; + Range_GoStraightStateEndMaxSpeed?: number; + Range_GoStraightToBrakeStateFrame?: number; + Range_FreeGravity?: number; + Range_FreeAirResist?: number; + Range_BrakeAirResist?: number; + Range_BrakeGravity?: number; + Range_BrakeToFreeStateFrame?: number; }; export type ParamsJson = { diff --git a/app/features/build-analyzer/core/utils.ts b/app/features/build-analyzer/core/utils.ts index 175fca829..18312a3f0 100644 --- a/app/features/build-analyzer/core/utils.ts +++ b/app/features/build-analyzer/core/utils.ts @@ -51,6 +51,14 @@ export function mainWeaponParams(weaponId: MainWeaponId): MainWeaponParams { return { ...baseStats, ...kit } as MainWeaponParams; } +export function specialWeaponParams( + specialWeaponId: SpecialWeaponId, +): SpecialWeaponParams { + const params = rawWeaponParams as unknown as ParamsJson; + + return params.specialWeapons[specialWeaponId] as SpecialWeaponParams; +} + export function buildToAbilityPoints(build: BuildAbilitiesTupleWithUnknown) { const result: AbilityPoints = new Map(); diff --git a/app/features/build-analyzer/data/weapon-params.ts b/app/features/build-analyzer/data/weapon-params.ts index b52015df1..75f64bfd9 100644 --- a/app/features/build-analyzer/data/weapon-params.ts +++ b/app/features/build-analyzer/data/weapon-params.ts @@ -2326,6 +2326,15 @@ export const weaponParams = { }, ], DirectDamage: 2200, + Range_SpawnSpeed: 1, + Range_GoStraightStateEndMaxSpeed: 1, + Range_GoStraightToBrakeStateFrame: 18, + Range_FreeGravity: 0.0190565, + Range_FreeAirResist: 0.01985, + Range_BrakeAirResist: 0.09, + Range_BrakeGravity: 0.09, + Range_BrakeToFreeStateFrame: 10, + Range_BlastRadius: 4, }, "2": { overwrites: { @@ -2364,6 +2373,7 @@ export const weaponParams = { Distance: 6, }, ], + Range_Radius: 35, }, "4": { overwrites: { @@ -2412,6 +2422,8 @@ export const weaponParams = { }, }, TickDamage: 33, + Range_Distance: 27, + Range_BlastRadius: 12.6, }, "7": { overwrites: { @@ -2428,6 +2440,7 @@ export const weaponParams = { }, DirectDamage: 300, WaveDamage: 450, + Range_Radius: 20, }, "8": { overwrites: { @@ -2504,6 +2517,8 @@ export const weaponParams = { }, ], DirectDamage: 1200, + Range_Distance: 30, + Range_BlastRadius: 5, }, "11": { overwrites: { @@ -2534,6 +2549,8 @@ export const weaponParams = { }, ], ThrowDirectDamage: 2200, + Range_Distance: 24, + Range_BlastRadius: 8, }, "12": { ArmorHP: 5000, @@ -2557,6 +2574,11 @@ export const weaponParams = { }, ], BumpDamage: 400, + Range_SpawnSpeed: 3.36, + Range_GoStraightStateEndMaxSpeed: 2.232, + Range_GoStraightToBrakeStateFrame: 7, + Range_FreeGravity: 0.016, + Range_BlastRadius: 4.8, }, "13": { overwrites: { @@ -2591,6 +2613,7 @@ export const weaponParams = { Distance: 14.9, }, ], + Range_Radius: 9, }, "14": { overwrites: { @@ -2601,6 +2624,8 @@ export const weaponParams = { }, }, TickDamage: 75, + Range_Distance: 28, + Range_BlastRadius: 7.7, }, "15": { overwrites: { @@ -2641,6 +2666,8 @@ export const weaponParams = { Distance: 6, }, ], + Range_Distance: 30, + Range_BlastRadius: 6, }, "17": { overwrites: { @@ -2676,6 +2703,7 @@ export const weaponParams = { Distance: 10.5, }, ], + Range_Radius: 7, }, "19": { overwrites: { diff --git a/app/features/comp-analyzer/components/SpecialRangeVisualization.module.css b/app/features/comp-analyzer/components/SpecialRangeVisualization.module.css new file mode 100644 index 000000000..2361934de --- /dev/null +++ b/app/features/comp-analyzer/components/SpecialRangeVisualization.module.css @@ -0,0 +1,60 @@ +.container { + display: flex; + flex-direction: column; + gap: var(--s-2); +} + +.legend { + display: flex; + gap: var(--s-4); + font-size: var(--font-xs); + color: var(--color-text-high); +} + +.legendItem { + display: flex; + align-items: center; + gap: var(--s-1-5); +} + +.legendSwatch { + width: 12px; + height: 12px; + border-radius: 2px; +} + +.row { + display: grid; + grid-template-columns: 32px 1fr 3rem; + align-items: center; + gap: var(--s-2); +} + +.track { + position: relative; + height: 14px; + border-radius: var(--radius-field); + background: var(--color-bg-high); + overflow: hidden; +} + +.bar { + position: absolute; + inset-block: 0; + inset-inline-start: 0; + border-radius: var(--radius-field); +} + +.blast { + position: absolute; + inset-block: 0; + opacity: 0.4; +} + +.range { + font-size: var(--font-xs); + font-weight: var(--weight-semi); + color: var(--color-text-high); + text-align: end; + font-variant-numeric: tabular-nums; +} diff --git a/app/features/comp-analyzer/components/SpecialRangeVisualization.tsx b/app/features/comp-analyzer/components/SpecialRangeVisualization.tsx new file mode 100644 index 000000000..6ca7f606d --- /dev/null +++ b/app/features/comp-analyzer/components/SpecialRangeVisualization.tsx @@ -0,0 +1,85 @@ +// note: dev only component, not used in production code + +import { useTranslation } from "react-i18next"; +import { Image } from "~/components/Image"; +import { specialWeaponImageUrl } from "~/utils/urls"; +import { + getSpecialsWithRange, + type SpecialWeaponWithRange, +} from "../core/special-weapon-range"; +import styles from "./SpecialRangeVisualization.module.css"; + +const RANGE_TYPE_COLOR: Record = { + projectile: "#8cd4f5", + radius: "#f5b8d0", +}; + +export function SpecialRangeVisualization() { + const { t } = useTranslation(["weapons"]); + + const specials = getSpecialsWithRange(); + if (specials.length === 0) { + return null; + } + + const maxRange = Math.max( + ...specials.map((special) => special.range + (special.blastRadius ?? 0)), + ); + + return ( +
+
+
+ + projectile +
+
+ + radius +
+
+ {specials.map((special) => { + const color = RANGE_TYPE_COLOR[special.rangeType]; + const rangeWidth = (special.range / maxRange) * 100; + const blastWidth = special.blastRadius + ? (special.blastRadius / maxRange) * 100 + : 0; + + return ( +
+ {t(`weapons:SPECIAL_${special.specialWeaponId}`)} +
+ {blastWidth > 0 ? ( + + ) : null} + +
+ {special.range.toFixed(1)} +
+ ); + })} +
+ ); +} diff --git a/app/features/comp-analyzer/core/special-weapon-range.test.ts b/app/features/comp-analyzer/core/special-weapon-range.test.ts new file mode 100644 index 000000000..3acbba68c --- /dev/null +++ b/app/features/comp-analyzer/core/special-weapon-range.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, test } from "vitest"; +import { + BIG_BUBBLER_ID, + BOOYAH_BOMB_ID, + CRAB_TANK_ID, + SPLATTERCOLOR_SCREEN_ID, + specialWeaponIds, + TRIZOOKA_ID, + WAVE_BREAKER_ID, +} from "~/modules/in-game-lists/weapon-ids"; +import { getSpecialWeaponRange } from "./special-weapon-range"; + +describe("special weapon range", () => { + test("computes a projectile range for Trizooka", () => { + const result = getSpecialWeaponRange(TRIZOOKA_ID); + + expect(result?.rangeType).toBe("projectile"); + expect(result?.range).toBeGreaterThan(0); + }); + + test("Crab Tank reaches further than Trizooka", () => { + const crab = getSpecialWeaponRange(CRAB_TANK_ID); + const trizooka = getSpecialWeaponRange(TRIZOOKA_ID); + + expect(crab!.range).toBeGreaterThan(trizooka!.range); + }); + + test("throws-and-bursts specials expose a throw range plus explosion blast", () => { + const booyah = getSpecialWeaponRange(BOOYAH_BOMB_ID); + + expect(booyah?.rangeType).toBe("projectile"); + expect(booyah?.range).toBeGreaterThan(0); + expect(booyah?.blastRadius).toBeGreaterThan(0); + }); + + test("returns the effect radius for area specials", () => { + const waveBreaker = getSpecialWeaponRange(WAVE_BREAKER_ID); + + expect(waveBreaker?.rangeType).toBe("radius"); + expect(waveBreaker?.range).toBeGreaterThan(0); + }); + + test("returns null for global / utility specials without a meaningful circle", () => { + expect(getSpecialWeaponRange(BIG_BUBBLER_ID)).toBeNull(); + expect(getSpecialWeaponRange(SPLATTERCOLOR_SCREEN_ID)).toBeNull(); + }); + + test("every special is either unsupported or has a positive finite range", () => { + for (const id of specialWeaponIds) { + const result = getSpecialWeaponRange(id); + if (result === null) continue; + + expect(Number.isFinite(result.range)).toBe(true); + expect(result.range).toBeGreaterThan(0); + } + }); +}); diff --git a/app/features/comp-analyzer/core/special-weapon-range.ts b/app/features/comp-analyzer/core/special-weapon-range.ts new file mode 100644 index 000000000..4c22cb0b5 --- /dev/null +++ b/app/features/comp-analyzer/core/special-weapon-range.ts @@ -0,0 +1,88 @@ +import { specialWeaponParams } from "~/features/build-analyzer/core/utils"; +import type { SpecialWeaponId } from "~/modules/in-game-lists/types"; +import { specialWeaponIds } from "~/modules/in-game-lists/weapon-ids"; +import { + calculateGroundRange, + simulateTrajectoryPoints, + type TrajectoryParams, +} from "./weapon-range"; + +const DEFAULT_BRAKE_AIR_RESIST = 0.36; +const DEFAULT_BRAKE_GRAVITY = 0.07; +const DEFAULT_BRAKE_TO_FREE_FRAME = 4; +const DEFAULT_FREE_GRAVITY = 0.016; +const DEFAULT_FREE_AIR_RESIST = 0; + +export interface SpecialWeaponRangeResult { + /** Radius of the range circle, in game distance units. */ + range: number; + /** Extra outer radius covered by the projectile's blast, in game distance units. */ + blastRadius?: number; + rangeType: "projectile" | "radius"; +} + +/** + * Range circle definition for a special weapon, or `null` when the special has no meaningful + * range to draw. The underlying values come from `weapon-params.ts` (populated by + * `scripts/create-analyzer-json.ts`): projectile specials reuse the main weapon trajectory + * model, the rest carry a single effect radius. + */ +export function getSpecialWeaponRange( + specialWeaponId: SpecialWeaponId, +): SpecialWeaponRangeResult | null { + const params = specialWeaponParams(specialWeaponId); + + if (params.Range_Radius !== undefined) { + return { range: params.Range_Radius, rangeType: "radius" }; + } + + if (params.Range_Distance !== undefined) { + return { + range: params.Range_Distance, + blastRadius: params.Range_BlastRadius, + rangeType: "projectile", + }; + } + + if (params.Range_SpawnSpeed === undefined) return null; + + const trajectoryParams: TrajectoryParams = { + spawnSpeed: params.Range_SpawnSpeed, + goStraightStateEndMaxSpeed: + params.Range_GoStraightStateEndMaxSpeed ?? params.Range_SpawnSpeed, + goStraightToBrakeStateFrame: params.Range_GoStraightToBrakeStateFrame ?? 4, + freeGravity: params.Range_FreeGravity ?? DEFAULT_FREE_GRAVITY, + freeAirResist: params.Range_FreeAirResist ?? DEFAULT_FREE_AIR_RESIST, + brakeAirResist: params.Range_BrakeAirResist ?? DEFAULT_BRAKE_AIR_RESIST, + brakeGravity: params.Range_BrakeGravity ?? DEFAULT_BRAKE_GRAVITY, + brakeToFreeFrame: + params.Range_BrakeToFreeStateFrame ?? DEFAULT_BRAKE_TO_FREE_FRAME, + }; + + const range = calculateGroundRange( + simulateTrajectoryPoints(trajectoryParams), + ); + + return { + range, + blastRadius: params.Range_BlastRadius, + rangeType: "projectile", + }; +} + +export interface SpecialWeaponWithRange extends SpecialWeaponRangeResult { + specialWeaponId: SpecialWeaponId; +} + +/** + * Every special weapon that has a range to draw, widest first. Specials without a meaningful + * range circle are omitted. + */ +export function getSpecialsWithRange(): SpecialWeaponWithRange[] { + return specialWeaponIds + .flatMap((specialWeaponId): SpecialWeaponWithRange[] => { + const result = getSpecialWeaponRange(specialWeaponId); + return result ? [{ specialWeaponId, ...result }] : []; + }) + .sort((a, b) => b.range - a.range); +} diff --git a/app/features/comp-analyzer/core/weapon-range.ts b/app/features/comp-analyzer/core/weapon-range.ts index 073addb13..8e3827b99 100644 --- a/app/features/comp-analyzer/core/weapon-range.ts +++ b/app/features/comp-analyzer/core/weapon-range.ts @@ -2,7 +2,7 @@ import { mainWeaponParams } from "~/features/build-analyzer/core/utils"; import type { MainWeaponId } from "~/modules/in-game-lists/types"; import { weaponCategories } from "~/modules/in-game-lists/weapon-ids"; -interface TrajectoryParams { +export interface TrajectoryParams { spawnSpeed: number; goStraightStateEndMaxSpeed: number; goStraightToBrakeStateFrame: number; @@ -37,7 +37,7 @@ function getWeaponCategoryName(weaponId: MainWeaponId): string | undefined { const PLAYER_HEIGHT = 1.0; -function calculateGroundRange(trajectory: TrajectoryPoint[]): number { +export function calculateGroundRange(trajectory: TrajectoryPoint[]): number { for (let i = 1; i < trajectory.length; i++) { const point = trajectory[i]; const prevPoint = trajectory[i - 1]; @@ -55,7 +55,9 @@ function calculateBouncingRange(trajectory: TrajectoryPoint[]): number { return lastPoint?.z ?? 0; } -function simulateTrajectoryPoints(params: TrajectoryParams): TrajectoryPoint[] { +export function simulateTrajectoryPoints( + params: TrajectoryParams, +): TrajectoryPoint[] { const { spawnSpeed, goStraightStateEndMaxSpeed, diff --git a/app/features/comp-analyzer/routes/comp-analyzer.all-ranges.tsx b/app/features/comp-analyzer/routes/comp-analyzer.all-ranges.tsx index ffcfd06fc..b586d3f09 100644 --- a/app/features/comp-analyzer/routes/comp-analyzer.all-ranges.tsx +++ b/app/features/comp-analyzer/routes/comp-analyzer.all-ranges.tsx @@ -5,6 +5,7 @@ import { weaponIdToType, } from "~/modules/in-game-lists/weapon-ids"; import { RangeVisualization } from "../components/RangeVisualization"; +import { SpecialRangeVisualization } from "../components/SpecialRangeVisualization"; export default function AllRangesPage() { return ( @@ -31,6 +32,10 @@ export default function AllRangesPage() { ); })} +
+

specials

+ +
); } diff --git a/app/features/map-planner/components/Planner.tsx b/app/features/map-planner/components/Planner.tsx index 3964d1430..7cb097651 100644 --- a/app/features/map-planner/components/Planner.tsx +++ b/app/features/map-planner/components/Planner.tsx @@ -33,6 +33,7 @@ import { } from "lucide-react"; import * as React from "react"; import { useTranslation } from "react-i18next"; +import { getSpecialWeaponRange } from "~/features/comp-analyzer/core/special-weapon-range"; import { getWeaponRange } from "~/features/comp-analyzer/core/weapon-range"; import { useTheme } from "~/features/theme/core/provider"; import type { LanguageCode } from "~/modules/i18n/config"; @@ -41,6 +42,7 @@ import { stageIds } from "~/modules/in-game-lists/stage-ids"; import type { MainWeaponId, ModeShort, + SpecialWeaponId, StageId, } from "~/modules/in-game-lists/types"; import { @@ -71,6 +73,7 @@ const GAME_UNITS_TO_PX: Record<"MINI" | "OVER", number> = { OVER: 8.4, }; const MAIN_WEAPON_URL_PATTERN = /main-weapons-outlined\/(\d+)/; +const SPECIAL_WEAPON_URL_PATTERN = /special-weapons\/(\d+)/; export default function Planner() { const { t, i18n } = useTranslation(["common"]); @@ -783,6 +786,36 @@ function extractMainWeaponIdFromSrc(src: string): MainWeaponId | null { return id as MainWeaponId; } +function extractSpecialWeaponIdFromSrc(src: string): SpecialWeaponId | null { + const match = src.match(SPECIAL_WEAPON_URL_PATTERN); + if (!match) return null; + + const id = Number(match[1]); + if (!specialWeaponIds.includes(id as SpecialWeaponId)) return null; + + return id as SpecialWeaponId; +} + +function rangeForSrc( + src: string, +): { range: number; blastRadius?: number } | null { + const mainWeaponId = extractMainWeaponIdFromSrc(src); + if (mainWeaponId !== null) { + const result = getWeaponRange(mainWeaponId); + if (result.rangeType === "unsupported" || result.range <= 0) return null; + return { range: result.range, blastRadius: result.blastRadius }; + } + + const specialWeaponId = extractSpecialWeaponIdFromSrc(src); + if (specialWeaponId !== null) { + const result = getSpecialWeaponRange(specialWeaponId); + if (!result || result.range <= 0) return null; + return { range: result.range, blastRadius: result.blastRadius }; + } + + return null; +} + function createRangeCircleForShape( editor: Editor, shape: ReturnType[number], @@ -796,11 +829,8 @@ function createRangeCircleForShape( const asset = editor.getAsset(assetId as TLAssetId); if (asset?.type !== "image" || !asset.props.src) return; - const weaponId = extractMainWeaponIdFromSrc(asset.props.src); - if (!weaponId) return; - - const rangeResult = getWeaponRange(weaponId); - if (rangeResult.rangeType === "unsupported" || rangeResult.range <= 0) return; + const rangeResult = rangeForSrc(asset.props.src); + if (!rangeResult) return; const centerX = shape.x + (shape.props as { w: number }).w / 2; const centerY = shape.y + (shape.props as { h: number }).h / 2; diff --git a/scripts/create-analyzer-json.ts b/scripts/create-analyzer-json.ts index f8ea1ba3a..a9dfcec1e 100644 --- a/scripts/create-analyzer-json.ts +++ b/scripts/create-analyzer-json.ts @@ -129,6 +129,8 @@ async function main() { params.DistanceDamage = undefined; } + Object.assign(params, specialWeaponRangeParams(specialWeapon, rawParams)); + if (hasLangDicts) { translationsToArray({ arr: translations, @@ -693,6 +695,96 @@ function parametersToSubWeaponResult( }; } +// Thrown targeting specials (Booyah Bomb, Super Chump, Triple Inkstrike, Ultra Stamp) are +// player-aimed and have no throw-distance param in the game data, so their throw (fly) range is +// approximated with a constant. Kept as named per-special constants so each can be tuned later; +// for now they all reuse Booyah Bomb's value. +const BOOYAH_BOMB_FLY_DISTANCE = 27; +const SUPER_CHUMP_FLY_DISTANCE = 30; +const TRIPLE_INKSTRIKE_FLY_DISTANCE = 28; +const ULTRA_STAMP_FLY_DISTANCE = 24; + +// Range circle data for the map planner. Projectile / thrown specials store the distance the shot +// travels plus its blast; a few store a single effect radius. Specials whose reach is global +// (Tenta Missiles, Killer Wail), placed or utility (Big Bubbler, Tacticooler, Ink Storm, Ink Vac), +// or otherwise has no meaningful circle (Kraken Royale, Splattercolor Screen) are left out. +// See app/features/comp-analyzer/core/special-weapon-range.ts. +function specialWeaponRangeParams( + specialWeapon: SpecialWeapon, + rawParams: any, +): Record { + const outerBlastDistance = (blastParam: any): number | undefined => + blastParam?.DistanceDamage?.at(-1)?.Distance; + + const trajectoryFromMoveParam = (moveParam: any) => ({ + Range_SpawnSpeed: moveParam?.SpawnSpeed, + Range_GoStraightStateEndMaxSpeed: moveParam?.GoStraightStateEndMaxSpeed, + Range_GoStraightToBrakeStateFrame: moveParam?.GoStraightToBrakeStateFrame, + Range_FreeGravity: moveParam?.FreeGravity, + Range_FreeAirResist: moveParam?.FreeAirResist, + Range_BrakeAirResist: moveParam?.BrakeAirResist, + Range_BrakeGravity: moveParam?.BrakeGravity, + Range_BrakeToFreeStateFrame: moveParam?.BrakeToFreeStateFrame, + }); + + switch (specialWeapon.__RowId) { + case "SpUltraShot": // Trizooka + return { + ...trajectoryFromMoveParam(rawParams.MoveParam), + Range_BlastRadius: outerBlastDistance(rawParams.BlastParam), + }; + case "SpJetpack": // Inkjet (shot flies straight for a fixed distance) + return { + Range_Distance: rawParams.MoveParam?.Distance, + Range_BlastRadius: outerBlastDistance(rawParams.BlastParam), + }; + case "SpChariot": // Crab Tank (rapid gun reach) + return { + ...trajectoryFromMoveParam(rawParams.ShooterMoveParam), + Range_BlastRadius: outerBlastDistance( + rawParams.CannonParam?.BlastParam, + ), + }; + case "SpNiceBall": // Booyah Bomb (thrown, then bursts on impact) + return { + Range_Distance: BOOYAH_BOMB_FLY_DISTANCE, + Range_BlastRadius: rawParams.BlastParam?.DamageRadiusEnd, + }; + case "SpFirework": // Super Chump (thrown, chumps burst on impact) + return { + Range_Distance: SUPER_CHUMP_FLY_DISTANCE, + Range_BlastRadius: outerBlastDistance(rawParams.IceParam?.BlastParam), + }; + case "SpTripleTornado": // Triple Inkstrike (thrown beacons, strikes burst) + return { + Range_Distance: TRIPLE_INKSTRIKE_FLY_DISTANCE, + Range_BlastRadius: rawParams.BlastParam?.DamageRadiusEnd, + }; + case "SpUltraStamp": // Ultra Stamp (thrown stamp bursts on impact) + return { + Range_Distance: ULTRA_STAMP_FLY_DISTANCE, + Range_BlastRadius: outerBlastDistance(rawParams.ThrowBlastParam), + }; + case "SpSuperHook": // Zipcaster (grapple reach) + return { Range_Radius: rawParams.WeaponParam?.MaxLengthHook }; + case "SpShockSonar": // Wave Breaker (tracking wave max radius, base Special Power Up) + return { + Range_Radius: + rawParams.spl__BulletSpShockSonarParam?.WaveParam?.MaxRadius?.Low, + }; + case "SpSkewer": // Reefslider (lethal blast) + return { + Range_Radius: rawParams.BulletBlastParam?.DistanceDamage?.[0]?.Distance, + }; + case "SpPogo": // Triple Splashdown (lethal blast, per splashdown) + return { + Range_Radius: rawParams.BlastParamNormal?.DistanceDamage?.[0]?.Distance, + }; + default: + return {}; + } +} + // some specials lack damage values in the params // so they are instead hardcoded here as a workaround function parametersToSpecialWeaponResult(params: any) { From 83484ff2d87aba3a5eacde8977a9b8eb4967720a Mon Sep 17 00:00:00 2001 From: Kim Tran <55945032+ngkimtran@users.noreply.github.com> Date: Wed, 1 Jul 2026 20:36:43 +0300 Subject: [PATCH 09/12] Add org participant stats admin page (#3197) --- ...amentOrganizationRepository.server.test.ts | 97 ++++++++++++ ...TournamentOrganizationRepository.server.ts | 43 ++++++ .../loaders/org.$slug.stats.server.ts | 54 +++++++ .../routes/org.$slug.stats.module.css | 74 +++++++++ .../routes/org.$slug.stats.test.ts | 92 ++++++++++++ .../routes/org.$slug.stats.tsx | 123 +++++++++++++++ .../routes/org.$slug.tsx | 24 ++- .../tournament-organization/test-utils.ts | 142 ++++++++++++++++++ .../tournament-organization-constants.ts | 8 + .../tournament/tournament-test-utils.ts | 18 ++- app/routes.ts | 4 + app/utils/urls.ts | 2 + locales/da/org.json | 5 +- locales/de/org.json | 5 +- locales/en/org.json | 5 +- locales/es-ES/org.json | 5 +- locales/es-US/org.json | 5 +- locales/fr-CA/org.json | 5 +- locales/fr-EU/org.json | 5 +- locales/he/org.json | 5 +- locales/it/org.json | 5 +- locales/ja/org.json | 5 +- locales/ko/org.json | 5 +- locales/nl/org.json | 5 +- locales/pl/org.json | 5 +- locales/pt-BR/org.json | 5 +- locales/ru/org.json | 5 +- locales/zh/org.json | 5 +- 28 files changed, 739 insertions(+), 22 deletions(-) create mode 100644 app/features/tournament-organization/loaders/org.$slug.stats.server.ts create mode 100644 app/features/tournament-organization/routes/org.$slug.stats.module.css create mode 100644 app/features/tournament-organization/routes/org.$slug.stats.test.ts create mode 100644 app/features/tournament-organization/routes/org.$slug.stats.tsx create mode 100644 app/features/tournament-organization/test-utils.ts diff --git a/app/features/tournament-organization/TournamentOrganizationRepository.server.test.ts b/app/features/tournament-organization/TournamentOrganizationRepository.server.test.ts index cf3f4b0cd..9faf074aa 100644 --- a/app/features/tournament-organization/TournamentOrganizationRepository.server.test.ts +++ b/app/features/tournament-organization/TournamentOrganizationRepository.server.test.ts @@ -1,6 +1,7 @@ import { afterEach, beforeEach, describe, expect, test } from "vitest"; import { dbInsertUsers, dbReset } from "~/utils/Test"; import * as TournamentOrganizationRepository from "./TournamentOrganizationRepository.server"; +import { seedOrgEventWithParticipants } from "./test-utils"; const createOrganization = async ({ ownerId, @@ -90,3 +91,99 @@ describe("findByUserId", () => { expect(result).toHaveLength(0); }); }); + +describe("countActiveParticipants", () => { + const WINDOW_START = 1_700_000_000; + const WINDOW_END = WINDOW_START + 60 * 60 * 24 * 31; + const IN_WINDOW = WINDOW_START + 60 * 60 * 24; + + const countForOrg = (organizationId: number) => + TournamentOrganizationRepository.countActiveParticipants({ + organizationId, + startTime: WINDOW_START, + endTime: WINDOW_END, + }); + + beforeEach(async () => { + await dbInsertUsers(5); + }); + + afterEach(() => { + dbReset(); + }); + + test("counts distinct participants across the organization's events in the window", async () => { + const org = await createOrganization({ ownerId: 1, name: "Org" }); + + await seedOrgEventWithParticipants({ + organizationId: org.id, + startTime: IN_WINDOW, + participantUserIds: [1, 2], + }); + await seedOrgEventWithParticipants({ + organizationId: org.id, + startTime: IN_WINDOW, + participantUserIds: [2, 3], + }); + + // users 1, 2, 3 — user 2 played in both events but is counted once + expect(await countForOrg(org.id)).toBe(3); + }); + + test("excludes teams that did not check in", async () => { + const org = await createOrganization({ ownerId: 1, name: "Org" }); + + await seedOrgEventWithParticipants({ + organizationId: org.id, + startTime: IN_WINDOW, + participantUserIds: [1, 2], + checkIn: "none", + }); + + expect(await countForOrg(org.id)).toBe(0); + }); + + test("excludes teams that checked out", async () => { + const org = await createOrganization({ ownerId: 1, name: "Org" }); + + await seedOrgEventWithParticipants({ + organizationId: org.id, + startTime: IN_WINDOW, + participantUserIds: [1, 2], + checkIn: "out", + }); + + expect(await countForOrg(org.id)).toBe(0); + }); + + test("excludes events outside the time window", async () => { + const org = await createOrganization({ ownerId: 1, name: "Org" }); + + await seedOrgEventWithParticipants({ + organizationId: org.id, + startTime: WINDOW_END + 60 * 60 * 24, + participantUserIds: [1, 2], + }); + + expect(await countForOrg(org.id)).toBe(0); + }); + + test("excludes other organizations' events", async () => { + const org = await createOrganization({ ownerId: 1, name: "Org" }); + const otherOrg = await createOrganization({ ownerId: 2, name: "Other" }); + + await seedOrgEventWithParticipants({ + organizationId: otherOrg.id, + startTime: IN_WINDOW, + participantUserIds: [1, 2, 3], + }); + + expect(await countForOrg(org.id)).toBe(0); + }); + + test("returns 0 when the organization has no events", async () => { + const org = await createOrganization({ ownerId: 1, name: "Org" }); + + expect(await countForOrg(org.id)).toBe(0); + }); +}); diff --git a/app/features/tournament-organization/TournamentOrganizationRepository.server.ts b/app/features/tournament-organization/TournamentOrganizationRepository.server.ts index f5be8ed33..363b9ff50 100644 --- a/app/features/tournament-organization/TournamentOrganizationRepository.server.ts +++ b/app/features/tournament-organization/TournamentOrganizationRepository.server.ts @@ -423,6 +423,49 @@ export async function findAllEventsBySeries({ return events.map(mapEvent); } +/** + * Counts the distinct players who participated in at least one match of a + * tournament hosted by the organization, whose event started within the + * `[startTime, endTime]` range. Only players belonging to teams that checked + * in (and did not check out) are included. + * + * `startTime` and `endTime` are database timestamps (seconds). + */ +export async function countActiveParticipants({ + organizationId, + startTime, + endTime, +}: { + organizationId: number; + startTime: number; + endTime: number; +}) { + const result = await db + .selectFrom("CalendarEvent as ce") + .innerJoin("CalendarEventDate as ced", "ced.eventId", "ce.id") + .innerJoin("Tournament as t", "t.id", "ce.tournamentId") + .innerJoin("TournamentTeam as tt", "tt.tournamentId", "t.id") + .innerJoin( + "TournamentTeamCheckIn as ttci", + "ttci.tournamentTeamId", + "tt.id", + ) + .innerJoin( + "TournamentMatchGameResultParticipant as tmgrp", + "tmgrp.tournamentTeamId", + "tt.id", + ) + .select(({ fn }) => fn.count("tmgrp.userId").distinct().as("count")) + .where("ce.organizationId", "=", organizationId) + .where("ced.startTime", ">=", startTime) + .where("ced.startTime", "<", endTime) + .where("ttci.checkedInAt", "is not", null) + .where("ttci.isCheckOut", "=", 0) + .executeTakeFirst(); + + return result?.count ?? 0; +} + interface UpdateArgs extends Pick< Tables["TournamentOrganization"], diff --git a/app/features/tournament-organization/loaders/org.$slug.stats.server.ts b/app/features/tournament-organization/loaders/org.$slug.stats.server.ts new file mode 100644 index 000000000..472c22df6 --- /dev/null +++ b/app/features/tournament-organization/loaders/org.$slug.stats.server.ts @@ -0,0 +1,54 @@ +import { addMonths, format, startOfMonth, subMonths } from "date-fns"; +import type { LoaderFunctionArgs } from "react-router"; +import * as R from "remeda"; +import { requirePermission } from "~/modules/permissions/guards.server"; +import { dateToDatabaseTimestamp } from "~/utils/dates"; +import * as TournamentOrganizationRepository from "../TournamentOrganizationRepository.server"; +import { + ESTABLISHED_ORG, + MONTH_PARAM_FORMAT, +} from "../tournament-organization-constants"; +import { organizationFromParams } from "../tournament-organization-utils.server"; + +export async function loader({ params }: LoaderFunctionArgs) { + const organization = await organizationFromParams(params); + + requirePermission(organization, "EDIT"); + + const fullMonths = recentFullMonths(ESTABLISHED_ORG.MONTHS_CONSIDERED); + + const monthlyCounts = await Promise.all( + fullMonths.map((month) => + TournamentOrganizationRepository.countActiveParticipants({ + organizationId: organization.id, + startTime: dateToDatabaseTimestamp(month), + endTime: dateToDatabaseTimestamp(addMonths(month, 1)), + }), + ), + ); + + const monthlyStats = fullMonths.map((month, index) => ({ + month: format(month, MONTH_PARAM_FORMAT), + count: monthlyCounts[index], + })); + + const averageMonthlyParticipants = R.mean(monthlyCounts) ?? 0; + + return { + monthlyStats, + averageMonthlyParticipants, + }; +} + +/** The `count` most recent full months + * (excluding the current month), most recent first. */ +function recentFullMonths(count: number) { + const months: Date[] = []; + const thisMonthStart = startOfMonth(new Date()); + + for (let index = 0; index < count; index++) { + months.push(subMonths(thisMonthStart, index + 1)); + } + + return months; +} diff --git a/app/features/tournament-organization/routes/org.$slug.stats.module.css b/app/features/tournament-organization/routes/org.$slug.stats.module.css new file mode 100644 index 000000000..db0f9c5e8 --- /dev/null +++ b/app/features/tournament-organization/routes/org.$slug.stats.module.css @@ -0,0 +1,74 @@ +.statNumber { + font-size: 2.5rem; + font-weight: var(--weight-extra); + line-height: 1; +} + +.progress { + display: flex; + flex-direction: column; + gap: var(--s-2); +} + +.progressHeader { + display: flex; + align-items: baseline; + gap: var(--s-2); +} + +.progressTrack { + width: 100%; + height: 0.75rem; + border-radius: var(--radius-full); + background-color: var(--color-bg-higher); + overflow: hidden; +} + +.progressBar { + height: 100%; + border-radius: var(--radius-full); + background-color: var(--color-accent); + transition: width 0.3s ease; +} + +.progressBarMet { + background-color: var(--color-success); +} + +.breakdown { + display: flex; + flex-direction: column; + gap: var(--s-2); + margin-top: var(--s-2); +} + +.breakdownRow { + display: grid; + grid-template-columns: 6rem 1fr 2.5rem; + align-items: center; + gap: var(--s-3); +} + +.breakdownLabel { + font-size: var(--font-xs); + color: var(--color-text-high); +} + +.breakdownTrack { + height: 0.5rem; + border-radius: var(--radius-full); + background-color: var(--color-bg-higher); + overflow: hidden; +} + +.breakdownBar { + height: 100%; + border-radius: var(--radius-full); + background-color: var(--color-accent); +} + +.breakdownCount { + font-size: var(--font-sm); + font-weight: var(--weight-bold); + text-align: right; +} diff --git a/app/features/tournament-organization/routes/org.$slug.stats.test.ts b/app/features/tournament-organization/routes/org.$slug.stats.test.ts new file mode 100644 index 000000000..55f31a016 --- /dev/null +++ b/app/features/tournament-organization/routes/org.$slug.stats.test.ts @@ -0,0 +1,92 @@ +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; +import { dateToDatabaseTimestamp } from "~/utils/dates"; +import type { SerializeFrom } from "~/utils/remix"; +import { dbInsertUsers, dbReset, wrappedLoader } from "~/utils/Test"; +import { loader } from "../loaders/org.$slug.stats.server"; +import * as TournamentOrganizationRepository from "../TournamentOrganizationRepository.server"; +import { seedOrgEventWithParticipants } from "../test-utils"; +import { ESTABLISHED_ORG } from "../tournament-organization-constants"; + +const statsLoader = wrappedLoader>({ loader }); + +const createOrg = () => + TournamentOrganizationRepository.create({ ownerId: 1, name: "Org" }); + +describe("org stats loader", () => { + beforeEach(async () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date(2026, 0, 15)); + await dbInsertUsers(5); + }); + + afterEach(() => { + vi.useRealTimers(); + dbReset(); + }); + + test("throws when the user is not an org admin", async () => { + const org = await createOrg(); + + await expect( + statsLoader({ user: "regular", params: { slug: org.slug } }), + ).rejects.toThrow(); + }); + + test("allows an org admin", async () => { + const org = await createOrg(); + + const data = await statsLoader({ + user: "admin", + params: { slug: org.slug }, + }); + + expect(data.monthlyStats).toHaveLength(ESTABLISHED_ORG.MONTHS_CONSIDERED); + }); + + test("returns finished months most recent first, excluding the current month", async () => { + const org = await createOrg(); + + const data = await statsLoader({ + user: "admin", + params: { slug: org.slug }, + }); + + // system time is Jan 2026 -> most recent finished month is Dec 2025, + // and the current (ongoing) month is not included + expect(data.monthlyStats.map((m) => m.month)).toEqual([ + "2025-12", + "2025-11", + "2025-10", + "2025-09", + "2025-08", + "2025-07", + ]); + }); + + test("counts participants per month and averages over the considered months", async () => { + const org = await createOrg(); + + // 3 participants in December 2025 (a finished month) + await seedOrgEventWithParticipants({ + organizationId: org.id, + startTime: dateToDatabaseTimestamp(new Date(2025, 11, 10)), + participantUserIds: [1, 2, 3], + }); + // an event in the current month is ignored + await seedOrgEventWithParticipants({ + organizationId: org.id, + startTime: dateToDatabaseTimestamp(new Date(2026, 0, 5)), + participantUserIds: [1, 2, 3, 4, 5], + }); + + const data = await statsLoader({ + user: "admin", + params: { slug: org.slug }, + }); + + expect(data.monthlyStats[0]).toEqual({ month: "2025-12", count: 3 }); + expect(data.averageMonthlyParticipants).toBeCloseTo( + 3 / ESTABLISHED_ORG.MONTHS_CONSIDERED, + ); + }); +}); diff --git a/app/features/tournament-organization/routes/org.$slug.stats.tsx b/app/features/tournament-organization/routes/org.$slug.stats.tsx new file mode 100644 index 000000000..7cbf5124a --- /dev/null +++ b/app/features/tournament-organization/routes/org.$slug.stats.tsx @@ -0,0 +1,123 @@ +import clsx from "clsx"; +import { parse } from "date-fns"; +import { ProgressBar } from "react-aria-components"; +import { useTranslation } from "react-i18next"; +import { useLoaderData } from "react-router"; +import { Main } from "~/components/Main"; +import { Section } from "~/components/Section"; +import { useDateTimeFormat } from "~/hooks/intl/useDateTimeFormat"; +import type { SendouRouteHandle } from "~/utils/remix.server"; +import { loader } from "../loaders/org.$slug.stats.server"; +import { + ESTABLISHED_ORG, + MONTH_PARAM_FORMAT, +} from "../tournament-organization-constants"; +import styles from "./org.$slug.stats.module.css"; + +export { loader }; + +export const handle: SendouRouteHandle = { + i18n: ["org"], +}; + +export default function OrganizationStatsPage() { + return ( +
+ +
+ ); +} + +function EstablishedStatus() { + const { t } = useTranslation(["org"]); + const { formatter } = useDateTimeFormat({ month: "short", year: "numeric" }); + const { monthlyStats, averageMonthlyParticipants } = + useLoaderData(); + + const meetsThreshold = + averageMonthlyParticipants >= ESTABLISHED_ORG.GAIN_THRESHOLD; + + const maxCount = Math.max( + ESTABLISHED_ORG.GAIN_THRESHOLD, + ...monthlyStats.map((monthStat) => monthStat.count), + ); + + return ( +
+
+ + {({ percentage }) => ( + <> +
+ + {averageMonthlyParticipants.toFixed(1)} + + + / {ESTABLISHED_ORG.GAIN_THRESHOLD} + +
+
+
+
+ + )} + +
+ {t("org:stats.established.help", { + months: ESTABLISHED_ORG.MONTHS_CONSIDERED, + gain: ESTABLISHED_ORG.GAIN_THRESHOLD, + lose: ESTABLISHED_ORG.LOSE_THRESHOLD, + })} +
+
+ {monthlyStats.map((monthStat) => ( + + {({ percentage }) => ( + <> + + {formatMonth(monthStat.month, formatter)} + +
+
+
+ + {monthStat.count} + + + )} + + ))} +
+
+
+ ); +} + +function formatMonth( + monthString: string, + formatter: { format: (date: Date | number) => string | null }, +) { + const date = parse(monthString, MONTH_PARAM_FORMAT, new Date()); + return formatter.format(date) ?? undefined; +} diff --git a/app/features/tournament-organization/routes/org.$slug.tsx b/app/features/tournament-organization/routes/org.$slug.tsx index 93324dfe6..92fee3ec9 100644 --- a/app/features/tournament-organization/routes/org.$slug.tsx +++ b/app/features/tournament-organization/routes/org.$slug.tsx @@ -1,4 +1,11 @@ -import { Link as LinkIcon, Lock, LogOut, SquarePen, Users } from "lucide-react"; +import { + ChartNoAxesColumn, + Link as LinkIcon, + Lock, + LogOut, + SquarePen, + Users, +} from "lucide-react"; import { useTranslation } from "react-i18next"; import type { MetaFunction } from "react-router"; import { Link, useLoaderData, useSearchParams } from "react-router"; @@ -33,6 +40,7 @@ import { navIconUrl, tournamentOrganizationEditPage, tournamentOrganizationPage, + tournamentOrganizationStatsPage, tournamentPage, userPage, } from "~/utils/urls"; @@ -118,8 +126,9 @@ function LogoHeader() { const currentMember = user ? data.organization.members.find((m) => m.id === user.id) : undefined; + const isOrgAdmin = currentMember?.role === "ADMIN"; const isSoleAdmin = - currentMember?.role === "ADMIN" && + isOrgAdmin && data.organization.members.filter((m) => m.role === "ADMIN").length === 1; return ( @@ -140,6 +149,17 @@ function LogoHeader() { {t("common:actions.edit")}
) : null} + {isOrgAdmin ? ( + } + size="small" + variant="outlined" + testId="org-stats-button" + > + {t("org:stats.title")} + + ) : null} {currentMember ? ( isSoleAdmin ? ( ({ + matchGameResultId: gameResult.id, + userId, + tournamentTeamId: team.id, + })), + ) + .execute(); + + return { tournamentId, teamId: team.id }; +} diff --git a/app/features/tournament-organization/tournament-organization-constants.ts b/app/features/tournament-organization/tournament-organization-constants.ts index 536e70132..415ccc208 100644 --- a/app/features/tournament-organization/tournament-organization-constants.ts +++ b/app/features/tournament-organization/tournament-organization-constants.ts @@ -1,6 +1,14 @@ export const TOURNAMENT_SERIES_EVENTS_PER_PAGE = 20; export const TOURNAMENT_SERIES_LEADERBOARD_SIZE = 50; +export const MONTH_PARAM_FORMAT = "yyyy-MM"; + +export const ESTABLISHED_ORG = { + MONTHS_CONSIDERED: 6, + GAIN_THRESHOLD: 150, + LOSE_THRESHOLD: 100, +}; + export const TOURNAMENT_ORGANIZATION = { DESCRIPTION_MAX_LENGTH: 1_000, BAN_REASON_MAX_LENGTH: 200, diff --git a/app/features/tournament/tournament-test-utils.ts b/app/features/tournament/tournament-test-utils.ts index f2f5a756d..2b6206579 100644 --- a/app/features/tournament/tournament-test-utils.ts +++ b/app/features/tournament/tournament-test-utils.ts @@ -9,9 +9,19 @@ import * as TournamentTeamRepository from "./TournamentTeamRepository.server"; /** * Creates a mock tournament with one single elimination bracket. + * + * @returns The created event and tournament ids. */ -export async function dbInsertTournament() { - await CalendarRepository.create({ +export async function dbInsertTournament({ + organizationId = null, + startTime = null, +}: { + /** Organization hosting the tournament. Defaults to no organization. */ + organizationId?: number | null; + /** Event start time as a database timestamp (seconds). Defaults to now. */ + startTime?: number | null; +} = {}) { + return CalendarRepository.create({ isFullTournament: true, authorId: 1, badges: [], @@ -19,9 +29,9 @@ export async function dbInsertTournament() { description: null, discordInviteCode: "test-discord", name: "Test Tournament", - organizationId: null, + organizationId, rules: null, - startTimes: [databaseTimestampNow()], + startTimes: [startTime ?? databaseTimestampNow()], tags: null, bracketProgression: [ { diff --git a/app/routes.ts b/app/routes.ts index 0e3395c83..9f414bd4a 100644 --- a/app/routes.ts +++ b/app/routes.ts @@ -181,6 +181,10 @@ export default [ ...prefix("/org/:slug", [ index("features/tournament-organization/routes/org.$slug.tsx"), route("edit", "features/tournament-organization/routes/org.$slug.edit.tsx"), + route( + "stats", + "features/tournament-organization/routes/org.$slug.stats.tsx", + ), ]), route("/faq", "features/info/routes/faq.tsx"), diff --git a/app/utils/urls.ts b/app/utils/urls.ts index d1a79ce9c..98db9a442 100644 --- a/app/utils/urls.ts +++ b/app/utils/urls.ts @@ -410,6 +410,8 @@ export const tournamentOrganizationPage = ({ }; export const tournamentOrganizationEditPage = (organizationSlug: string) => `${tournamentOrganizationPage({ organizationSlug })}/edit`; +export const tournamentOrganizationStatsPage = (organizationSlug: string) => + `${tournamentOrganizationPage({ organizationSlug })}/stats`; export const sendouQInviteLink = (inviteCode: string) => `${SENDOUQ_PAGE}?${JOIN_CODE_SEARCH_PARAM_KEY}=${inviteCode}`; diff --git a/locales/da/org.json b/locales/da/org.json index 9f378ba20..c4a8fa9df 100644 --- a/locales/da/org.json +++ b/locales/da/org.json @@ -43,5 +43,8 @@ "leave.confirm": "", "leave.soleAdmin": "", "new.heading": "", - "new.noPermissions": "" + "new.noPermissions": "", + "stats.title": "", + "stats.established.title": "", + "stats.established.help": "" } diff --git a/locales/de/org.json b/locales/de/org.json index 9f378ba20..c4a8fa9df 100644 --- a/locales/de/org.json +++ b/locales/de/org.json @@ -43,5 +43,8 @@ "leave.confirm": "", "leave.soleAdmin": "", "new.heading": "", - "new.noPermissions": "" + "new.noPermissions": "", + "stats.title": "", + "stats.established.title": "", + "stats.established.help": "" } diff --git a/locales/en/org.json b/locales/en/org.json index c82e2ae6b..cbc571502 100644 --- a/locales/en/org.json +++ b/locales/en/org.json @@ -43,5 +43,8 @@ "leave.confirm": "Are you sure you want to leave {{organizationName}}?", "leave.soleAdmin": "You are the only admin of this organization. Add another admin first or ask a site administrator to delete it.", "new.heading": "New Organization", - "new.noPermissions": "No permissions to add organizations. Organizations can be created by users with tournament adder permissions." + "new.noPermissions": "No permissions to add organizations. Organizations can be created by users with tournament adder permissions.", + "stats.title": "Stats", + "stats.established.title": "Established status", + "stats.established.help": "Average active players over the last {{months}} months. Reach {{gain}} to become established, drop below {{lose}} to lose it. Check the FAQ page for more information on established organizations." } diff --git a/locales/es-ES/org.json b/locales/es-ES/org.json index 9f12eb2bb..dbc7a0038 100644 --- a/locales/es-ES/org.json +++ b/locales/es-ES/org.json @@ -43,5 +43,8 @@ "leave.confirm": "¿Seguro que quieres abandonar {{organizationName}}?", "leave.soleAdmin": "Eres el único admin de esta organización. Añade otro admin primero o pide a un administrador del sitio que la elimine.", "new.heading": "Nueva organización", - "new.noPermissions": "Sin permisos para añadir organizaciones. Las organizaciones pueden ser creadas por usuarios con permisos de organizador de torneos." + "new.noPermissions": "Sin permisos para añadir organizaciones. Las organizaciones pueden ser creadas por usuarios con permisos de organizador de torneos.", + "stats.title": "", + "stats.established.title": "", + "stats.established.help": "" } diff --git a/locales/es-US/org.json b/locales/es-US/org.json index 4a7bc44cc..02e055556 100644 --- a/locales/es-US/org.json +++ b/locales/es-US/org.json @@ -43,5 +43,8 @@ "leave.confirm": "", "leave.soleAdmin": "", "new.heading": "", - "new.noPermissions": "" + "new.noPermissions": "", + "stats.title": "", + "stats.established.title": "", + "stats.established.help": "" } diff --git a/locales/fr-CA/org.json b/locales/fr-CA/org.json index 9f378ba20..c4a8fa9df 100644 --- a/locales/fr-CA/org.json +++ b/locales/fr-CA/org.json @@ -43,5 +43,8 @@ "leave.confirm": "", "leave.soleAdmin": "", "new.heading": "", - "new.noPermissions": "" + "new.noPermissions": "", + "stats.title": "", + "stats.established.title": "", + "stats.established.help": "" } diff --git a/locales/fr-EU/org.json b/locales/fr-EU/org.json index dd775fa33..efc04bb06 100644 --- a/locales/fr-EU/org.json +++ b/locales/fr-EU/org.json @@ -43,5 +43,8 @@ "leave.confirm": "", "leave.soleAdmin": "", "new.heading": "", - "new.noPermissions": "" + "new.noPermissions": "", + "stats.title": "", + "stats.established.title": "", + "stats.established.help": "" } diff --git a/locales/he/org.json b/locales/he/org.json index 9f378ba20..c4a8fa9df 100644 --- a/locales/he/org.json +++ b/locales/he/org.json @@ -43,5 +43,8 @@ "leave.confirm": "", "leave.soleAdmin": "", "new.heading": "", - "new.noPermissions": "" + "new.noPermissions": "", + "stats.title": "", + "stats.established.title": "", + "stats.established.help": "" } diff --git a/locales/it/org.json b/locales/it/org.json index 430387f20..2b427a26f 100644 --- a/locales/it/org.json +++ b/locales/it/org.json @@ -43,5 +43,8 @@ "leave.confirm": "", "leave.soleAdmin": "", "new.heading": "", - "new.noPermissions": "" + "new.noPermissions": "", + "stats.title": "", + "stats.established.title": "", + "stats.established.help": "" } diff --git a/locales/ja/org.json b/locales/ja/org.json index 9f378ba20..c4a8fa9df 100644 --- a/locales/ja/org.json +++ b/locales/ja/org.json @@ -43,5 +43,8 @@ "leave.confirm": "", "leave.soleAdmin": "", "new.heading": "", - "new.noPermissions": "" + "new.noPermissions": "", + "stats.title": "", + "stats.established.title": "", + "stats.established.help": "" } diff --git a/locales/ko/org.json b/locales/ko/org.json index 9f378ba20..c4a8fa9df 100644 --- a/locales/ko/org.json +++ b/locales/ko/org.json @@ -43,5 +43,8 @@ "leave.confirm": "", "leave.soleAdmin": "", "new.heading": "", - "new.noPermissions": "" + "new.noPermissions": "", + "stats.title": "", + "stats.established.title": "", + "stats.established.help": "" } diff --git a/locales/nl/org.json b/locales/nl/org.json index 9f378ba20..c4a8fa9df 100644 --- a/locales/nl/org.json +++ b/locales/nl/org.json @@ -43,5 +43,8 @@ "leave.confirm": "", "leave.soleAdmin": "", "new.heading": "", - "new.noPermissions": "" + "new.noPermissions": "", + "stats.title": "", + "stats.established.title": "", + "stats.established.help": "" } diff --git a/locales/pl/org.json b/locales/pl/org.json index 9f378ba20..c4a8fa9df 100644 --- a/locales/pl/org.json +++ b/locales/pl/org.json @@ -43,5 +43,8 @@ "leave.confirm": "", "leave.soleAdmin": "", "new.heading": "", - "new.noPermissions": "" + "new.noPermissions": "", + "stats.title": "", + "stats.established.title": "", + "stats.established.help": "" } diff --git a/locales/pt-BR/org.json b/locales/pt-BR/org.json index 9f378ba20..c4a8fa9df 100644 --- a/locales/pt-BR/org.json +++ b/locales/pt-BR/org.json @@ -43,5 +43,8 @@ "leave.confirm": "", "leave.soleAdmin": "", "new.heading": "", - "new.noPermissions": "" + "new.noPermissions": "", + "stats.title": "", + "stats.established.title": "", + "stats.established.help": "" } diff --git a/locales/ru/org.json b/locales/ru/org.json index 0e1375483..6eefa3f4f 100644 --- a/locales/ru/org.json +++ b/locales/ru/org.json @@ -43,5 +43,8 @@ "leave.confirm": "", "leave.soleAdmin": "", "new.heading": "", - "new.noPermissions": "" + "new.noPermissions": "", + "stats.title": "", + "stats.established.title": "", + "stats.established.help": "" } diff --git a/locales/zh/org.json b/locales/zh/org.json index b4ce31ad1..da7350609 100644 --- a/locales/zh/org.json +++ b/locales/zh/org.json @@ -43,5 +43,8 @@ "leave.confirm": "您确定要退出 {{organizationName}} 吗?", "leave.soleAdmin": "您是该组织唯一的管理员。请先添加另一位管理员,或者联系网站管理员删除该组织。", "new.heading": "创建组织", - "new.noPermissions": "您没有创建组织的权限。只有拥有赛事创建权限的用户才能创建组织。" + "new.noPermissions": "您没有创建组织的权限。只有拥有赛事创建权限的用户才能创建组织。", + "stats.title": "", + "stats.established.title": "", + "stats.established.help": "" } From 970e660b1c2d5685a2c4d428acac98364eaae73c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 20:37:01 +0300 Subject: [PATCH 10/12] build(deps-dev): bump @types/node from 25.9.3 to 26.0.0 (#3201) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 92 +++++++++++++++++++++++++------------------------- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/package.json b/package.json index da4b790f0..7db0178d3 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "@playwright/test": "1.60.0", "@react-router/dev": "7.17.0", "@types/better-sqlite3": "7.6.13", - "@types/node": "25.9.3", + "@types/node": "26.0.0", "@types/node-cron": "3.0.11", "@types/nprogress": "0.2.3", "@types/react": "19.2.17", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eff773adb..7309b6ed1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -201,13 +201,13 @@ importers: version: 1.60.0 '@react-router/dev': specifier: 7.17.0 - version: 7.17.0(@react-router/serve@7.15.0(patch_hash=38a61caab4f8dc67c82ecfb17507d5a650ea7b632b4e58a369c0aa4583611c87)(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3))(@types/node@25.9.3)(jiti@2.7.0)(lightningcss@1.32.0)(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))(yaml@2.9.0) + version: 7.17.0(@react-router/serve@7.15.0(patch_hash=38a61caab4f8dc67c82ecfb17507d5a650ea7b632b4e58a369c0aa4583611c87)(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3))(@types/node@26.0.0)(jiti@2.7.0)(lightningcss@1.32.0)(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))(yaml@2.9.0) '@types/better-sqlite3': specifier: 7.6.13 version: 7.6.13 '@types/node': - specifier: 25.9.3 - version: 25.9.3 + specifier: 26.0.0 + version: 26.0.0 '@types/node-cron': specifier: 3.0.11 version: 3.0.11 @@ -225,7 +225,7 @@ importers: version: 3.6.4 '@vitest/browser-playwright': specifier: 4.1.8 - version: 4.1.8(playwright@1.60.0)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.8) + version: 4.1.8(playwright@1.60.0)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.8) '@vitest/ui': specifier: 4.1.8 version: 4.1.8(vitest@4.1.8) @@ -252,16 +252,16 @@ importers: version: 6.0.3 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) + version: 8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) vite-node: specifier: 6.0.0 - version: 6.0.0(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) + version: 6.0.0(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) vite-plugin-babel: specifier: 1.7.3 - version: 1.7.3(@babel/core@7.29.7)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + version: 1.7.3(@babel/core@7.29.7)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) vitest: specifier: 4.1.8 - version: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@25.9.3)(@vitest/browser-playwright@4.1.8)(@vitest/ui@4.1.8)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + version: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@26.0.0)(@vitest/browser-playwright@4.1.8)(@vitest/ui@4.1.8)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) vitest-browser-react: specifier: 2.2.0 version: 2.2.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.8) @@ -2987,8 +2987,8 @@ packages: '@types/node-cron@3.0.11': resolution: {integrity: sha512-0ikrnug3/IyneSHqCBeslAhlK2aBfYek1fGo4bP4QnZPmiqSGRK+Oy7ZMisLWkesffJvQ1cqAcBnJC+8+nxIAg==} - '@types/node@25.9.3': - resolution: {integrity: sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==} + '@types/node@26.0.0': + resolution: {integrity: sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==} '@types/nprogress@0.2.3': resolution: {integrity: sha512-k7kRA033QNtC+gLc4VPlfnue58CM1iQLgn1IMAU8VPHGOj7oIHPp9UlhedEnD/Gl8evoCjwkZjlBORtZ3JByUA==} @@ -4739,8 +4739,8 @@ packages: resolution: {integrity: sha512-FeFPZ/WFT0mbRCuydiZzpPFlrYN8ZUpphQKoq4EeElVIYjYyGzPMxQR/simUwCOJIyVhpFk4RbtyO7RuMpMnHA==} engines: {node: '>=14'} - undici-types@7.24.6: - resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + undici-types@8.3.0: + resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} @@ -6306,7 +6306,7 @@ snapshots: '@radix-ui/rect@1.1.1': {} - '@react-router/dev@7.17.0(@react-router/serve@7.15.0(patch_hash=38a61caab4f8dc67c82ecfb17507d5a650ea7b632b4e58a369c0aa4583611c87)(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3))(@types/node@25.9.3)(jiti@2.7.0)(lightningcss@1.32.0)(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))(yaml@2.9.0)': + '@react-router/dev@7.17.0(@react-router/serve@7.15.0(patch_hash=38a61caab4f8dc67c82ecfb17507d5a650ea7b632b4e58a369c0aa4583611c87)(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3))(@types/node@26.0.0)(jiti@2.7.0)(lightningcss@1.32.0)(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))(yaml@2.9.0)': dependencies: '@babel/core': 7.29.7 '@babel/generator': 7.29.7 @@ -6336,8 +6336,8 @@ snapshots: semver: 7.8.4 tinyglobby: 0.2.17 valibot: 1.4.1(typescript@6.0.3) - vite: 8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) - vite-node: 3.2.4(@types/node@25.9.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0) + vite: 8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) + vite-node: 3.2.4(@types/node@26.0.0)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0) optionalDependencies: '@react-router/serve': 7.15.0(patch_hash=38a61caab4f8dc67c82ecfb17507d5a650ea7b632b4e58a369c0aa4583611c87)(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) typescript: 6.0.3 @@ -7860,7 +7860,7 @@ snapshots: '@types/better-sqlite3@7.6.13': dependencies: - '@types/node': 25.9.3 + '@types/node': 26.0.0 '@types/chai@5.2.3': dependencies: @@ -7884,9 +7884,9 @@ snapshots: '@types/node-cron@3.0.11': {} - '@types/node@25.9.3': + '@types/node@26.0.0': dependencies: - undici-types: 7.24.6 + undici-types: 8.3.0 '@types/nprogress@0.2.3': {} @@ -7906,7 +7906,7 @@ snapshots: '@types/web-push@3.6.4': dependencies: - '@types/node': 25.9.3 + '@types/node': 26.0.0 '@use-gesture/core@10.3.1': {} @@ -7915,29 +7915,29 @@ snapshots: '@use-gesture/core': 10.3.1 react: 19.2.7 - '@vitest/browser-playwright@4.1.8(playwright@1.60.0)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.8)': + '@vitest/browser-playwright@4.1.8(playwright@1.60.0)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.8)': dependencies: - '@vitest/browser': 4.1.8(vite@8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.8) - '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + '@vitest/browser': 4.1.8(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.8) + '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) playwright: 1.60.0 tinyrainbow: 3.1.0 - vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@25.9.3)(@vitest/browser-playwright@4.1.8)(@vitest/ui@4.1.8)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@26.0.0)(@vitest/browser-playwright@4.1.8)(@vitest/ui@4.1.8)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite - '@vitest/browser@4.1.8(vite@8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.8)': + '@vitest/browser@4.1.8(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.8)': dependencies: '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) '@vitest/utils': 4.1.8 magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.0 - vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@25.9.3)(@vitest/browser-playwright@4.1.8)(@vitest/ui@4.1.8)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@26.0.0)(@vitest/browser-playwright@4.1.8)(@vitest/ui@4.1.8)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) ws: 8.21.0 transitivePeerDependencies: - bufferutil @@ -7954,13 +7954,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.8(vite@8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))': + '@vitest/mocker@4.1.8(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.8 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) + vite: 8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) '@vitest/pretty-format@4.1.8': dependencies: @@ -7989,7 +7989,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@25.9.3)(@vitest/browser-playwright@4.1.8)(@vitest/ui@4.1.8)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@26.0.0)(@vitest/browser-playwright@4.1.8)(@vitest/ui@4.1.8)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) '@vitest/utils@4.1.8': dependencies: @@ -9711,7 +9711,7 @@ snapshots: unbash@3.0.0: {} - undici-types@7.24.6: {} + undici-types@8.3.0: {} universalify@2.0.1: {} @@ -9752,13 +9752,13 @@ snapshots: vary@1.1.2: {} - vite-node@3.2.4(@types/node@25.9.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0): + vite-node@3.2.4(@types/node@26.0.0)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.3.5(@types/node@25.9.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0) + vite: 7.3.5(@types/node@26.0.0)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0) transitivePeerDependencies: - '@types/node' - jiti @@ -9773,13 +9773,13 @@ snapshots: - tsx - yaml - vite-node@6.0.0(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0): + vite-node@6.0.0(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0): dependencies: cac: 7.0.0 es-module-lexer: 2.0.0 obug: 2.1.1 pathe: 2.0.3 - vite: 8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) + vite: 8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) transitivePeerDependencies: - '@types/node' - '@vitejs/devtools' @@ -9794,12 +9794,12 @@ snapshots: - tsx - yaml - vite-plugin-babel@1.7.3(@babel/core@7.29.7)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)): + vite-plugin-babel@1.7.3(@babel/core@7.29.7)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)): dependencies: '@babel/core': 7.29.7 - vite: 8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) + vite: 8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) - vite@7.3.5(@types/node@25.9.3)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0): + vite@7.3.5(@types/node@26.0.0)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0): dependencies: esbuild: 0.27.7 fdir: 6.5.0(picomatch@4.0.4) @@ -9808,13 +9808,13 @@ snapshots: rollup: 4.61.1 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 25.9.3 + '@types/node': 26.0.0 fsevents: 2.3.3 jiti: 2.7.0 lightningcss: 1.32.0 yaml: 2.9.0 - vite@8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0): + vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -9822,7 +9822,7 @@ snapshots: rolldown: 1.0.3 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 25.9.3 + '@types/node': 26.0.0 esbuild: 0.27.7 fsevents: 2.3.3 jiti: 2.7.0 @@ -9832,15 +9832,15 @@ snapshots: dependencies: react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@25.9.3)(@vitest/browser-playwright@4.1.8)(@vitest/ui@4.1.8)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@26.0.0)(@vitest/browser-playwright@4.1.8)(@vitest/ui@4.1.8)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) optionalDependencies: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - vitest@4.1.8(@opentelemetry/api@1.9.1)(@types/node@25.9.3)(@vitest/browser-playwright@4.1.8)(@vitest/ui@4.1.8)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)): + vitest@4.1.8(@opentelemetry/api@1.9.1)(@types/node@26.0.0)(@vitest/browser-playwright@4.1.8)(@vitest/ui@4.1.8)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.8 - '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.8 '@vitest/runner': 4.1.8 '@vitest/snapshot': 4.1.8 @@ -9857,12 +9857,12 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) + vite: 8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.1 - '@types/node': 25.9.3 - '@vitest/browser-playwright': 4.1.8(playwright@1.60.0)(vite@8.0.16(@types/node@25.9.3)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.8) + '@types/node': 26.0.0 + '@vitest/browser-playwright': 4.1.8(playwright@1.60.0)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.8) '@vitest/ui': 4.1.8(vitest@4.1.8) transitivePeerDependencies: - msw From 5239725777d39c3e076cea7dcbbfb9f93beacb07 Mon Sep 17 00:00:00 2001 From: Inkorest <95531127+Inkorest@users.noreply.github.com> Date: Thu, 2 Jul 2026 01:37:46 +0800 Subject: [PATCH 11/12] locale(zh): complete localization for new keys (#3204) --- locales/zh/common.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/zh/common.json b/locales/zh/common.json index 30383ba8d..9e6c271d3 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -141,7 +141,7 @@ "actions.noOutline": "无描边", "actions.join": "加入", "host": "房主", - "seed": "", + "seed": "{{number}} 号种子", "actions.nevermind": "反悔", "actions.clickHere": "点击此处", "actions.goBack": "返回", From 8f482802baef00c296ce120f06dab9f159db3d34 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:38:26 +0300 Subject: [PATCH 12/12] Upgrade to React Router 8 --- app/components/layout/NotificationPopover.tsx | 2 +- .../api-public/api-action-wrapper.server.ts | 7 +- app/features/art/routes/art.tsx | 2 +- app/features/articles/routes/a.$slug.tsx | 4 +- app/features/auth/core/user.ts | 2 +- .../badges/routes/badges.$id.edit.tsx | 2 +- .../routes/builds.$slug.popular.tsx | 10 +- .../build-stats/routes/builds.$slug.stats.tsx | 10 +- app/features/builds/routes/builds.$slug.tsx | 10 +- app/features/calendar/routes/calendar.$id.tsx | 4 +- app/features/calendar/routes/calendar.new.tsx | 8 +- app/features/chat/ChatProvider.tsx | 2 +- .../leaderboards/routes/leaderboards.tsx | 2 +- app/features/params/routes/params.$slug.tsx | 8 +- ...plus.suggestions.comment.$tier.$userId.tsx | 2 +- .../routes/plus.suggestions.new.tsx | 2 +- .../sendouq-match/routes/q.match.$id.tsx | 2 +- app/features/settings/components/ThemeTab.tsx | 2 +- .../team/components/TeamGoBackButton.tsx | 2 +- .../team/routes/t.$customUrl.index.tsx | 4 +- app/features/team/routes/t.$customUrl.tsx | 12 +- .../top-search/routes/xsearch.player.$id.tsx | 12 +- .../routes/org.$slug.tsx | 12 +- .../tournament/routes/to.$id.info.tsx | 9 +- .../tournament/routes/to.$id.teams.$tid.tsx | 6 +- app/features/tournament/routes/to.$id.tsx | 4 +- .../user-page/routes/u.$identifier.admin.tsx | 2 +- .../user-page/routes/u.$identifier.art.tsx | 2 +- .../routes/u.$identifier.builds.new.tsx | 2 +- .../user-page/routes/u.$identifier.builds.tsx | 4 +- .../user-page/routes/u.$identifier.edit.tsx | 2 +- .../user-page/routes/u.$identifier.index.tsx | 4 +- .../routes/u.$identifier.results.tsx | 2 +- .../routes/u.$identifier.seasons.tsx | 8 +- .../user-page/routes/u.$identifier.tsx | 12 +- .../user-page/routes/u.$identifier.vods.tsx | 2 +- app/features/vods/routes/vods.$id.tsx | 6 +- app/root.tsx | 4 +- app/utils/remix.server.ts | 5 +- .../request-context-middleware.server.ts | 16 + app/utils/request-context.server.ts | 26 + package.json | 10 +- patches/@react-router__serve@7.15.0.patch | 12 - patches/@react-router__serve@8.1.0.patch | 12 + pnpm-lock.yaml | 1479 +++++------------ pnpm-workspace.yaml | 2 +- react-router.config.ts | 11 +- 47 files changed, 609 insertions(+), 1156 deletions(-) create mode 100644 app/utils/request-context-middleware.server.ts create mode 100644 app/utils/request-context.server.ts delete mode 100644 patches/@react-router__serve@7.15.0.patch create mode 100644 patches/@react-router__serve@8.1.0.patch diff --git a/app/components/layout/NotificationPopover.tsx b/app/components/layout/NotificationPopover.tsx index 98d7db1ee..fb38641f4 100644 --- a/app/components/layout/NotificationPopover.tsx +++ b/app/components/layout/NotificationPopover.tsx @@ -22,7 +22,7 @@ export type LoaderNotification = NonNullable< export function useNotifications() { const [root] = useMatches(); - const notifications = (root.data as RootLoaderData | undefined) + const notifications = (root.loaderData as RootLoaderData | undefined) ?.notifications; const unseenIds = React.useMemo( diff --git a/app/features/api-public/api-action-wrapper.server.ts b/app/features/api-public/api-action-wrapper.server.ts index c7e7b9030..6c450d9d2 100644 --- a/app/features/api-public/api-action-wrapper.server.ts +++ b/app/features/api-public/api-action-wrapper.server.ts @@ -23,10 +23,9 @@ export async function wrapActionForApi( } catch (e) { if (e instanceof Response && e.status === 302) { const location = e.headers.get("Location") ?? ""; - if (location.includes("__error=")) { - const errorMsg = new URLSearchParams(location.replace("?", "")).get( - "__error", - ); + const search = location.slice(location.indexOf("?") + 1); + const errorMsg = new URLSearchParams(search).get("__error"); + if (errorMsg !== null) { return new Response(JSON.stringify({ error: errorMsg }), { status: 400, headers: { "Content-Type": "application/json" }, diff --git a/app/features/art/routes/art.tsx b/app/features/art/routes/art.tsx index 4c6806421..7e472067e 100644 --- a/app/features/art/routes/art.tsx +++ b/app/features/art/routes/art.tsx @@ -54,7 +54,7 @@ export const handle: SendouRouteHandle = { }; export const meta: MetaFunction = (args) => { - const data = args.data as SerializeFrom | null; + const data = args.loaderData as SerializeFrom | null; if (!data) return []; diff --git a/app/features/articles/routes/a.$slug.tsx b/app/features/articles/routes/a.$slug.tsx index dcd68a7a3..69e0e6d22 100644 --- a/app/features/articles/routes/a.$slug.tsx +++ b/app/features/articles/routes/a.$slug.tsx @@ -19,7 +19,7 @@ export { loader }; export const handle: SendouRouteHandle = { breadcrumb: ({ match }) => { - const data = match.data as SerializeFrom | undefined; + const data = match.loaderData as SerializeFrom | undefined; if (!data) return []; @@ -35,7 +35,7 @@ export const handle: SendouRouteHandle = { export const meta: MetaFunction = (args) => { invariant(args.params.slug); - const data = args.data as SerializeFrom | null; + const data = args.loaderData as SerializeFrom | null; if (!data) return []; diff --git a/app/features/auth/core/user.ts b/app/features/auth/core/user.ts index ad2bea266..1efdb83d0 100644 --- a/app/features/auth/core/user.ts +++ b/app/features/auth/core/user.ts @@ -4,5 +4,5 @@ import type { RootLoaderData } from "~/root"; export function useUser() { const [root] = useMatches(); - return (root.data as RootLoaderData | undefined)?.user; + return (root.loaderData as RootLoaderData | undefined)?.user; } diff --git a/app/features/badges/routes/badges.$id.edit.tsx b/app/features/badges/routes/badges.$id.edit.tsx index c55c4c55f..2d712606c 100644 --- a/app/features/badges/routes/badges.$id.edit.tsx +++ b/app/features/badges/routes/badges.$id.edit.tsx @@ -18,7 +18,7 @@ export default function EditBadgePage() { const isStaff = useHasRole("STAFF"); const matches = useMatches(); const parentMatch = matches.at(-2)!; - const data = parentMatch.data as BadgeDetailsLoaderData; + const data = parentMatch.loaderData as BadgeDetailsLoaderData; const { badge } = useOutletContext(); const canManageBadge = useHasPermission(badge, "MANAGE"); diff --git a/app/features/build-stats/routes/builds.$slug.popular.tsx b/app/features/build-stats/routes/builds.$slug.popular.tsx index 62ecd411d..89ba815f4 100644 --- a/app/features/build-stats/routes/builds.$slug.popular.tsx +++ b/app/features/build-stats/routes/builds.$slug.popular.tsx @@ -18,12 +18,12 @@ import { loader } from "../loaders/builds.$slug.popular.server"; export { loader }; export const meta: MetaFunction = (args) => { - if (!args.data) return []; + if (!args.loaderData) return []; return metaTags({ - title: `${args.data.weaponName} popular builds`, - ogTitle: `${args.data.weaponName} Splatoon 3 popular builds`, - description: `List of most popular ability combinations for ${args.data.weaponName}.`, + title: `${args.loaderData.weaponName} popular builds`, + ogTitle: `${args.loaderData.weaponName} Splatoon 3 popular builds`, + description: `List of most popular ability combinations for ${args.loaderData.weaponName}.`, location: args.location, }); }; @@ -31,7 +31,7 @@ export const meta: MetaFunction = (args) => { export const handle: SendouRouteHandle = { i18n: ["analyzer", "builds"], breadcrumb: ({ match }) => { - const data = match.data as SerializeFrom | undefined; + const data = match.loaderData as SerializeFrom | undefined; if (!data) return []; diff --git a/app/features/build-stats/routes/builds.$slug.stats.tsx b/app/features/build-stats/routes/builds.$slug.stats.tsx index 8695cc824..fc52252d7 100644 --- a/app/features/build-stats/routes/builds.$slug.stats.tsx +++ b/app/features/build-stats/routes/builds.$slug.stats.tsx @@ -21,12 +21,12 @@ import { MAX_AP } from "~/features/build-analyzer/analyzer-constants"; import styles from "./builds.$slug.stats.module.css"; export const meta: MetaFunction = (args) => { - if (!args.data) return []; + if (!args.loaderData) return []; return metaTags({ - title: `${args.data.weaponName} popular abilities`, - ogTitle: `${args.data.weaponName} Splatoon 3 popular abilities`, - description: `List of the most popular abilities for ${args.data.weaponName} in Splatoon 3.`, + title: `${args.loaderData.weaponName} popular abilities`, + ogTitle: `${args.loaderData.weaponName} Splatoon 3 popular abilities`, + description: `List of the most popular abilities for ${args.loaderData.weaponName} in Splatoon 3.`, location: args.location, }); }; @@ -34,7 +34,7 @@ export const meta: MetaFunction = (args) => { export const handle: SendouRouteHandle = { i18n: ["weapons", "builds", "analyzer"], breadcrumb: ({ match }) => { - const data = match.data as SerializeFrom | undefined; + const data = match.loaderData as SerializeFrom | undefined; if (!data) return []; diff --git a/app/features/builds/routes/builds.$slug.tsx b/app/features/builds/routes/builds.$slug.tsx index 63d99efa4..d98472107 100644 --- a/app/features/builds/routes/builds.$slug.tsx +++ b/app/features/builds/routes/builds.$slug.tsx @@ -129,12 +129,12 @@ function filterKey(filter: ParsedFilter): string { } export const meta: MetaFunction = (args) => { - if (!args.data) return []; + if (!args.loaderData) return []; return metaTags({ - title: `${args.data.weaponName} builds`, - ogTitle: `${args.data.weaponName} Splatoon 3 builds`, - description: `Collection of ${args.data.weaponName} builds from the top competitive players. Find the best combination of abilities and level up your gameplay.`, + title: `${args.loaderData.weaponName} builds`, + ogTitle: `${args.loaderData.weaponName} Splatoon 3 builds`, + description: `Collection of ${args.loaderData.weaponName} builds from the top competitive players. Find the best combination of abilities and level up your gameplay.`, location: args.location, }); }; @@ -142,7 +142,7 @@ export const meta: MetaFunction = (args) => { export const handle: SendouRouteHandle = { i18n: ["weapons", "builds", "gear", "analyzer"], breadcrumb: ({ match }) => { - const data = match.data as SerializeFrom | undefined; + const data = match.loaderData as SerializeFrom | undefined; if (!data) return []; diff --git a/app/features/calendar/routes/calendar.$id.tsx b/app/features/calendar/routes/calendar.$id.tsx index a786ac54c..5d5372f8f 100644 --- a/app/features/calendar/routes/calendar.$id.tsx +++ b/app/features/calendar/routes/calendar.$id.tsx @@ -41,7 +41,7 @@ import { loader } from "../loaders/calendar.$id.server"; export { action, loader }; export const meta: MetaFunction = (args) => { - const data = args.data as SerializeFrom; + const data = args.loaderData as SerializeFrom; if (!data) return []; @@ -57,7 +57,7 @@ export const meta: MetaFunction = (args) => { export const handle: SendouRouteHandle = { i18n: ["calendar", "game-misc"], breadcrumb: ({ match }) => { - const data = match.data as SerializeFrom | undefined; + const data = match.loaderData as SerializeFrom | undefined; if (!data) return []; diff --git a/app/features/calendar/routes/calendar.new.tsx b/app/features/calendar/routes/calendar.new.tsx index 4b7e27bde..da200c7eb 100644 --- a/app/features/calendar/routes/calendar.new.tsx +++ b/app/features/calendar/routes/calendar.new.tsx @@ -34,12 +34,14 @@ import { loader } from "../loaders/calendar.new.server"; export { action, loader }; export const meta: MetaFunction = (args) => { - if (!args.data) return []; + if (!args.loaderData) return []; - const what = args.data.isAddingTournament ? "tournament" : "calendar event"; + const what = args.loaderData.isAddingTournament + ? "tournament" + : "calendar event"; return metaTags({ - title: args.data.eventToEdit ? `Editing ${what}` : `New ${what}`, + title: args.loaderData.eventToEdit ? `Editing ${what}` : `New ${what}`, location: args.location, }); }; diff --git a/app/features/chat/ChatProvider.tsx b/app/features/chat/ChatProvider.tsx index 5b7c28eb7..aa9110853 100644 --- a/app/features/chat/ChatProvider.tsx +++ b/app/features/chat/ChatProvider.tsx @@ -763,7 +763,7 @@ export function useCurrentRouteChatCodes(): string[] { const matches = useMatches(); for (const match of matches) { - const matchData = match.data as + const matchData = match.loaderData as | { chatCode?: string | string[] } | undefined; if (matchData?.chatCode) { diff --git a/app/features/leaderboards/routes/leaderboards.tsx b/app/features/leaderboards/routes/leaderboards.tsx index 0e5fe9842..a968c9da0 100644 --- a/app/features/leaderboards/routes/leaderboards.tsx +++ b/app/features/leaderboards/routes/leaderboards.tsx @@ -45,7 +45,7 @@ export const handle: SendouRouteHandle = { }; export const meta: MetaFunction = (args) => { - const data = args.data as SerializeFrom | null; + const data = args.loaderData as SerializeFrom | null; if (!data) return []; diff --git a/app/features/params/routes/params.$slug.tsx b/app/features/params/routes/params.$slug.tsx index 0ca6b5461..970764939 100644 --- a/app/features/params/routes/params.$slug.tsx +++ b/app/features/params/routes/params.$slug.tsx @@ -23,7 +23,7 @@ export { loader }; export const handle: SendouRouteHandle = { i18n: ["weapons", "common", "analyzer", "params"], breadcrumb: ({ match }) => { - const data = match.data as SerializeFrom | undefined; + const data = match.loaderData as SerializeFrom | undefined; if (!data) return []; return [ { @@ -36,10 +36,10 @@ export const handle: SendouRouteHandle = { }; export const meta: MetaFunction = (args) => { - if (!args.data) return []; + if (!args.loaderData) return []; return metaTags({ - title: `${args.data.weaponName} parameters`, - description: `${args.data.weaponName} parameters with version history compared across ${comparedAcross(args.data.kind)}.`, + title: `${args.loaderData.weaponName} parameters`, + description: `${args.loaderData.weaponName} parameters with version history compared across ${comparedAcross(args.loaderData.kind)}.`, location: args.location, }); }; diff --git a/app/features/plus-suggestions/routes/plus.suggestions.comment.$tier.$userId.tsx b/app/features/plus-suggestions/routes/plus.suggestions.comment.$tier.$userId.tsx index 32b230e96..be936a81e 100644 --- a/app/features/plus-suggestions/routes/plus.suggestions.comment.$tier.$userId.tsx +++ b/app/features/plus-suggestions/routes/plus.suggestions.comment.$tier.$userId.tsx @@ -15,7 +15,7 @@ export default function PlusCommentModalPage() { const user = useUser(); const matches = useMatches(); const params = useParams(); - const data = matches.at(-2)!.data as PlusSuggestionsLoaderData; + const data = matches.at(-2)!.loaderData as PlusSuggestionsLoaderData; const targetUserId = Number(params.userId); const tierSuggestedTo = Number(params.tier); diff --git a/app/features/plus-suggestions/routes/plus.suggestions.new.tsx b/app/features/plus-suggestions/routes/plus.suggestions.new.tsx index a75a1566e..30738a73a 100644 --- a/app/features/plus-suggestions/routes/plus.suggestions.new.tsx +++ b/app/features/plus-suggestions/routes/plus.suggestions.new.tsx @@ -15,7 +15,7 @@ export { action }; export default function PlusNewSuggestionModalPage() { const user = useUser(); const matches = useMatches(); - const data = matches.at(-2)!.data as PlusSuggestionsLoaderData; + const data = matches.at(-2)!.loaderData as PlusSuggestionsLoaderData; const tierOptions = PLUS_TIERS.filter((tier) => { // user will be redirected anyway diff --git a/app/features/sendouq-match/routes/q.match.$id.tsx b/app/features/sendouq-match/routes/q.match.$id.tsx index ae854a5b6..e88740b7a 100644 --- a/app/features/sendouq-match/routes/q.match.$id.tsx +++ b/app/features/sendouq-match/routes/q.match.$id.tsx @@ -14,7 +14,7 @@ import { loader } from "../loaders/q.match.$id.server"; export { action, loader }; export const meta: MetaFunction = (args) => { - const data = args.data as SerializeFrom | null; + const data = args.loaderData as SerializeFrom | null; if (!data) return []; diff --git a/app/features/settings/components/ThemeTab.tsx b/app/features/settings/components/ThemeTab.tsx index 42842b6d7..f67cb8405 100644 --- a/app/features/settings/components/ThemeTab.tsx +++ b/app/features/settings/components/ThemeTab.tsx @@ -48,7 +48,7 @@ function ThemeSelector() { function CustomColorSelector() { const [root] = useMatches(); - const rootData = root.data as RootLoaderData | undefined; + const rootData = root.loaderData as RootLoaderData | undefined; const isSupporter = useHasRole("SUPPORTER"); const fetcher = useFetcher(); diff --git a/app/features/team/components/TeamGoBackButton.tsx b/app/features/team/components/TeamGoBackButton.tsx index 5e5665310..654f97d88 100644 --- a/app/features/team/components/TeamGoBackButton.tsx +++ b/app/features/team/components/TeamGoBackButton.tsx @@ -11,7 +11,7 @@ export function TeamGoBackButton() { const [, parentRoute] = useMatches(); invariant(parentRoute); - const layoutData = parentRoute.data as TeamLoaderData; + const layoutData = parentRoute.loaderData as TeamLoaderData; return (
diff --git a/app/features/team/routes/t.$customUrl.index.tsx b/app/features/team/routes/t.$customUrl.index.tsx index 0b6cac377..e390158a7 100644 --- a/app/features/team/routes/t.$customUrl.index.tsx +++ b/app/features/team/routes/t.$customUrl.index.tsx @@ -45,7 +45,7 @@ export default function TeamIndexPage() { const { t } = useTranslation(["team"]); const [, parentRoute] = useMatches(); invariant(parentRoute); - const layoutData = parentRoute.data as TeamLoaderData; + const layoutData = parentRoute.loaderData as TeamLoaderData; const members = layoutData.team.members; const playerMembers = members.filter( @@ -110,7 +110,7 @@ function ActionButtons() { const isAdmin = useHasRole("ADMIN"); const [, parentRoute] = useMatches(); invariant(parentRoute); - const layoutData = parentRoute.data as TeamLoaderData; + const layoutData = parentRoute.loaderData as TeamLoaderData; const team = layoutData.team; if (!isTeamMember({ user, team }) && !isAdmin) { diff --git a/app/features/team/routes/t.$customUrl.tsx b/app/features/team/routes/t.$customUrl.tsx index 40cf5b47b..da9e36780 100644 --- a/app/features/team/routes/t.$customUrl.tsx +++ b/app/features/team/routes/t.$customUrl.tsx @@ -15,15 +15,15 @@ export { loader }; import styles from "../team.module.css"; export const meta: MetaFunction = (args) => { - if (!args.data) return []; + if (!args.loaderData) return []; return metaTags({ - title: args.data.team.name, - description: args.data.team.bio ?? undefined, + title: args.loaderData.team.name, + description: args.loaderData.team.bio ?? undefined, location: args.location, - image: args.data.team.avatarUrl + image: args.loaderData.team.avatarUrl ? { - url: args.data.team.avatarUrl, + url: args.loaderData.team.avatarUrl, dimensions: { width: 124, height: 124, @@ -36,7 +36,7 @@ export const meta: MetaFunction = (args) => { export const handle: SendouRouteHandle = { i18n: ["team"], breadcrumb: ({ match }) => { - const data = match.data as SerializeFrom | undefined; + const data = match.loaderData as SerializeFrom | undefined; if (!data) return []; diff --git a/app/features/top-search/routes/xsearch.player.$id.tsx b/app/features/top-search/routes/xsearch.player.$id.tsx index 95abc5b05..a2b01210e 100644 --- a/app/features/top-search/routes/xsearch.player.$id.tsx +++ b/app/features/top-search/routes/xsearch.player.$id.tsx @@ -22,7 +22,7 @@ export { action, loader }; export const handle: SendouRouteHandle = { breadcrumb: ({ match }) => { - const data = match.data as SerializeFrom | undefined; + const data = match.loaderData as SerializeFrom | undefined; if (!data) return []; @@ -44,16 +44,16 @@ export const handle: SendouRouteHandle = { }; export const meta: MetaFunction = (args) => { - if (!args.data) return []; + if (!args.loaderData) return []; const aliasesStr = - args.data.names.aliases.length > 0 - ? ` (Aliases: ${args.data.names.aliases.join(", ")})` + args.loaderData.names.aliases.length > 0 + ? ` (Aliases: ${args.loaderData.names.aliases.join(", ")})` : ""; return metaTags({ - title: `${args.data.names.primary} X Battle Top 500 Placements`, - description: `Splatoon 3 X Battle results for the player ${args.data.names.primary}${aliasesStr}`, + title: `${args.loaderData.names.primary} X Battle Top 500 Placements`, + description: `Splatoon 3 X Battle results for the player ${args.loaderData.names.primary}${aliasesStr}`, location: args.location, }); }; diff --git a/app/features/tournament-organization/routes/org.$slug.tsx b/app/features/tournament-organization/routes/org.$slug.tsx index 92fee3ec9..a0edab52c 100644 --- a/app/features/tournament-organization/routes/org.$slug.tsx +++ b/app/features/tournament-organization/routes/org.$slug.tsx @@ -55,15 +55,15 @@ import { updateIsEstablishedSchema } from "../tournament-organization-schemas"; export { action, loader }; export const meta: MetaFunction = (args) => { - if (!args.data) return []; + if (!args.loaderData) return []; return metaTags({ - title: args.data.organization.name, + title: args.loaderData.organization.name, location: args.location, - description: args.data.organization.description ?? undefined, - image: args.data.organization.avatarUrl + description: args.loaderData.organization.description ?? undefined, + image: args.loaderData.organization.avatarUrl ? { - url: args.data.organization.avatarUrl, + url: args.loaderData.organization.avatarUrl, dimensions: { width: 124, height: 124 }, } : undefined, @@ -73,7 +73,7 @@ export const meta: MetaFunction = (args) => { export const handle: SendouRouteHandle = { i18n: ["badges", "org"], breadcrumb: ({ match }) => { - const data = match.data as SerializeFrom | undefined; + const data = match.loaderData as SerializeFrom | undefined; if (!data) return []; diff --git a/app/features/tournament/routes/to.$id.info.tsx b/app/features/tournament/routes/to.$id.info.tsx index d68df4f3f..69fbc427f 100644 --- a/app/features/tournament/routes/to.$id.info.tsx +++ b/app/features/tournament/routes/to.$id.info.tsx @@ -27,15 +27,14 @@ import styles from "./to.$id.info.module.css"; export { action, loader }; export const meta: MetaFunction = (args) => { - const tournamentData = JSON.parse(args.matches[1].data as any)?.tournament as - | TournamentData - | undefined; + const tournamentData = JSON.parse(args.matches[1].loaderData as any) + ?.tournament as TournamentData | undefined; if (!tournamentData) return []; return metaTags({ title: tournamentData.ctx.name, - description: args.data?.description - ? removeMarkdown(args.data.description) + description: args.loaderData?.description + ? removeMarkdown(args.loaderData.description) : undefined, image: { url: tournamentData.ctx.logoUrl, diff --git a/app/features/tournament/routes/to.$id.teams.$tid.tsx b/app/features/tournament/routes/to.$id.teams.$tid.tsx index 684501d96..537593a23 100644 --- a/app/features/tournament/routes/to.$id.teams.$tid.tsx +++ b/app/features/tournament/routes/to.$id.teams.$tid.tsx @@ -29,12 +29,12 @@ import { useTournament } from "./to.$id"; export { loader }; export const meta: MetaFunction = (args) => { - const tournamentData = JSON.parse(args.matches[1].data as any) + const tournamentData = JSON.parse(args.matches[1].loaderData as any) ?.tournament as TournamentData; - if (!args.data || !tournamentData) return []; + if (!args.loaderData || !tournamentData) return []; const team = tournamentData.ctx.teams.find( - (t) => t.id === args.data!.tournamentTeamId, + (t) => t.id === args.loaderData!.tournamentTeamId, )!; const teamLogoUrl = team.team?.logoUrl ?? team.pickupAvatarUrl; diff --git a/app/features/tournament/routes/to.$id.tsx b/app/features/tournament/routes/to.$id.tsx index 8061f9c7d..50f0873e3 100644 --- a/app/features/tournament/routes/to.$id.tsx +++ b/app/features/tournament/routes/to.$id.tsx @@ -31,7 +31,7 @@ export const shouldRevalidate: ShouldRevalidateFunction = (args) => { }; export const meta: MetaFunction = (args) => { - const rawData = args.data as string | undefined; + const rawData = args.loaderData as string | undefined; if (!rawData) return []; @@ -51,7 +51,7 @@ export const meta: MetaFunction = (args) => { export const handle: SendouRouteHandle = { i18n: ["tournament", "calendar"], breadcrumb: ({ match }) => { - const rawData = match.data as string | undefined; + const rawData = match.loaderData as string | undefined; if (!rawData) return []; diff --git a/app/features/user-page/routes/u.$identifier.admin.tsx b/app/features/user-page/routes/u.$identifier.admin.tsx index 32fa9f3f3..92d7127d6 100644 --- a/app/features/user-page/routes/u.$identifier.admin.tsx +++ b/app/features/user-page/routes/u.$identifier.admin.tsx @@ -20,7 +20,7 @@ export { action, loader }; export default function UserAdminPage() { const [, parentRoute] = useMatches(); invariant(parentRoute); - const layoutData = parentRoute.data as UserPageLoaderData; + const layoutData = parentRoute.loaderData as UserPageLoaderData; return (
diff --git a/app/features/user-page/routes/u.$identifier.art.tsx b/app/features/user-page/routes/u.$identifier.art.tsx index 329cc4d3b..24973192e 100644 --- a/app/features/user-page/routes/u.$identifier.art.tsx +++ b/app/features/user-page/routes/u.$identifier.art.tsx @@ -35,7 +35,7 @@ export default function UserArtPage() { }); const [, parentRoute] = useMatches(); invariant(parentRoute); - const layoutData = parentRoute.data as UserPageLoaderData; + const layoutData = parentRoute.loaderData as UserPageLoaderData; const hasBothArtMadeByAndMadeOf = data.arts.some((a) => a.author) && data.arts.some((a) => !a.author); diff --git a/app/features/user-page/routes/u.$identifier.builds.new.tsx b/app/features/user-page/routes/u.$identifier.builds.new.tsx index 411944290..2b78917de 100644 --- a/app/features/user-page/routes/u.$identifier.builds.new.tsx +++ b/app/features/user-page/routes/u.$identifier.builds.new.tsx @@ -20,7 +20,7 @@ export default function NewBuildPage() { const { defaultValues, gearIdToAbilities } = useLoaderData(); const [, parentRoute] = useMatches(); invariant(parentRoute); - const layoutData = parentRoute.data as UserPageLoaderData; + const layoutData = parentRoute.loaderData as UserPageLoaderData; const { t } = useTranslation(["builds"]); if (layoutData.user.buildsCount >= BUILD.MAX_COUNT) { diff --git a/app/features/user-page/routes/u.$identifier.builds.tsx b/app/features/user-page/routes/u.$identifier.builds.tsx index 7d76205f9..6676b1fcf 100644 --- a/app/features/user-page/routes/u.$identifier.builds.tsx +++ b/app/features/user-page/routes/u.$identifier.builds.tsx @@ -39,7 +39,7 @@ type BuildFilter = "ALL" | "PUBLIC" | "PRIVATE" | MainWeaponId; export default function UserBuildsPage() { const { t } = useTranslation(["builds", "user"]); const user = useUser(); - const layoutData = useMatches().at(-2)!.data as UserPageLoaderData; + const layoutData = useMatches().at(-2)!.loaderData as UserPageLoaderData; const data = useLoaderData(); const [weaponFilter, setWeaponFilter] = useSearchParamState({ defaultValue: "ALL", @@ -122,7 +122,7 @@ function BuildsFilters({ const { t } = useTranslation(["weapons", "builds"]); const data = useLoaderData(); const user = useUser(); - const layoutData = useMatches().at(-2)!.data as UserPageLoaderData; + const layoutData = useMatches().at(-2)!.loaderData as UserPageLoaderData; if (data.builds.length === 0) return null; diff --git a/app/features/user-page/routes/u.$identifier.edit.tsx b/app/features/user-page/routes/u.$identifier.edit.tsx index f97119bff..66d5c8c07 100644 --- a/app/features/user-page/routes/u.$identifier.edit.tsx +++ b/app/features/user-page/routes/u.$identifier.edit.tsx @@ -27,7 +27,7 @@ export default function UserEditPage() { const { t } = useTranslation(["common", "user"]); const [, parentRoute] = useMatches(); invariant(parentRoute); - const layoutData = parentRoute.data as UserPageLoaderData; + const layoutData = parentRoute.loaderData as UserPageLoaderData; const data = useLoaderData(); const isSupporter = useHasRole("SUPPORTER"); const isArtist = useHasRole("ARTIST"); diff --git a/app/features/user-page/routes/u.$identifier.index.tsx b/app/features/user-page/routes/u.$identifier.index.tsx index 9c4dcd39e..e2249877a 100644 --- a/app/features/user-page/routes/u.$identifier.index.tsx +++ b/app/features/user-page/routes/u.$identifier.index.tsx @@ -73,7 +73,7 @@ function NewUserInfoPage() { const user = useUser(); const [, parentRoute] = useMatches(); invariant(parentRoute); - const layoutData = parentRoute.data as UserPageLoaderData; + const layoutData = parentRoute.loaderData as UserPageLoaderData; const { navItems } = useOutletContext<{ navItems: UserPageNavItem[] }>(); if (data.type !== "new") { @@ -171,7 +171,7 @@ export function OldUserInfoPage() { const data = useLoaderData(); const [, parentRoute] = useMatches(); invariant(parentRoute); - const layoutData = parentRoute.data as UserPageLoaderData; + const layoutData = parentRoute.loaderData as UserPageLoaderData; if (data.type !== "old") { throw new Error("Expected old user data"); diff --git a/app/features/user-page/routes/u.$identifier.results.tsx b/app/features/user-page/routes/u.$identifier.results.tsx index ba8a2e512..4ae465325 100644 --- a/app/features/user-page/routes/u.$identifier.results.tsx +++ b/app/features/user-page/routes/u.$identifier.results.tsx @@ -25,7 +25,7 @@ export default function UserResultsPage() { const [, parentRoute] = useMatches(); invariant(parentRoute); - const layoutData = parentRoute.data as UserPageLoaderData; + const layoutData = parentRoute.loaderData as UserPageLoaderData; const [searchParams, setSearchParams] = useSearchParams(); const showAll = searchParams.get("all") === "true"; diff --git a/app/features/user-page/routes/u.$identifier.seasons.tsx b/app/features/user-page/routes/u.$identifier.seasons.tsx index bbdd12c15..3bc39f32d 100644 --- a/app/features/user-page/routes/u.$identifier.seasons.tsx +++ b/app/features/user-page/routes/u.$identifier.seasons.tsx @@ -78,7 +78,7 @@ export default function UserSeasonsPage() { const data = useLoaderData(); const [, parentRoute] = useMatches(); invariant(parentRoute); - const layoutData = parentRoute.data as UserPageLoaderData; + const layoutData = parentRoute.loaderData as UserPageLoaderData; if (!data) { return ( @@ -325,7 +325,7 @@ function Rank({ const { t } = useTranslation(["user"]); const [, parentRoute] = useMatches(); invariant(parentRoute); - const layoutData = parentRoute.data as UserPageLoaderData; + const layoutData = parentRoute.loaderData as UserPageLoaderData; const maxOrdinal = Math.max(...skills.map((s) => s.ordinal)); @@ -469,7 +469,7 @@ function Stages({ stages: NonNullable; }) { const { t } = useTranslation(["user", "game-misc"]); - const layoutData = useMatches().at(-2)!.data as UserPageLoaderData; + const layoutData = useMatches().at(-2)!.loaderData as UserPageLoaderData; return (
@@ -804,7 +804,7 @@ function Results({ function GroupMatchResult({ match }: { match: SeasonGroupMatch }) { const [, parentRoute] = useMatches(); invariant(parentRoute); - const layoutData = parentRoute.data as UserPageLoaderData; + const layoutData = parentRoute.loaderData as UserPageLoaderData; const userId = layoutData.user.id; // score when match has not yet been played or was canceled diff --git a/app/features/user-page/routes/u.$identifier.tsx b/app/features/user-page/routes/u.$identifier.tsx index f9654a60f..28805faf2 100644 --- a/app/features/user-page/routes/u.$identifier.tsx +++ b/app/features/user-page/routes/u.$identifier.tsx @@ -30,11 +30,11 @@ export { loader }; import "~/features/user-page/user-page.module.css"; export const meta: MetaFunction = (args) => { - if (!args.data) return []; + if (!args.loaderData) return []; return metaTags({ - title: args.data.user.username, - description: `${args.data.user.username}'s profile on sendou.ink including builds, tournament results, art and more.`, + title: args.loaderData.user.username, + description: `${args.loaderData.user.username}'s profile on sendou.ink including builds, tournament results, art and more.`, location: args.location, }); }; @@ -42,7 +42,7 @@ export const meta: MetaFunction = (args) => { export const handle: SendouRouteHandle = { i18n: ["user", "badges", "game-badges"], breadcrumb: ({ match }) => { - const data = match.data as UserPageLoaderData | undefined; + const data = match.loaderData as UserPageLoaderData | undefined; if (!data) return []; @@ -86,7 +86,9 @@ export default function UserPageLayout() { const allResultsCount = data.user.calendarEventResultsCount + data.user.tournamentResultsCount; - const isNewUserPage = matches.some((m) => (m.data as any)?.type === "new"); + const isNewUserPage = matches.some( + (m) => (m.loaderData as any)?.type === "new", + ); const navItems: UserPageNavItem[] = [ { diff --git a/app/features/user-page/routes/u.$identifier.vods.tsx b/app/features/user-page/routes/u.$identifier.vods.tsx index c4455b9c0..858bd6393 100644 --- a/app/features/user-page/routes/u.$identifier.vods.tsx +++ b/app/features/user-page/routes/u.$identifier.vods.tsx @@ -19,7 +19,7 @@ export default function UserVodsPage() { const [, parentRoute] = useMatches(); invariant(parentRoute); const data = useLoaderData(); - const layoutData = parentRoute.data as UserPageLoaderData; + const layoutData = parentRoute.loaderData as UserPageLoaderData; const [, setSearchParams] = useSearchParams(); const setPage = (page: number) => { diff --git a/app/features/vods/routes/vods.$id.tsx b/app/features/vods/routes/vods.$id.tsx index 271636838..749989eb7 100644 --- a/app/features/vods/routes/vods.$id.tsx +++ b/app/features/vods/routes/vods.$id.tsx @@ -42,7 +42,7 @@ export { action, loader }; export const handle: SendouRouteHandle = { i18n: ["vods"], breadcrumb: ({ match }) => { - const data = match.data as SerializeFrom | undefined; + const data = match.loaderData as SerializeFrom | undefined; if (!data) return []; @@ -62,10 +62,10 @@ export const handle: SendouRouteHandle = { }; export const meta: MetaFunction = (args) => { - if (!args.data) return []; + if (!args.loaderData) return []; return metaTags({ - title: args.data.vod.title, + title: args.loaderData.vod.title, description: "Splatoon 3 VoD with timestamps to check out specific weapons as well as map and mode combinations.", location: args.location, diff --git a/app/root.tsx b/app/root.tsx index 870279079..04a66da5b 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -61,9 +61,11 @@ import { isSupporter } from "./modules/permissions/utils"; import { IS_E2E_TEST_RUN } from "./utils/e2e"; import { allI18nNamespaces } from "./utils/i18n"; import { isRevalidation, metaTags, type SerializeFrom } from "./utils/remix"; +import { requestContextMiddleware } from "./utils/request-context-middleware.server"; import { APP_ICON_URL, pwaSplashScreenImageUrl } from "./utils/urls"; export const middleware: Route.MiddlewareFunction[] = [ + requestContextMiddleware, sessionIdMiddleware, userMiddleware, ]; @@ -334,7 +336,7 @@ function useCustomThemeVars() { const styles: Map = new Map(); for (const match of matches) { - const data = match.data as { customTheme?: CustomTheme } | undefined; + const data = match.loaderData as { customTheme?: CustomTheme } | undefined; if (data?.customTheme) { for (const [key, value] of Object.entries(data.customTheme)) { diff --git a/app/utils/remix.server.ts b/app/utils/remix.server.ts index ba42a0fa8..413f09552 100644 --- a/app/utils/remix.server.ts +++ b/app/utils/remix.server.ts @@ -8,6 +8,7 @@ import type { z } from "zod"; import type { navItems } from "~/components/layout/nav-items"; import { ServerConfig } from "~/config.server"; import { logger } from "./logger"; +import { currentRequestPathname } from "./request-context.server"; export function notFoundIfFalsy(value: T | null | undefined): T { if (!value) throw new Response(null, { status: 404 }); @@ -226,7 +227,7 @@ export function canAccessLohiEndpoint(request: Request) { } function errorToastRedirect(message: string) { - return redirect(`?__error=${message}`); + return redirect(`${currentRequestPathname() ?? ""}?__error=${message}`); } /** Asserts condition is truthy. Throws a redirect triggering an error toast with given message otherwise. */ @@ -258,7 +259,7 @@ export function errorToast(message: string) { } export function successToast(message: string) { - return redirect(`?__success=${message}`); + return redirect(`${currentRequestPathname() ?? ""}?__success=${message}`); } export function successToastWithRedirect({ diff --git a/app/utils/request-context-middleware.server.ts b/app/utils/request-context-middleware.server.ts new file mode 100644 index 000000000..97059397f --- /dev/null +++ b/app/utils/request-context-middleware.server.ts @@ -0,0 +1,16 @@ +import { runWithRequestContext } from "./request-context.server"; + +type MiddlewareArgs = { + request: Request; + url: URL; + context: unknown; +}; + +type MiddlewareFn = ( + args: MiddlewareArgs, + next: () => Promise, +) => Promise; + +// TODO: this is only needed for our current hacky toast setup, once a proper one in place this middleware can be deleted +export const requestContextMiddleware: MiddlewareFn = ({ url }, next) => + runWithRequestContext({ url }, () => next()); diff --git a/app/utils/request-context.server.ts b/app/utils/request-context.server.ts new file mode 100644 index 000000000..b37afecaa --- /dev/null +++ b/app/utils/request-context.server.ts @@ -0,0 +1,26 @@ +import { AsyncLocalStorage } from "node:async_hooks"; + +// TODO: this is only needed for our current hacky toast setup, once a proper one in place this middleware can be deleted + +interface RequestContext { + /** Normalized request URL, as provided to middleware in framework mode + * (single-fetch `.data` suffix and internal search params removed). */ + url: URL; +} + +const requestContextAsyncLocalStorage = new AsyncLocalStorage(); + +/** Runs `fn` with the given request context available to server-side helpers + * (e.g. toast redirects) that don't otherwise receive the request. */ +export function runWithRequestContext( + context: RequestContext, + fn: () => T, +): T { + return requestContextAsyncLocalStorage.run(context, fn); +} + +/** Normalized pathname of the current request, or `undefined` outside a request + * context. Used to build absolute redirects from helpers lacking the request. */ +export function currentRequestPathname(): string | undefined { + return requestContextAsyncLocalStorage.getStore()?.url.pathname; +} diff --git a/package.json b/package.json index 7db0178d3..d3caaf63c 100644 --- a/package.json +++ b/package.json @@ -47,10 +47,10 @@ "@faker-js/faker": "10.4.0", "@formatjs/intl-durationformat": "0.10.14", "@internationalized/date": "3.12.2", - "@react-router/node": "7.17.0", - "@react-router/serve": "7.15.0", + "@react-router/node": "8.1.0", + "@react-router/serve": "8.1.0", "@remix-run/form-data-parser": "0.17.3", - "@sentry/react-router": "^10.57.0", + "@sentry/react-router": "10.63.0", "@tldraw/tldraw": "3.12.1", "@zumer/snapdom": "2.12.8", "better-sqlite3": "12.10.0", @@ -84,7 +84,7 @@ "react-error-boundary": "6.1.2", "react-flip-toolkit": "7.2.4", "react-i18next": "17.0.8", - "react-router": "7.17.0", + "react-router": "8.1.0", "react-use-draggable-scroll": "0.4.7", "remeda": "2.39.0", "remix-auth": "4.2.0", @@ -100,7 +100,7 @@ "@babel/preset-typescript": "7.29.7", "@biomejs/biome": "2.5.1", "@playwright/test": "1.60.0", - "@react-router/dev": "7.17.0", + "@react-router/dev": "8.1.0", "@types/better-sqlite3": "7.6.13", "@types/node": "26.0.0", "@types/node-cron": "3.0.11", diff --git a/patches/@react-router__serve@7.15.0.patch b/patches/@react-router__serve@7.15.0.patch deleted file mode 100644 index d2a2c2e18..000000000 --- a/patches/@react-router__serve@7.15.0.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/dist/cli.js b/dist/cli.js -index 08277520abadf36c3da03e50afc08919d316d5b2..55d9a1cf1f0220a96acd9f330b5668b03e4b6ddc 100644 ---- a/dist/cli.js -+++ b/dist/cli.js -@@ -127,7 +127,6 @@ async function run() { - ); - app.use(build.publicPath, import_express2.default.static(build.assetsBuildDirectory)); - app.use(import_express2.default.static("public", { maxAge: "1h" })); -- app.use((0, import_morgan.default)("tiny")); - if (build.fetch) { - app.all("*", (0, import_node_fetch_server.createRequestListener)(build.fetch)); - } else { diff --git a/patches/@react-router__serve@8.1.0.patch b/patches/@react-router__serve@8.1.0.patch new file mode 100644 index 000000000..2cdd5638e --- /dev/null +++ b/patches/@react-router__serve@8.1.0.patch @@ -0,0 +1,12 @@ +diff --git a/dist/cli.js b/dist/cli.js +index 7871cebe46f6df886b76364db5346adfa1838622..495960a0a12d3d027ababaacc0b92a7dc39274a6 100644 +--- a/dist/cli.js ++++ b/dist/cli.js +@@ -118,7 +118,6 @@ async function run() { + })); + app.use(expressPublicPath, express.static(build.assetsBuildDirectory)); + app.use(express.static("public", { maxAge: "1h" })); +- app.use(morgan("tiny")); + if (build.fetch) app.all("/{*splat}", createRequestListener(build.fetch)); + else app.all("/{*splat}", createRequestHandler({ + build: buildModule, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7309b6ed1..16a382e0f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,7 +5,7 @@ settings: excludeLinksFromLockfile: false patchedDependencies: - '@react-router/serve@7.15.0': 38a61caab4f8dc67c82ecfb17507d5a650ea7b632b4e58a369c0aa4583611c87 + '@react-router/serve@8.1.0': 4d79b209948d4237f1ca3a5cca417c6d11519d8b1ab7084c75c46a39e3ac27d6 kysely@0.29.0: 6f395b25414c1ef852485fa3a03d2d521816064697d8c4bff337e2b24d19daa6 importers: @@ -46,17 +46,17 @@ importers: specifier: 3.12.2 version: 3.12.2 '@react-router/node': - specifier: 7.17.0 - version: 7.17.0(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) + specifier: 8.1.0 + version: 8.1.0(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) '@react-router/serve': - specifier: 7.15.0 - version: 7.15.0(patch_hash=38a61caab4f8dc67c82ecfb17507d5a650ea7b632b4e58a369c0aa4583611c87)(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) + specifier: 8.1.0 + version: 8.1.0(patch_hash=4d79b209948d4237f1ca3a5cca417c6d11519d8b1ab7084c75c46a39e3ac27d6)(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) '@remix-run/form-data-parser': specifier: 0.17.3 version: 0.17.3 '@sentry/react-router': - specifier: ^10.57.0 - version: 10.57.0(@react-router/node@7.17.0(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3))(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)(rollup@4.61.1) + specifier: 10.63.0 + version: 10.63.0(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@react-router/node@8.1.0(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3))(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) '@tldraw/tldraw': specifier: 3.12.1 version: 3.12.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -157,8 +157,8 @@ importers: specifier: 17.0.8 version: 17.0.8(i18next@26.3.1(typescript@6.0.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) react-router: - specifier: 7.17.0 - version: 7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + specifier: 8.1.0 + version: 8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react-use-draggable-scroll: specifier: 0.4.7 version: 0.4.7(react@19.2.7) @@ -173,7 +173,7 @@ importers: version: 3.4.1(remix-auth@4.2.0) remix-i18next: specifier: 7.5.0 - version: 7.5.0(i18next@26.3.1(typescript@6.0.3))(react-i18next@17.0.8(i18next@26.3.1(typescript@6.0.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) + version: 7.5.0(i18next@26.3.1(typescript@6.0.3))(react-i18next@17.0.8(i18next@26.3.1(typescript@6.0.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) slugify: specifier: 1.6.9 version: 1.6.9 @@ -200,8 +200,8 @@ importers: specifier: 1.60.0 version: 1.60.0 '@react-router/dev': - specifier: 7.17.0 - version: 7.17.0(@react-router/serve@7.15.0(patch_hash=38a61caab4f8dc67c82ecfb17507d5a650ea7b632b4e58a369c0aa4583611c87)(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3))(@types/node@26.0.0)(jiti@2.7.0)(lightningcss@1.32.0)(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))(yaml@2.9.0) + specifier: 8.1.0 + version: 8.1.0(@react-router/serve@8.1.0(patch_hash=4d79b209948d4237f1ca3a5cca417c6d11519d8b1ab7084c75c46a39e3ac27d6)(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3))(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0)) '@types/better-sqlite3': specifier: 7.6.13 version: 7.6.13 @@ -225,7 +225,7 @@ importers: version: 3.6.4 '@vitest/browser-playwright': specifier: 4.1.8 - version: 4.1.8(playwright@1.60.0)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.8) + version: 4.1.8(playwright@1.60.0)(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.8) '@vitest/ui': specifier: 4.1.8 version: 4.1.8(vitest@4.1.8) @@ -252,22 +252,33 @@ importers: version: 6.0.3 vite: specifier: 8.0.16 - version: 8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) + version: 8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0) vite-node: specifier: 6.0.0 - version: 6.0.0(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) + version: 6.0.0(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0) vite-plugin-babel: specifier: 1.7.3 - version: 1.7.3(@babel/core@7.29.7)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + version: 1.7.3(@babel/core@7.29.7)(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0)) vitest: specifier: 4.1.8 - version: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@26.0.0)(@vitest/browser-playwright@4.1.8)(@vitest/ui@4.1.8)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + version: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@26.0.0)(@vitest/browser-playwright@4.1.8)(@vitest/ui@4.1.8)(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0)) vitest-browser-react: specifier: 2.2.0 version: 2.2.0(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(vitest@4.1.8) packages: + '@apm-js-collab/code-transformer-bundler-plugins@0.5.0': + resolution: {integrity: sha512-YxLBY5nGlurL7QeJLq6e5g0ouBpAp0pwgyA/5rHXEXwhiPLn9ZHbT+Y2LlP90GT872cSocfjWRYu/fnpuBudNQ==} + engines: {node: '>=18.0.0'} + + '@apm-js-collab/code-transformer@0.15.0': + resolution: {integrity: sha512-XmXYVs8CzJ1Aj79noVbn2weUO/XWtRyURpGqx7aU7DOXlUQhR0WKOQNF0okh7PCeY37vxf7kU3v57OAkEPm3ww==} + hasBin: true + + '@apm-js-collab/tracing-hooks@0.10.0': + resolution: {integrity: sha512-2/Z3NTewJTruUkmsSnBC5bJlLNUd9keuD1OLlTEpim4FyLhm6m2Rnfv+wrFdUvFfhmH8CRdiDZBqBrn+wyaGuA==} + '@aws-crypto/crc32@5.2.0': resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} engines: {node: '>=16.0.0'} @@ -632,162 +643,6 @@ packages: '@epic-web/invariant@1.0.0': resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} - '@esbuild/aix-ppc64@0.27.7': - resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.27.7': - resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.27.7': - resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.27.7': - resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.27.7': - resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.27.7': - resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.27.7': - resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.27.7': - resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.27.7': - resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.27.7': - resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.27.7': - resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.27.7': - resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.27.7': - resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.27.7': - resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.27.7': - resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.27.7': - resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.27.7': - resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.27.7': - resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.27.7': - resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.27.7': - resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.27.7': - resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.27.7': - resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.27.7': - resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.27.7': - resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.27.7': - resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.27.7': - resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@faker-js/faker@10.4.0': resolution: {integrity: sha512-sDBWI3yLy8EcDzgobvJTWq1MJYzAkQdpjXuPukga9wXonhpMRvd1Izuo2Qgwey2OiEoRIBr35RMU9HJRoOHzpw==} engines: {node: ^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0, npm: '>=10'} @@ -850,9 +705,6 @@ packages: '@mjackson/headers@0.10.0': resolution: {integrity: sha512-U1Eu1gF979k7ZoIBsJyD+T5l9MjtPONsZfoXfktsQHPJD0s7SokBGx+tLKDLsOY+gzVYAWS0yRFDNY8cgbQzWQ==} - '@mjackson/node-fetch-server@0.2.0': - resolution: {integrity: sha512-EMlH1e30yzmTpGLQjlFmaDAjyOeZhng1/XCd7DExR8PNAnG/G1tyruZxEoUe11ClnwGhGrtsdnyyUx1frSzjng==} - '@napi-rs/wasm-runtime@1.1.5': resolution: {integrity: sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==} peerDependencies: @@ -1529,18 +1381,18 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - '@react-router/dev@7.17.0': - resolution: {integrity: sha512-+ITMmv/1xUB+QF6ehCCOIVBNBDuKIEE+3JKCt+kTBvxDTk1s49qHbtCA4TzJYge41pNRGtFIiy5VAksLSVJheg==} - engines: {node: '>=20.0.0'} + '@react-router/dev@8.1.0': + resolution: {integrity: sha512-0R7g3hPm+vXjfOzA6oInZ3KI/N9hxrtibtue3s1pdHcSPrfRFU2jgmC5I/W0gCJ9wtszLKqisHRet2vzY8USoA==} + engines: {node: '>=22.22.0'} hasBin: true peerDependencies: - '@react-router/serve': ^7.17.0 - '@vitejs/plugin-rsc': ~0.5.21 - react-router: ^7.17.0 - react-server-dom-webpack: ^19.2.3 + '@react-router/serve': ^8.1.0 + '@vitejs/plugin-rsc': ~0.5.26 + react-router: ^8.1.0 + react-server-dom-webpack: ^19.2.7 typescript: ^5.1.0 || ^6.0.0 - vite: ^5.1.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - wrangler: ^3.28.2 || ^4.0.0 + vite: ^7.0.0 || ^8.0.0 + wrangler: ^4.0.0 peerDependenciesMeta: '@react-router/serve': optional: true @@ -1553,43 +1405,33 @@ packages: wrangler: optional: true - '@react-router/express@7.15.0': - resolution: {integrity: sha512-WldQrI5FxbsTLztOqx0+c7248m8IDUchCUUX177I+qz9OMuLR9ueIqz53zBKCVQ+Zf7k6FyatwpoLmr/Wovalg==} - engines: {node: '>=20.0.0'} + '@react-router/express@8.1.0': + resolution: {integrity: sha512-SuyQNfxbfphmEjF0joUir5boACNRoD2HUPrIqp56fhG7zwOAR9NQpnu6ZQZ8iA2ox3U013BR8Flijb2tI1jFbg==} + engines: {node: '>=22.22.0'} peerDependencies: - express: ^4.17.1 || ^5 - react-router: 7.15.0 + express: ^4.22.2 || ^5 + react-router: 8.1.0 typescript: ^5.1.0 || ^6.0.0 peerDependenciesMeta: typescript: optional: true - '@react-router/node@7.15.0': - resolution: {integrity: sha512-SgvWaWF1n3u+bpXXZUW9BSd2p/NwkIYLz4SSeDYqoX5RkYX5rcI4cHHuNJXszPu+Dm9QIri4J9g/4EV3KfgiXQ==} - engines: {node: '>=20.0.0'} + '@react-router/node@8.1.0': + resolution: {integrity: sha512-KgdcDTp+rDFybx4qBaGxBpKEBCSjBT+bmRXRziD3A9TOlQ1AlaqK1sSttYtgA/t4HEgoDsKVa7OO9jaZAacLgg==} + engines: {node: '>=22.22.0'} peerDependencies: - react-router: 7.15.0 + react-router: 8.1.0 typescript: ^5.1.0 || ^6.0.0 peerDependenciesMeta: typescript: optional: true - '@react-router/node@7.17.0': - resolution: {integrity: sha512-RYR47qM9gJ8zV8Ntial5Rkgcst2YnwWXt0Ai34FezzkDK6AILpxpVatEzFEhNRwbSh6JO6iweY7XhfM4/K5dBA==} - engines: {node: '>=20.0.0'} - peerDependencies: - react-router: 7.17.0 - typescript: ^5.1.0 || ^6.0.0 - peerDependenciesMeta: - typescript: - optional: true - - '@react-router/serve@7.15.0': - resolution: {integrity: sha512-0XtYmwc11vWdYn2zeEXx9E3u0I6TH3bm4uDaMdsyI09S6hl6uc98vBkTSXg7Znm3qR82R/jjtn3LvV2QEZ193w==} - engines: {node: '>=20.0.0'} + '@react-router/serve@8.1.0': + resolution: {integrity: sha512-ZK6BK25axqfWMha773/+5ZPGyh0zSWiOhsRXECnrHImUhzBcVvEK7niApbimIW9zllHegUIqbwb2AC47A6MQ4w==} + engines: {node: '>=22.22.0'} hasBin: true peerDependencies: - react-router: 7.15.0 + react-router: 8.1.0 '@react-types/shared@3.35.0': resolution: {integrity: sha512-iNWvuzEwANttpQpdlu8nPBtdHb0mcCMj1ZTH//iRB5E/14IAnyRlR25rxH7pNLyzHINsPGEKnWvpwDMCT6vziQ==} @@ -1709,176 +1551,22 @@ packages: '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - '@rollup/rollup-android-arm-eabi@4.61.1': - resolution: {integrity: sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.61.1': - resolution: {integrity: sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.61.1': - resolution: {integrity: sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.61.1': - resolution: {integrity: sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.61.1': - resolution: {integrity: sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.61.1': - resolution: {integrity: sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.61.1': - resolution: {integrity: sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-arm-musleabihf@4.61.1': - resolution: {integrity: sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==} - cpu: [arm] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-arm64-gnu@4.61.1': - resolution: {integrity: sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-arm64-musl@4.61.1': - resolution: {integrity: sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-loong64-gnu@4.61.1': - resolution: {integrity: sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==} - cpu: [loong64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-loong64-musl@4.61.1': - resolution: {integrity: sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==} - cpu: [loong64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-ppc64-gnu@4.61.1': - resolution: {integrity: sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-ppc64-musl@4.61.1': - resolution: {integrity: sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==} - cpu: [ppc64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-riscv64-gnu@4.61.1': - resolution: {integrity: sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-riscv64-musl@4.61.1': - resolution: {integrity: sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==} - cpu: [riscv64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-s390x-gnu@4.61.1': - resolution: {integrity: sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==} - cpu: [s390x] - os: [linux] - libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.60.0': resolution: {integrity: sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.61.1': - resolution: {integrity: sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-x64-musl@4.61.1': - resolution: {integrity: sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rollup/rollup-openbsd-x64@4.61.1': - resolution: {integrity: sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==} - cpu: [x64] - os: [openbsd] - - '@rollup/rollup-openharmony-arm64@4.61.1': - resolution: {integrity: sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==} - cpu: [arm64] - os: [openharmony] - - '@rollup/rollup-win32-arm64-msvc@4.61.1': - resolution: {integrity: sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.61.1': - resolution: {integrity: sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-gnu@4.61.1': - resolution: {integrity: sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==} - cpu: [x64] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.61.1': - resolution: {integrity: sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==} - cpu: [x64] - os: [win32] - - '@sentry-internal/browser-utils@10.57.0': - resolution: {integrity: sha512-tXObp954rMTSYKlbftjVXHtNl4t/6ssks3jkqyzmKb+PDPWzabGQO7sWwqVuTjT8Kx/8A3FmriS1bGmqxiJy3A==} - engines: {node: '>=18'} - - '@sentry-internal/feedback@10.57.0': - resolution: {integrity: sha512-ZcF4QhkqGX3iiQSXB2N0N3Awp+j5iqnDRu6PA/qyLFrWqH5ZiiAAgu59OLD9E6XAdg6iFtLYw19MAMZVK8qNOQ==} - engines: {node: '>=18'} - - '@sentry-internal/replay-canvas@10.57.0': - resolution: {integrity: sha512-zsfa4JcfV0AEc9YhNxNabd5lSZL2Av84saAyexGAqcHs+67m9Gd0cGStOzMb/nCl7UAtmdP0aI+G7a3rcxxN/A==} - engines: {node: '>=18'} - - '@sentry-internal/replay@10.57.0': - resolution: {integrity: sha512-Wmnx/6ABynVH1iwuoNUqJNyjIUqsqoGML7qsyivBRKb5Wo2YQtPOQlQYfxfZSvWzGpcoSVdInkRjDssUQxQEQg==} - engines: {node: '>=18'} - - '@sentry-internal/server-utils@10.57.0': - resolution: {integrity: sha512-Qu8ETmX/ITzteG7Im46b9HOxKKzeaIeqNvftaIlFURu1RUQdHbtGerS7QOmXzwnhuqNGNeiCQYkduB798IfRqA==} - engines: {node: '>=18'} - '@sentry/babel-plugin-component-annotate@5.3.0': resolution: {integrity: sha512-p4q8gn8wcFqZGP/s2MnJCAAd8fTikaU6A0mM97RDHQgStcrYiaS0Sc5zUNfb1V+UOLPuvdEdL6MwyxfzjYJQTA==} engines: {node: '>= 18'} - '@sentry/browser@10.57.0': - resolution: {integrity: sha512-s36AQy/CKXTfyY9Z+qUhzNomntZXgfs0rbaK7q9ffnFkqcPwzE8qQtVs58y3Suut56u+AhwSztgQtERcuZ5VIA==} + '@sentry/browser-utils@10.63.0': + resolution: {integrity: sha512-DhUGNN+CH8fzAs6qAsueKPU70qShyTX3NxLhIP+l5DbGXDSXpYXBT6s8ubZus0/LhxpLvI0iSyNIDvZRD/gZaA==} + engines: {node: '>=18'} + + '@sentry/browser@10.63.0': + resolution: {integrity: sha512-0mi56YOkwgyjdLOcN5cB1//EcYzEOt3NZ2GLygE92B3zAAwVM1WgbmibZCXToKFClH7z1uH3VWVfBffmkwIMYw==} engines: {node: '>=18'} '@sentry/bundler-plugin-core@5.3.0': @@ -1937,12 +1625,20 @@ packages: engines: {node: '>= 10'} hasBin: true - '@sentry/core@10.57.0': - resolution: {integrity: sha512-kntItTA2kiT0YpL7encXaF6mkdZMB+y48lwj8w1wkfBpfJAC7sifdgrzLQZqmsqVNE3crg9VfufaAGA+78uFMg==} + '@sentry/conventions@0.12.0': + resolution: {integrity: sha512-z1JQrl/1SLY+8wpzvork6vl+fpsg/oCCxM7HWWhUnI/R+OGNyoIzieQuggX3uUMY7NBtp8UWCQx6FeFazzOF9g==} + engines: {node: '>=14'} + + '@sentry/core@10.63.0': + resolution: {integrity: sha512-OtUbsrnbEHffOF2S2+M5zXa3HIM0U2b4CDVLKMY1dgS0J3ivRF8XvkjvyIcEG/y8JXnwXbnprLyjhG+AqMdUZQ==} engines: {node: '>=18'} - '@sentry/node-core@10.57.0': - resolution: {integrity: sha512-2v2IF6MfTiu7pimWEq2rYhZsmlwyNbs3bHUsrYFPeP/Rpa6ObDuUWPdVEzJjfyK+AqqYZYxZdV0l3+B13kTEmQ==} + '@sentry/feedback@10.63.0': + resolution: {integrity: sha512-If/+72xFg9ylz4twUo3U9gUpZ+Ys+T/3Y09WH7r2gGhWEOF9bp+ta94+Pg7Lb0M2nVD7waz4OxIvB49GEvtLDA==} + engines: {node: '>=18'} + + '@sentry/node-core@10.63.0': + resolution: {integrity: sha512-TaNtkGDRNxH3SjOea2PDtaebkNjMbAH8ZFsEcwlqmadpS7nqSR7z6slZy/iu7y1nLiUdbmcM5JmXwxksy52WRQ==} engines: {node: '>=18'} peerDependencies: '@opentelemetry/api': ^1.9.0 @@ -1950,7 +1646,6 @@ packages: '@opentelemetry/exporter-trace-otlp-http': '>=0.57.0 <1' '@opentelemetry/instrumentation': '>=0.57.1 <1' '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 - '@opentelemetry/semantic-conventions': ^1.39.0 peerDependenciesMeta: '@opentelemetry/api': optional: true @@ -1962,36 +1657,41 @@ packages: optional: true '@opentelemetry/sdk-trace-base': optional: true - '@opentelemetry/semantic-conventions': - optional: true - '@sentry/node@10.57.0': - resolution: {integrity: sha512-7KEStrJ97wPf1fA5nU5ONeTTcIIlh7oT8OMffEVA1PXmlhFoXhcQZVzr4rM+zj9tfMWT01og5Ng/Grgh3dN+FA==} + '@sentry/node@10.63.0': + resolution: {integrity: sha512-E+JfDTdUDGQPRsAfCTR2YgmQgxYdoxk4ks6niHN+ByW8alEZL+nXlcN9vI57qj1LsS4v2jjfLxJf1/cMMt84YA==} engines: {node: '>=18'} - '@sentry/opentelemetry@10.57.0': - resolution: {integrity: sha512-iwRz8cEK0GOISG34aJRO8GdYOk3nfpuT6dT2GDQrxw8f7JjkJKx9LPU8MaenOFa4MhY+Z02hI6NNcrbsoI3cXg==} + '@sentry/opentelemetry@10.63.0': + resolution: {integrity: sha512-8yqi8+Ej/anmMn82blXA0BNMeAMs4av6nx0DzhxDrFya28ZaYOn19PChd3erMidfU0HnLLFNqWiFlYxBKq+/KA==} engines: {node: '>=18'} peerDependencies: '@opentelemetry/api': ^1.9.0 '@opentelemetry/core': ^1.30.1 || ^2.1.0 '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 - '@opentelemetry/semantic-conventions': ^1.39.0 - '@sentry/react-router@10.57.0': - resolution: {integrity: sha512-IWdlqvI46JFpYvHgYQPxNpszVFK12akqKOgudBfEPZFNNzjrEELHyAgj67QWpRCRhpm9y/1UCOVdrQzPcZ3ZSg==} + '@sentry/react-router@10.63.0': + resolution: {integrity: sha512-2aiPxaLSC7gZ9cGgHQZ5CocXmYrjE1ewBg9pj9v9sTA+jdB0LJo9K8haZUCdo60w2wI+IKHq6fS81owXBEddzw==} engines: {node: '>=20'} peerDependencies: - '@react-router/node': 7.x + '@react-router/node': 7.x || ^8.x react: '>=18' - react-router: 7.x + react-router: 7.x || ^8.x - '@sentry/react@10.57.0': - resolution: {integrity: sha512-6QThwQ4XWQ2rwKZEVQ9P9WKl7JlowC7S5LpAvmMdrwlfJBpLDFOsM7tycnIvbXTXf0ZOOuLFPa4L4YYbdyNGmA==} + '@sentry/react@10.63.0': + resolution: {integrity: sha512-+/Y0dd4EMqyqYBJ1D3bAYYuG+ccIx5+IFcbTZ9p+XWnW6nNIVjy5zttVftYo6xOmtTQbzRuPT/vO4dqDHKKmfw==} engines: {node: '>=18'} peerDependencies: react: ^16.14.0 || 17.x || 18.x || 19.x + '@sentry/replay-canvas@10.63.0': + resolution: {integrity: sha512-1Dg6yo+KDNZcE9M6V2EP4DGgTDJMcUgg5ui69w/E96ZZPWErS/bibK2bGj20H3qwpJXlnEwXB5YAJ2fZ620T1A==} + engines: {node: '>=18'} + + '@sentry/replay@10.63.0': + resolution: {integrity: sha512-u4fDaLbd4QmJbU0qGzV5g2B2hjw5utdeZzpTrmq565AS5o6mfaZdCz30zF9R2Unkn0g9SJr90piTN2RMwvDrkw==} + engines: {node: '>=18'} + '@sentry/rollup-plugin@5.3.0': resolution: {integrity: sha512-hgPGPYdQJ/G1cGYOxAb7d4z3V+/k/E5/P/5TFPEEBLuIbFFk+JG0CISUDJdzXJjO382Lb99PBJuXGbueBmO79w==} engines: {node: '>= 18'} @@ -2001,6 +1701,10 @@ packages: rollup: optional: true + '@sentry/server-utils@10.63.0': + resolution: {integrity: sha512-7NN//DG9Yak8t2+6WiEcNmN269iHRVdtZtZIwucEd0OXyZ3FEBBDaBF+bT9V6H/kPtUvVMkHQ72Bn2Xs5JYGxg==} + engines: {node: '>=18'} + '@sentry/vite-plugin@5.3.0': resolution: {integrity: sha512-qcoSzo4n2MulVQ70UUPLq6dTleb2a2HwL2wuwvAgWhPChrYTuk6A6mDg6aQb9fairPAwFPiU9PzOANpoDJcz1A==} engines: {node: '>= 18'} @@ -3066,8 +2770,8 @@ packages: '@zumer/snapdom@2.12.8': resolution: {integrity: sha512-dLX6ZMNjLveasn9yhcruOOfd8GBZBDp59F7iJoLlGf7BnGp0vfVsjxIZDIjN2UTZJN1KoJR/BXsxEnAyY7LuXA==} - accepts@1.3.8: - resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} acorn-import-attributes@1.9.5: @@ -3102,9 +2806,6 @@ packages: arctic@3.7.0: resolution: {integrity: sha512-ZMQ+f6VazDgUJOd+qNV+H7GohNSYal1mVjm5kEaZfE2Ifb7Ss70w+Q7xpJC87qZDkMZIXYf0pTIYZA0OPasSbw==} - arg@5.0.2: - resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -3115,9 +2816,6 @@ packages: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} - array-flatten@1.1.1: - resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} - asn1.js@5.4.1: resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} @@ -3125,6 +2823,10 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + babel-dead-code-elimination@1.0.12: resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==} @@ -3163,9 +2865,9 @@ packages: bn.js@4.12.3: resolution: {integrity: sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==} - body-parser@1.20.5: - resolution: {integrity: sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + body-parser@2.3.0: + resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} + engines: {node: '>=18'} bowser@2.14.1: resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} @@ -3195,10 +2897,6 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - cac@7.0.0: resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} engines: {node: '>=20.19.0'} @@ -3226,9 +2924,9 @@ packages: resolution: {integrity: sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==} engines: {pnpm: '>=8'} - chokidar@4.0.3: - resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} - engines: {node: '>= 14.16.0'} + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} @@ -3274,28 +2972,32 @@ packages: confbox@0.2.4: resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} - content-disposition@0.5.4: - resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} - engines: {node: '>= 0.6'} + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + engines: {node: '>=18'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie-signature@1.0.7: - resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} + cookie-es@3.1.1: + resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} cookie@0.7.2: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} - cookie@1.1.1: - resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} - engines: {node: '>=18'} - core-js@3.49.0: resolution: {integrity: sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==} @@ -3358,10 +3060,6 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - destroy@1.2.0: - resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -3414,9 +3112,6 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - es-module-lexer@2.0.0: resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} @@ -3427,11 +3122,6 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} - esbuild@0.27.7: - resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} - engines: {node: '>=18'} - hasBin: true - escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -3448,6 +3138,14 @@ packages: engines: {node: '>=4'} hasBin: true + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -3465,9 +3163,9 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} - exit-hook@2.2.1: - resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} - engines: {node: '>=6'} + exit-hook@5.1.0: + resolution: {integrity: sha512-INjr2xyxHo7bhAqf5ong++GZPPnpcuBcaXUKt03yf7Fie9yWD7FapL4teOU0+awQazGs5ucBh7xWs/AD+6nhog==} + engines: {node: '>=20'} expand-template@2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} @@ -3477,9 +3175,9 @@ packages: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} - express@4.22.1: - resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} - engines: {node: '>= 0.10.0'} + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} exsolve@1.0.8: resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} @@ -3516,9 +3214,9 @@ packages: file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - finalhandler@1.3.2: - resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} - engines: {node: '>= 0.8'} + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} @@ -3543,9 +3241,9 @@ packages: fractional-indexing-jittered@0.9.1: resolution: {integrity: sha512-qyzDZ7JXWf/yZT2rQDpQwFBbIaZS2o+zb0s740vqreXQ6bFQPd8tAy4D1gGN0CUeIcnNHjuvb0EaLnqHhGV/PA==} - fresh@0.5.2: - resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} - engines: {node: '>= 0.6'} + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} @@ -3583,10 +3281,6 @@ packages: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} - get-port@5.1.1: - resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} - engines: {node: '>=8'} - get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -3666,8 +3360,8 @@ packages: typescript: optional: true - iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} ics@3.12.0: @@ -3713,6 +3407,9 @@ packages: resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} engines: {node: '>=0.10.0'} + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + isbot@5.1.42: resolution: {integrity: sha512-/SXsVh7KpPRISrD4ffrGSxnTLlUBzEQUfWIusaJPrpJ93FW1P0YEZri5vAUkFsA0m2HRUhQRQadk2wJ+EeKowQ==} engines: {node: '>=18'} @@ -3736,6 +3433,11 @@ packages: engines: {node: '>=6'} hasBin: true + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -3924,33 +3626,25 @@ packages: mdurl@2.0.0: resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} - media-typer@0.3.0: - resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} - engines: {node: '>= 0.6'} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} - merge-descriptors@1.0.3: - resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} - methods@1.1.2: - resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} - engines: {node: '>= 0.6'} - - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} + meriyah@6.1.4: + resolution: {integrity: sha512-Sz8FzjzI0kN13GK/6MVEsVzMZEPvOhnmmI1lU5+/1cGOiK3QUahntrNNtdVeihrO7t9JpoH75iMNXg6R6uWflQ==} + engines: {node: '>=18.0.0'} mime-db@1.54.0: resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} engines: {node: '>= 0.6'} - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - - mime@1.6.0: - resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} - engines: {node: '>=4'} - hasBin: true + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} @@ -4018,14 +3712,14 @@ packages: resolution: {integrity: sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==} hasBin: true - negotiator@0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} - engines: {node: '>= 0.6'} - negotiator@0.6.4: resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} engines: {node: '>= 0.6'} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + neverthrow@8.2.0: resolution: {integrity: sha512-kOCT/1MCPAxY5iUV3wytNFUMUolzuwd/VF/1KCx7kf6CutrOsTie+84zTGTpgQycjvfLdBBdvBvFLqFD2c0wkQ==} engines: {node: '>=18'} @@ -4145,11 +3839,8 @@ packages: resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} engines: {node: 18 || 20 || >=22} - path-to-regexp@0.1.13: - resolution: {integrity: sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==} - - pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -4280,14 +3971,14 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - qs@6.14.2: - resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==} - engines: {node: '>=0.6'} - qs@6.15.1: resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} engines: {node: '>=0.6'} + qs@6.15.3: + resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} + engines: {node: '>=0.6'} + railroad-diagrams@1.0.0: resolution: {integrity: sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==} @@ -4302,9 +3993,9 @@ packages: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} - raw-body@2.5.3: - resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} - engines: {node: '>= 0.8'} + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} @@ -4364,8 +4055,8 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - react-refresh@0.14.2: - resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + react-refresh@0.18.0: + resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==} engines: {node: '>=0.10.0'} react-remove-scroll-bar@2.3.8: @@ -4388,12 +4079,12 @@ packages: '@types/react': optional: true - react-router@7.17.0: - resolution: {integrity: sha512-FDELK7rTMlCHO5+reyXsPlmfr7N1F91lPHsWYfMEGQm/KQ+F4JFM8jGoeQDmDvdTs93Fw9aSilH+uKRb4/jXvQ==} - engines: {node: '>=20.0.0'} + react-router@8.1.0: + resolution: {integrity: sha512-Mdfi61uObuvWNN9OhChOC0HV6YWOIfKRzEWOvCHRSuQg8IM+Nv10edaM/2HE8ZixBpUTdQbruyWqC3sDkkh9vw==} + engines: {node: '>=22.22.0'} peerDependencies: - react: '>=18' - react-dom: '>=18' + react: '>=19.2.7' + react-dom: '>=19.2.7' peerDependenciesMeta: react-dom: optional: true @@ -4427,9 +4118,9 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} rematrix@0.2.2: resolution: {integrity: sha512-agFFS3RzrLXJl5LY5xg/xYyXvUuVAnkhgKO7RaO9J1Ssth6yvbO+PIiV67V59MB5NCdAK2flvGvNT4mdKVniFA==} @@ -4482,14 +4173,13 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - rollup@4.61.1: - resolution: {integrity: sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - rope-sequence@1.3.4: resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + runes2@1.1.4: resolution: {integrity: sha512-LNPnEDPOOU4ehF71m5JoQyzT2yxwD6ZreFJ7MxZUAoMKNMY1XrAo60H1CUoX5ncSm0rIuKlqn9JZNRrRkNou2g==} @@ -4513,6 +4203,9 @@ packages: resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} engines: {node: '>=4'} + semifies@1.0.0: + resolution: {integrity: sha512-xXR3KGeoxTNWPD4aBvL5NUpMTT7WMANr3EWnaS190QVkY52lqqcVRD7Q05UVbBhiWDGWMlJEUam9m7uFFGVScw==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -4522,16 +4215,13 @@ packages: engines: {node: '>=10'} hasBin: true - send@0.19.2: - resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} - engines: {node: '>= 0.8.0'} + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} - serve-static@1.16.3: - resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} - engines: {node: '>= 0.8.0'} - - set-cookie-parser@2.7.2: - resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -4548,6 +4238,10 @@ packages: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} engines: {node: '>= 0.4'} + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + side-channel-map@1.0.1: resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} engines: {node: '>= 0.4'} @@ -4560,6 +4254,10 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -4720,9 +4418,9 @@ packages: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} - type-is@1.6.18: - resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} - engines: {node: '>= 0.6'} + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} types-ramda@0.31.0: resolution: {integrity: sha512-vaoC35CRC3xvL8Z6HkshDbi6KWM1ezK0LHN0YyxXWUn9HKzBNg/T3xSGlJZjCYspnOD3jE7bcizsp0bUXZDxnQ==} @@ -4784,10 +4482,6 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - utils-merge@1.0.1: - resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} - engines: {node: '>= 0.4.0'} - valibot@1.4.1: resolution: {integrity: sha512-klCmFTz2jeDluy9RwX+F884TCiogtdBJ/YaxSx1EOBYXa3NXNWj8kR1jjN8rzluwojJVWWaHJ4r1U5LfICnM3g==} peerDependencies: @@ -4800,11 +4494,6 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - vite-node@3.2.4: - resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - vite-node@6.0.0: resolution: {integrity: sha512-oj4PVrT+pDh6GYf5wfUXkcZyekYS8kKPfLPXVl8qe324Ec6l4K2DUKNadRbZ3LQl0qGcDz+PyOo7ZAh00Y+JjQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4816,46 +4505,6 @@ packages: '@babel/core': ^7.0.0 vite: ^2.7.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - vite@7.3.5: - resolution: {integrity: sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - jiti: '>=1.21.0' - less: ^4.0.0 - lightningcss: ^1.21.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - vite@8.0.16: resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -5062,6 +4711,30 @@ packages: snapshots: + '@apm-js-collab/code-transformer-bundler-plugins@0.5.0': + dependencies: + '@apm-js-collab/code-transformer': 0.15.0 + es-module-lexer: 2.1.0 + magic-string: 0.30.21 + module-details-from-path: 1.0.4 + + '@apm-js-collab/code-transformer@0.15.0': + dependencies: + '@types/estree': 1.0.9 + astring: 1.9.0 + esquery: 1.7.0 + meriyah: 6.1.4 + semifies: 1.0.0 + source-map: 0.6.1 + + '@apm-js-collab/tracing-hooks@0.10.0': + dependencies: + '@apm-js-collab/code-transformer': 0.15.0 + debug: 4.4.3 + module-details-from-path: 1.0.4 + transitivePeerDependencies: + - supports-color + '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 @@ -5592,84 +5265,6 @@ snapshots: '@epic-web/invariant@1.0.0': {} - '@esbuild/aix-ppc64@0.27.7': - optional: true - - '@esbuild/android-arm64@0.27.7': - optional: true - - '@esbuild/android-arm@0.27.7': - optional: true - - '@esbuild/android-x64@0.27.7': - optional: true - - '@esbuild/darwin-arm64@0.27.7': - optional: true - - '@esbuild/darwin-x64@0.27.7': - optional: true - - '@esbuild/freebsd-arm64@0.27.7': - optional: true - - '@esbuild/freebsd-x64@0.27.7': - optional: true - - '@esbuild/linux-arm64@0.27.7': - optional: true - - '@esbuild/linux-arm@0.27.7': - optional: true - - '@esbuild/linux-ia32@0.27.7': - optional: true - - '@esbuild/linux-loong64@0.27.7': - optional: true - - '@esbuild/linux-mips64el@0.27.7': - optional: true - - '@esbuild/linux-ppc64@0.27.7': - optional: true - - '@esbuild/linux-riscv64@0.27.7': - optional: true - - '@esbuild/linux-s390x@0.27.7': - optional: true - - '@esbuild/linux-x64@0.27.7': - optional: true - - '@esbuild/netbsd-arm64@0.27.7': - optional: true - - '@esbuild/netbsd-x64@0.27.7': - optional: true - - '@esbuild/openbsd-arm64@0.27.7': - optional: true - - '@esbuild/openbsd-x64@0.27.7': - optional: true - - '@esbuild/openharmony-arm64@0.27.7': - optional: true - - '@esbuild/sunos-x64@0.27.7': - optional: true - - '@esbuild/win32-arm64@0.27.7': - optional: true - - '@esbuild/win32-ia32@0.27.7': - optional: true - - '@esbuild/win32-x64@0.27.7': - optional: true - '@faker-js/faker@10.4.0': {} '@floating-ui/core@1.7.5': @@ -5737,8 +5332,6 @@ snapshots: '@mjackson/headers@0.10.0': {} - '@mjackson/node-fetch-server@0.2.0': {} - '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@emnapi/core': 1.10.0 @@ -6306,7 +5899,7 @@ snapshots: '@radix-ui/rect@1.1.1': {} - '@react-router/dev@7.17.0(@react-router/serve@7.15.0(patch_hash=38a61caab4f8dc67c82ecfb17507d5a650ea7b632b4e58a369c0aa4583611c87)(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3))(@types/node@26.0.0)(jiti@2.7.0)(lightningcss@1.32.0)(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))(yaml@2.9.0)': + '@react-router/dev@8.1.0(@react-router/serve@8.1.0(patch_hash=4d79b209948d4237f1ca3a5cca417c6d11519d8b1ab7084c75c46a39e3ac27d6)(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3))(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0))': dependencies: '@babel/core': 7.29.7 '@babel/generator': 7.29.7 @@ -6315,79 +5908,58 @@ snapshots: '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7) '@babel/traverse': 7.29.7 '@babel/types': 7.29.7 - '@react-router/node': 7.17.0(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) + '@react-router/node': 8.1.0(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) '@remix-run/node-fetch-server': 0.13.3 - arg: 5.0.2 babel-dead-code-elimination: 1.0.12 - chokidar: 4.0.3 + chokidar: 5.0.0 dedent: 1.7.2 - es-module-lexer: 1.7.0 - exit-hook: 2.2.1 + es-module-lexer: 2.1.0 + exit-hook: 5.1.0 isbot: 5.1.42 - jsesc: 3.0.2 + jsesc: 3.1.0 lodash: 4.18.1 p-map: 7.0.4 - pathe: 1.1.2 + pathe: 2.0.3 picocolors: 1.1.1 pkg-types: 2.3.1 prettier: 3.8.4 - react-refresh: 0.14.2 - react-router: 7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react-refresh: 0.18.0 + react-router: 8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) semver: 7.8.4 tinyglobby: 0.2.17 valibot: 1.4.1(typescript@6.0.3) - vite: 8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) - vite-node: 3.2.4(@types/node@26.0.0)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0) + vite: 8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0) optionalDependencies: - '@react-router/serve': 7.15.0(patch_hash=38a61caab4f8dc67c82ecfb17507d5a650ea7b632b4e58a369c0aa4583611c87)(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) + '@react-router/serve': 8.1.0(patch_hash=4d79b209948d4237f1ca3a5cca417c6d11519d8b1ab7084c75c46a39e3ac27d6)(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - - '@types/node' - babel-plugin-macros - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - supports-color - - terser - - tsx - - yaml - '@react-router/express@7.15.0(express@4.22.1)(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)': + '@react-router/express@8.1.0(express@5.2.1)(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)': dependencies: - '@react-router/node': 7.15.0(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) - express: 4.22.1 - react-router: 7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@react-router/node': 8.1.0(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) + express: 5.2.1 + react-router: 8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) optionalDependencies: typescript: 6.0.3 - '@react-router/node@7.15.0(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)': + '@react-router/node@8.1.0(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)': dependencies: - '@mjackson/node-fetch-server': 0.2.0 - react-router: 7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@remix-run/node-fetch-server': 0.13.3 + react-router: 8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) optionalDependencies: typescript: 6.0.3 - '@react-router/node@7.17.0(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)': + '@react-router/serve@8.1.0(patch_hash=4d79b209948d4237f1ca3a5cca417c6d11519d8b1ab7084c75c46a39e3ac27d6)(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)': dependencies: - '@mjackson/node-fetch-server': 0.2.0 - react-router: 7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - optionalDependencies: - typescript: 6.0.3 - - '@react-router/serve@7.15.0(patch_hash=38a61caab4f8dc67c82ecfb17507d5a650ea7b632b4e58a369c0aa4583611c87)(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3)': - dependencies: - '@mjackson/node-fetch-server': 0.2.0 - '@react-router/express': 7.15.0(express@4.22.1)(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) - '@react-router/node': 7.15.0(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) + '@react-router/express': 8.1.0(express@5.2.1)(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) + '@react-router/node': 8.1.0(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) + '@remix-run/node-fetch-server': 0.13.3 compression: 1.8.1 - express: 4.22.1 - get-port: 5.1.1 + express: 5.2.1 morgan: 1.10.1 - react-router: 7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react-router: 8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) source-map-support: 0.5.21 transitivePeerDependencies: - supports-color @@ -6462,115 +6034,22 @@ snapshots: '@rolldown/pluginutils@1.0.1': {} - '@rollup/rollup-android-arm-eabi@4.61.1': - optional: true - - '@rollup/rollup-android-arm64@4.61.1': - optional: true - - '@rollup/rollup-darwin-arm64@4.61.1': - optional: true - - '@rollup/rollup-darwin-x64@4.61.1': - optional: true - - '@rollup/rollup-freebsd-arm64@4.61.1': - optional: true - - '@rollup/rollup-freebsd-x64@4.61.1': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.61.1': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.61.1': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.61.1': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.61.1': - optional: true - - '@rollup/rollup-linux-loong64-gnu@4.61.1': - optional: true - - '@rollup/rollup-linux-loong64-musl@4.61.1': - optional: true - - '@rollup/rollup-linux-ppc64-gnu@4.61.1': - optional: true - - '@rollup/rollup-linux-ppc64-musl@4.61.1': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.61.1': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.61.1': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.61.1': - optional: true - '@rollup/rollup-linux-x64-gnu@4.60.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.61.1': - optional: true - - '@rollup/rollup-linux-x64-musl@4.61.1': - optional: true - - '@rollup/rollup-openbsd-x64@4.61.1': - optional: true - - '@rollup/rollup-openharmony-arm64@4.61.1': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.61.1': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.61.1': - optional: true - - '@rollup/rollup-win32-x64-gnu@4.61.1': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.61.1': - optional: true - - '@sentry-internal/browser-utils@10.57.0': - dependencies: - '@sentry/core': 10.57.0 - - '@sentry-internal/feedback@10.57.0': - dependencies: - '@sentry/core': 10.57.0 - - '@sentry-internal/replay-canvas@10.57.0': - dependencies: - '@sentry-internal/replay': 10.57.0 - '@sentry/core': 10.57.0 - - '@sentry-internal/replay@10.57.0': - dependencies: - '@sentry-internal/browser-utils': 10.57.0 - '@sentry/core': 10.57.0 - - '@sentry-internal/server-utils@10.57.0': - dependencies: - '@sentry/core': 10.57.0 - '@sentry/babel-plugin-component-annotate@5.3.0': {} - '@sentry/browser@10.57.0': + '@sentry/browser-utils@10.63.0': dependencies: - '@sentry-internal/browser-utils': 10.57.0 - '@sentry-internal/feedback': 10.57.0 - '@sentry-internal/replay': 10.57.0 - '@sentry-internal/replay-canvas': 10.57.0 - '@sentry/core': 10.57.0 + '@sentry/core': 10.63.0 + + '@sentry/browser@10.63.0': + dependencies: + '@sentry/browser-utils': 10.63.0 + '@sentry/core': 10.63.0 + '@sentry/feedback': 10.63.0 + '@sentry/replay': 10.63.0 + '@sentry/replay-canvas': 10.63.0 '@sentry/bundler-plugin-core@5.3.0': dependencies: @@ -6629,86 +6108,112 @@ snapshots: - encoding - supports-color - '@sentry/core@10.57.0': {} + '@sentry/conventions@0.12.0': {} - '@sentry/node-core@10.57.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.214.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1)': + '@sentry/core@10.63.0': {} + + '@sentry/feedback@10.63.0': dependencies: - '@sentry/core': 10.57.0 - '@sentry/opentelemetry': 10.57.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1) + '@sentry/core': 10.63.0 + + '@sentry/node-core@10.63.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.214.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))': + dependencies: + '@sentry/conventions': 0.12.0 + '@sentry/core': 10.63.0 + '@sentry/opentelemetry': 10.63.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1)) import-in-the-middle: 3.0.2 optionalDependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.41.1 - '@sentry/node@10.57.0': + '@sentry/node@10.63.0(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': 2.8.0(@opentelemetry/api@1.9.1) '@opentelemetry/semantic-conventions': 1.41.1 - '@sentry-internal/server-utils': 10.57.0 - '@sentry/core': 10.57.0 - '@sentry/node-core': 10.57.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.214.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1) - '@sentry/opentelemetry': 10.57.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1) + '@sentry/conventions': 0.12.0 + '@sentry/core': 10.63.0 + '@sentry/node-core': 10.63.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/instrumentation@0.214.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1)) + '@sentry/opentelemetry': 10.63.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1)) + '@sentry/server-utils': 10.63.0 import-in-the-middle: 3.0.2 transitivePeerDependencies: + - '@opentelemetry/core' - '@opentelemetry/exporter-trace-otlp-http' - supports-color - '@sentry/opentelemetry@10.57.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/semantic-conventions@1.41.1)': + '@sentry/opentelemetry@10.63.0(@opentelemetry/api@1.9.1)(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@opentelemetry/sdk-trace-base@2.8.0(@opentelemetry/api@1.9.1))': dependencies: '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': 2.8.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.41.1 - '@sentry/core': 10.57.0 + '@sentry/conventions': 0.12.0 + '@sentry/core': 10.63.0 - '@sentry/react-router@10.57.0(@react-router/node@7.17.0(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3))(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)(rollup@4.61.1)': + '@sentry/react-router@10.63.0(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1))(@react-router/node@8.1.0(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3))(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)': dependencies: '@opentelemetry/api': 1.9.1 - '@opentelemetry/core': 2.8.0(@opentelemetry/api@1.9.1) '@opentelemetry/instrumentation': 0.214.0(@opentelemetry/api@1.9.1) - '@opentelemetry/semantic-conventions': 1.41.1 - '@react-router/node': 7.17.0(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) - '@sentry/browser': 10.57.0 + '@react-router/node': 8.1.0(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(typescript@6.0.3) + '@sentry/browser': 10.63.0 '@sentry/cli': 2.58.6 - '@sentry/core': 10.57.0 - '@sentry/node': 10.57.0 - '@sentry/react': 10.57.0(react@19.2.7) - '@sentry/vite-plugin': 5.3.0(rollup@4.61.1) + '@sentry/conventions': 0.12.0 + '@sentry/core': 10.63.0 + '@sentry/node': 10.63.0(@opentelemetry/core@2.8.0(@opentelemetry/api@1.9.1)) + '@sentry/react': 10.63.0(react@19.2.7) + '@sentry/vite-plugin': 5.3.0 glob: 13.0.6 react: 19.2.7 - react-router: 7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react-router: 8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) transitivePeerDependencies: + - '@opentelemetry/core' - '@opentelemetry/exporter-trace-otlp-http' - encoding - rollup - supports-color - '@sentry/react@10.57.0(react@19.2.7)': + '@sentry/react@10.63.0(react@19.2.7)': dependencies: - '@sentry/browser': 10.57.0 - '@sentry/core': 10.57.0 + '@sentry/browser': 10.63.0 + '@sentry/core': 10.63.0 react: 19.2.7 - '@sentry/rollup-plugin@5.3.0(rollup@4.61.1)': + '@sentry/replay-canvas@10.63.0': + dependencies: + '@sentry/core': 10.63.0 + '@sentry/replay': 10.63.0 + + '@sentry/replay@10.63.0': + dependencies: + '@sentry/browser-utils': 10.63.0 + '@sentry/core': 10.63.0 + + '@sentry/rollup-plugin@5.3.0': dependencies: '@sentry/bundler-plugin-core': 5.3.0 magic-string: 0.30.21 - optionalDependencies: - rollup: 4.61.1 transitivePeerDependencies: - encoding - supports-color - '@sentry/vite-plugin@5.3.0(rollup@4.61.1)': + '@sentry/server-utils@10.63.0': + dependencies: + '@apm-js-collab/code-transformer': 0.15.0 + '@apm-js-collab/code-transformer-bundler-plugins': 0.5.0 + '@apm-js-collab/tracing-hooks': 0.10.0 + '@sentry/conventions': 0.12.0 + '@sentry/core': 10.63.0 + magic-string: 0.30.21 + transitivePeerDependencies: + - supports-color + + '@sentry/vite-plugin@5.3.0': dependencies: '@sentry/bundler-plugin-core': 5.3.0 - '@sentry/rollup-plugin': 5.3.0(rollup@4.61.1) + '@sentry/rollup-plugin': 5.3.0 transitivePeerDependencies: - encoding - rollup @@ -7915,29 +7420,29 @@ snapshots: '@use-gesture/core': 10.3.1 react: 19.2.7 - '@vitest/browser-playwright@4.1.8(playwright@1.60.0)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.8)': + '@vitest/browser-playwright@4.1.8(playwright@1.60.0)(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.8)': dependencies: - '@vitest/browser': 4.1.8(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.8) - '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + '@vitest/browser': 4.1.8(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.8) + '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0)) playwright: 1.60.0 tinyrainbow: 3.1.0 - vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@26.0.0)(@vitest/browser-playwright@4.1.8)(@vitest/ui@4.1.8)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@26.0.0)(@vitest/browser-playwright@4.1.8)(@vitest/ui@4.1.8)(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0)) transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite - '@vitest/browser@4.1.8(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.8)': + '@vitest/browser@4.1.8(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.8)': dependencies: '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0)) '@vitest/utils': 4.1.8 magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.0 - vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@26.0.0)(@vitest/browser-playwright@4.1.8)(@vitest/ui@4.1.8)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@26.0.0)(@vitest/browser-playwright@4.1.8)(@vitest/ui@4.1.8)(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0)) ws: 8.21.0 transitivePeerDependencies: - bufferutil @@ -7954,13 +7459,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.8(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))': + '@vitest/mocker@4.1.8(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.8 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) + vite: 8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0) '@vitest/pretty-format@4.1.8': dependencies: @@ -7989,7 +7494,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@26.0.0)(@vitest/browser-playwright@4.1.8)(@vitest/ui@4.1.8)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@26.0.0)(@vitest/browser-playwright@4.1.8)(@vitest/ui@4.1.8)(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0)) '@vitest/utils@4.1.8': dependencies: @@ -7999,10 +7504,10 @@ snapshots: '@zumer/snapdom@2.12.8': {} - accepts@1.3.8: + accepts@2.0.0: dependencies: - mime-types: 2.1.35 - negotiator: 0.6.3 + mime-types: 3.0.2 + negotiator: 1.0.0 acorn-import-attributes@1.9.5(acorn@8.17.0): dependencies: @@ -8032,8 +7537,6 @@ snapshots: '@oslojs/encoding': 1.1.0 '@oslojs/jwt': 0.2.0 - arg@5.0.2: {} - argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -8044,8 +7547,6 @@ snapshots: dependencies: tslib: 2.8.1 - array-flatten@1.1.1: {} - asn1.js@5.4.1: dependencies: bn.js: 4.12.3 @@ -8055,6 +7556,8 @@ snapshots: assertion-error@2.0.1: {} + astring@1.9.0: {} + babel-dead-code-elimination@1.0.12: dependencies: '@babel/core': 7.29.7 @@ -8097,20 +7600,17 @@ snapshots: bn.js@4.12.3: {} - body-parser@1.20.5: + body-parser@2.3.0: dependencies: bytes: 3.1.2 - content-type: 1.0.5 - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 + content-type: 2.0.0 + debug: 4.4.3 http-errors: 2.0.1 - iconv-lite: 0.4.24 + iconv-lite: 0.7.2 on-finished: 2.4.1 - qs: 6.15.1 - raw-body: 2.5.3 - type-is: 1.6.18 - unpipe: 1.0.0 + qs: 6.15.3 + raw-body: 3.0.2 + type-is: 2.1.0 transitivePeerDependencies: - supports-color @@ -8144,8 +7644,6 @@ snapshots: bytes@3.1.2: {} - cac@6.7.14: {} - cac@7.0.0: {} call-bind-apply-helpers@1.0.2: @@ -8171,9 +7669,9 @@ snapshots: dependencies: '@kurkle/color': 0.3.4 - chokidar@4.0.3: + chokidar@5.0.0: dependencies: - readdirp: 4.1.2 + readdirp: 5.0.0 chownr@1.1.4: {} @@ -8222,20 +7720,20 @@ snapshots: confbox@0.2.4: {} - content-disposition@0.5.4: - dependencies: - safe-buffer: 5.2.1 + content-disposition@1.1.0: {} content-type@1.0.5: {} + content-type@2.0.0: {} + convert-source-map@2.0.0: {} - cookie-signature@1.0.7: {} + cookie-es@3.1.1: {} + + cookie-signature@1.2.2: {} cookie@0.7.2: {} - cookie@1.1.1: {} - core-js@3.49.0: {} crelt@1.0.6: {} @@ -8275,8 +7773,6 @@ snapshots: dequal@2.0.3: {} - destroy@1.2.0: {} - detect-libc@2.1.2: {} detect-node-es@1.1.0: {} @@ -8315,8 +7811,6 @@ snapshots: es-errors@1.3.0: {} - es-module-lexer@1.7.0: {} - es-module-lexer@2.0.0: {} es-module-lexer@2.1.0: {} @@ -8325,35 +7819,6 @@ snapshots: dependencies: es-errors: 1.3.0 - esbuild@0.27.7: - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.7 - '@esbuild/android-arm': 0.27.7 - '@esbuild/android-arm64': 0.27.7 - '@esbuild/android-x64': 0.27.7 - '@esbuild/darwin-arm64': 0.27.7 - '@esbuild/darwin-x64': 0.27.7 - '@esbuild/freebsd-arm64': 0.27.7 - '@esbuild/freebsd-x64': 0.27.7 - '@esbuild/linux-arm': 0.27.7 - '@esbuild/linux-arm64': 0.27.7 - '@esbuild/linux-ia32': 0.27.7 - '@esbuild/linux-loong64': 0.27.7 - '@esbuild/linux-mips64el': 0.27.7 - '@esbuild/linux-ppc64': 0.27.7 - '@esbuild/linux-riscv64': 0.27.7 - '@esbuild/linux-s390x': 0.27.7 - '@esbuild/linux-x64': 0.27.7 - '@esbuild/netbsd-arm64': 0.27.7 - '@esbuild/netbsd-x64': 0.27.7 - '@esbuild/openbsd-arm64': 0.27.7 - '@esbuild/openbsd-x64': 0.27.7 - '@esbuild/openharmony-arm64': 0.27.7 - '@esbuild/sunos-x64': 0.27.7 - '@esbuild/win32-arm64': 0.27.7 - '@esbuild/win32-ia32': 0.27.7 - '@esbuild/win32-x64': 0.27.7 - escalade@3.2.0: {} escape-html@1.0.3: {} @@ -8362,6 +7827,12 @@ snapshots: esprima@4.0.1: {} + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.9 @@ -8374,44 +7845,41 @@ snapshots: events@3.3.0: {} - exit-hook@2.2.1: {} + exit-hook@5.1.0: {} expand-template@2.0.3: {} expect-type@1.3.0: {} - express@4.22.1: + express@5.2.1: dependencies: - accepts: 1.3.8 - array-flatten: 1.1.1 - body-parser: 1.20.5 - content-disposition: 0.5.4 + accepts: 2.0.0 + body-parser: 2.3.0 + content-disposition: 1.1.0 content-type: 1.0.5 cookie: 0.7.2 - cookie-signature: 1.0.7 - debug: 2.6.9 + cookie-signature: 1.2.2 + debug: 4.4.3 depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 1.3.2 - fresh: 0.5.2 + finalhandler: 2.1.1 + fresh: 2.0.0 http-errors: 2.0.1 - merge-descriptors: 1.0.3 - methods: 1.1.2 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 on-finished: 2.4.1 + once: 1.4.0 parseurl: 1.3.3 - path-to-regexp: 0.1.13 proxy-addr: 2.0.7 - qs: 6.14.2 + qs: 6.15.1 range-parser: 1.2.1 - safe-buffer: 5.2.1 - send: 0.19.2 - serve-static: 1.16.3 - setprototypeof: 1.2.0 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 statuses: 2.0.2 - type-is: 1.6.18 - utils-merge: 1.0.1 + type-is: 2.1.0 vary: 1.1.2 transitivePeerDependencies: - supports-color @@ -8448,15 +7916,14 @@ snapshots: file-uri-to-path@1.0.0: {} - finalhandler@1.3.2: + finalhandler@2.1.1: dependencies: - debug: 2.6.9 + debug: 4.4.3 encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 parseurl: 1.3.3 statuses: 2.0.2 - unpipe: 1.0.0 transitivePeerDependencies: - supports-color @@ -8479,7 +7946,7 @@ snapshots: fractional-indexing-jittered@0.9.1: {} - fresh@0.5.2: {} + fresh@2.0.0: {} fs-constants@1.0.0: {} @@ -8516,8 +7983,6 @@ snapshots: get-nonce@1.0.1: {} - get-port@5.1.1: {} - get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -8602,7 +8067,7 @@ snapshots: optionalDependencies: typescript: 6.0.3 - iconv-lite@0.4.24: + iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 @@ -8641,6 +8106,8 @@ snapshots: is-plain-object@5.0.0: {} + is-promise@4.0.0: {} + isbot@5.1.42: {} isexe@2.0.0: {} @@ -8656,6 +8123,8 @@ snapshots: jsesc@3.0.2: {} + jsesc@3.1.0: {} + json5@2.2.3: {} jsoncrush@1.1.8: {} @@ -8810,21 +8279,17 @@ snapshots: mdurl@2.0.0: {} - media-typer@0.3.0: {} + media-typer@1.1.0: {} - merge-descriptors@1.0.3: {} + merge-descriptors@2.0.0: {} - methods@1.1.2: {} - - mime-db@1.52.0: {} + meriyah@6.1.4: {} mime-db@1.54.0: {} - mime-types@2.1.35: + mime-types@3.0.2: dependencies: - mime-db: 1.52.0 - - mime@1.6.0: {} + mime-db: 1.54.0 mimic-response@3.1.0: {} @@ -8877,10 +8342,10 @@ snapshots: railroad-diagrams: 1.0.0 randexp: 0.4.6 - negotiator@0.6.3: {} - negotiator@0.6.4: {} + negotiator@1.0.0: {} + neverthrow@8.2.0: optionalDependencies: '@rollup/rollup-linux-x64-gnu': 4.60.0 @@ -9015,9 +8480,7 @@ snapshots: lru-cache: 11.5.1 minipass: 7.1.3 - path-to-regexp@0.1.13: {} - - pathe@1.1.2: {} + path-to-regexp@8.4.2: {} pathe@2.0.3: {} @@ -9195,14 +8658,15 @@ snapshots: dependencies: react: 19.2.7 - qs@6.14.2: - dependencies: - side-channel: 1.1.0 - qs@6.15.1: dependencies: side-channel: 1.1.0 + qs@6.15.3: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + railroad-diagrams@1.0.0: {} ramda@0.32.0: {} @@ -9214,11 +8678,11 @@ snapshots: range-parser@1.2.1: {} - raw-body@2.5.3: + raw-body@3.0.2: dependencies: bytes: 3.1.2 http-errors: 2.0.1 - iconv-lite: 0.4.24 + iconv-lite: 0.7.2 unpipe: 1.0.0 rc@1.2.8: @@ -9287,7 +8751,7 @@ snapshots: react-is@16.13.1: {} - react-refresh@0.14.2: {} + react-refresh@0.18.0: {} react-remove-scroll-bar@2.3.8(@types/react@19.2.17)(react@19.2.7): dependencies: @@ -9308,11 +8772,10 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 - react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): + react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: - cookie: 1.1.1 + cookie-es: 3.1.1 react: 19.2.7 - set-cookie-parser: 2.7.2 optionalDependencies: react-dom: 19.2.7(react@19.2.7) @@ -9346,7 +8809,7 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 - readdirp@4.1.2: {} + readdirp@5.0.0: {} rematrix@0.2.2: {} @@ -9361,12 +8824,12 @@ snapshots: remix-auth@4.2.0: {} - remix-i18next@7.5.0(i18next@26.3.1(typescript@6.0.3))(react-i18next@17.0.8(i18next@26.3.1(typescript@6.0.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(react-router@7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7): + remix-i18next@7.5.0(i18next@26.3.1(typescript@6.0.3))(react-i18next@17.0.8(i18next@26.3.1(typescript@6.0.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(react-router@8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7): dependencies: i18next: 26.3.1(typescript@6.0.3) react: 19.2.7 react-i18next: 17.0.8(i18next@26.3.1(typescript@6.0.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3) - react-router: 7.17.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react-router: 8.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) require-directory@2.1.1: {} @@ -9409,39 +8872,18 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.3 '@rolldown/binding-win32-x64-msvc': 1.0.3 - rollup@4.61.1: - dependencies: - '@types/estree': 1.0.9 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.61.1 - '@rollup/rollup-android-arm64': 4.61.1 - '@rollup/rollup-darwin-arm64': 4.61.1 - '@rollup/rollup-darwin-x64': 4.61.1 - '@rollup/rollup-freebsd-arm64': 4.61.1 - '@rollup/rollup-freebsd-x64': 4.61.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.61.1 - '@rollup/rollup-linux-arm-musleabihf': 4.61.1 - '@rollup/rollup-linux-arm64-gnu': 4.61.1 - '@rollup/rollup-linux-arm64-musl': 4.61.1 - '@rollup/rollup-linux-loong64-gnu': 4.61.1 - '@rollup/rollup-linux-loong64-musl': 4.61.1 - '@rollup/rollup-linux-ppc64-gnu': 4.61.1 - '@rollup/rollup-linux-ppc64-musl': 4.61.1 - '@rollup/rollup-linux-riscv64-gnu': 4.61.1 - '@rollup/rollup-linux-riscv64-musl': 4.61.1 - '@rollup/rollup-linux-s390x-gnu': 4.61.1 - '@rollup/rollup-linux-x64-gnu': 4.61.1 - '@rollup/rollup-linux-x64-musl': 4.61.1 - '@rollup/rollup-openbsd-x64': 4.61.1 - '@rollup/rollup-openharmony-arm64': 4.61.1 - '@rollup/rollup-win32-arm64-msvc': 4.61.1 - '@rollup/rollup-win32-ia32-msvc': 4.61.1 - '@rollup/rollup-win32-x64-gnu': 4.61.1 - '@rollup/rollup-win32-x64-msvc': 4.61.1 - fsevents: 2.3.3 - rope-sequence@1.3.4: {} + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + runes2@1.1.4: {} sade@1.8.1: @@ -9461,21 +8903,21 @@ snapshots: extend-shallow: 2.0.1 kind-of: 6.0.3 + semifies@1.0.0: {} + semver@6.3.1: {} semver@7.8.4: {} - send@0.19.2: + send@1.2.1: dependencies: - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 + debug: 4.4.3 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - fresh: 0.5.2 + fresh: 2.0.0 http-errors: 2.0.1 - mime: 1.6.0 + mime-types: 3.0.2 ms: 2.1.3 on-finished: 2.4.1 range-parser: 1.2.1 @@ -9483,17 +8925,15 @@ snapshots: transitivePeerDependencies: - supports-color - serve-static@1.16.3: + serve-static@2.2.1: dependencies: encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 - send: 0.19.2 + send: 1.2.1 transitivePeerDependencies: - supports-color - set-cookie-parser@2.7.2: {} - setprototypeof@1.2.0: {} shebang-command@2.0.0: @@ -9507,6 +8947,11 @@ snapshots: es-errors: 1.3.0 object-inspect: 1.13.4 + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-map@1.0.1: dependencies: call-bound: 1.0.4 @@ -9530,6 +8975,14 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} simple-concat@1.0.1: {} @@ -9696,10 +9149,11 @@ snapshots: type-fest@2.19.0: {} - type-is@1.6.18: + type-is@2.1.0: dependencies: - media-typer: 0.3.0 - mime-types: 2.1.35 + content-type: 2.0.0 + media-typer: 1.1.0 + mime-types: 3.0.2 types-ramda@0.31.0: dependencies: @@ -9744,42 +9198,19 @@ snapshots: util-deprecate@1.0.2: {} - utils-merge@1.0.1: {} - valibot@1.4.1(typescript@6.0.3): optionalDependencies: typescript: 6.0.3 vary@1.1.2: {} - vite-node@3.2.4(@types/node@26.0.0)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0): - dependencies: - cac: 6.7.14 - debug: 4.4.3 - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 7.3.5(@types/node@26.0.0)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - vite-node@6.0.0(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0): + vite-node@6.0.0(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0): dependencies: cac: 7.0.0 es-module-lexer: 2.0.0 obug: 2.1.1 pathe: 2.0.3 - vite: 8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) + vite: 8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0) transitivePeerDependencies: - '@types/node' - '@vitejs/devtools' @@ -9794,27 +9225,12 @@ snapshots: - tsx - yaml - vite-plugin-babel@1.7.3(@babel/core@7.29.7)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)): + vite-plugin-babel@1.7.3(@babel/core@7.29.7)(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0)): dependencies: '@babel/core': 7.29.7 - vite: 8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) + vite: 8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0) - vite@7.3.5(@types/node@26.0.0)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.9.0): - dependencies: - esbuild: 0.27.7 - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - postcss: 8.5.15 - rollup: 4.61.1 - tinyglobby: 0.2.17 - optionalDependencies: - '@types/node': 26.0.0 - fsevents: 2.3.3 - jiti: 2.7.0 - lightningcss: 1.32.0 - yaml: 2.9.0 - - vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0): + vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -9823,7 +9239,6 @@ snapshots: tinyglobby: 0.2.17 optionalDependencies: '@types/node': 26.0.0 - esbuild: 0.27.7 fsevents: 2.3.3 jiti: 2.7.0 yaml: 2.9.0 @@ -9832,15 +9247,15 @@ snapshots: dependencies: react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@26.0.0)(@vitest/browser-playwright@4.1.8)(@vitest/ui@4.1.8)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + vitest: 4.1.8(@opentelemetry/api@1.9.1)(@types/node@26.0.0)(@vitest/browser-playwright@4.1.8)(@vitest/ui@4.1.8)(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0)) optionalDependencies: '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) - vitest@4.1.8(@opentelemetry/api@1.9.1)(@types/node@26.0.0)(@vitest/browser-playwright@4.1.8)(@vitest/ui@4.1.8)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)): + vitest@4.1.8(@opentelemetry/api@1.9.1)(@types/node@26.0.0)(@vitest/browser-playwright@4.1.8)(@vitest/ui@4.1.8)(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.8 - '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + '@vitest/mocker': 4.1.8(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.8 '@vitest/runner': 4.1.8 '@vitest/snapshot': 4.1.8 @@ -9857,12 +9272,12 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) + vite: 8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.1 '@types/node': 26.0.0 - '@vitest/browser-playwright': 4.1.8(playwright@1.60.0)(vite@8.0.16(@types/node@26.0.0)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.8) + '@vitest/browser-playwright': 4.1.8(playwright@1.60.0)(vite@8.0.16(@types/node@26.0.0)(jiti@2.7.0)(yaml@2.9.0))(vitest@4.1.8) '@vitest/ui': 4.1.8(vitest@4.1.8) transitivePeerDependencies: - msw diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ad918e196..40cc90d76 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -5,5 +5,5 @@ allowBuilds: core-js: false esbuild: true patchedDependencies: - '@react-router/serve@7.15.0': patches/@react-router__serve@7.15.0.patch + '@react-router/serve@8.1.0': patches/@react-router__serve@8.1.0.patch kysely@0.29.0: patches/kysely@0.29.0.patch diff --git a/react-router.config.ts b/react-router.config.ts index 12c01e089..052881ddc 100644 --- a/react-router.config.ts +++ b/react-router.config.ts @@ -6,16 +6,7 @@ export default { // also lazy loading causes more load on the server // this matches old Remix v2 behavior routeDiscovery: { mode: "initial" }, - future: { - v8_middleware: true, - v8_splitRouteModules: true, - // Disabled: passing the raw request makes relative redirects (e.g. the - // successToast/errorToast `redirect("?__success=...")` pattern) resolve - // against the `.data` URL of single-fetch requests, breaking navigation. - v8_passThroughRequests: false, - v8_trailingSlashAwareDataRequests: true, - v8_viteEnvironmentApi: true, - }, + splitRouteModules: true, buildEnd: async ({ viteConfig, reactRouterConfig, buildManifest }) => { await sentryOnBuildEnd({ viteConfig: viteConfig,