diff --git a/api/model/db.ts b/api/model/db.ts index 5dcfc0a..c4426aa 100644 --- a/api/model/db.ts +++ b/api/model/db.ts @@ -15,9 +15,11 @@ import { ORPHANED_ATTACHMENTS_RETENTION_HOURS, UNUSED_INVITES_RETENTION_DAYS, } from "../config.ts"; +import { runMigrations } from "../sql/migrate.ts"; export const db = new DatabaseSync(DB_PATH); db.exec("PRAGMA foreign_keys = ON;"); +runMigrations(db); // Purge expired/used password reset tokens on startup db.prepare( @@ -236,6 +238,7 @@ export interface CommentRow { created_at: string; updated_at: string | null; deleted: number; + like_count: number; author_username: string; author_avatar_mime: string | null; [key: string]: SQLOutputValue; @@ -255,6 +258,7 @@ export function isCommentRow( "updated_at" in obj && (typeof obj.updated_at === "string" || obj.updated_at === null) && "deleted" in obj && typeof obj.deleted === "number" && + "like_count" in obj && typeof obj.like_count === "number" && "author_username" in obj && typeof obj.author_username === "string" && "author_avatar_mime" in obj && (typeof obj.author_avatar_mime === "string" || @@ -271,6 +275,7 @@ export function commentRowToApi(row: CommentRow): Comment { createdAt: new Date(row.created_at), updatedAt: row.updated_at ? new Date(row.updated_at) : undefined, deleted: Boolean(row.deleted), + likeCount: row.like_count, authorUsername: row.author_username, authorAvatarMime: row.author_avatar_mime ?? undefined, }; diff --git a/api/model/interfaces.ts b/api/model/interfaces.ts index 8115b49..eb95096 100644 --- a/api/model/interfaces.ts +++ b/api/model/interfaces.ts @@ -255,6 +255,7 @@ export interface Comment { createdAt: Date; updatedAt?: Date; deleted: boolean; + likeCount: number; authorUsername: string; authorAvatarMime?: string; } @@ -469,11 +470,23 @@ export interface VoteRemoveMessage { dumpId: string; } +export interface CommentLikeCastMessage { + type: "comment_like_cast"; + commentId: string; +} + +export interface CommentLikeRemoveMessage { + type: "comment_like_remove"; + commentId: string; +} + export type ClientToServerMessage = | PingMessage | PongMessage | VoteCastMessage - | VoteRemoveMessage; + | VoteRemoveMessage + | CommentLikeCastMessage + | CommentLikeRemoveMessage; // ── Server → Client ────────────────────────────────────────────────────────── @@ -488,6 +501,7 @@ export interface WelcomeMessage { type: "welcome"; users: OnlineUser[]; myVotes: string[]; + myCommentLikes: string[]; unreadNotificationCount: number; } @@ -511,6 +525,21 @@ export interface VoteAckMessage { voteCount: number; } +export interface CommentLikesUpdateMessage { + type: "comment_likes_update"; + commentId: string; + likeCount: number; + likerId: string; + action: "cast" | "remove"; +} + +export interface CommentLikeAckMessage { + type: "comment_like_ack"; + commentId: string; + action: "cast" | "remove"; + likeCount: number; +} + export interface DumpCreatedMessage { type: "dump_created"; dump: Dump; @@ -589,6 +618,8 @@ export type ServerToClientMessage = | PresenceUpdateMessage | VotesUpdateMessage | VoteAckMessage + | CommentLikesUpdateMessage + | CommentLikeAckMessage | DumpCreatedMessage | DumpUpdatedMessage | DumpDeletedMessage @@ -624,7 +655,8 @@ export type NotificationType = | "playlist_dump_added" | "dump_upvoted" | "user_mentioned" - | "dump_commented"; + | "dump_commented" + | "comment_liked"; export interface PlaylistFollowedData { followerId: string; @@ -676,6 +708,14 @@ export interface DumpCommentedData { dumpTitle: string; } +export interface CommentLikedData { + likerId: string; + likerUsername: string; + commentId: string; + dumpId: string; + dumpTitle: string; +} + export type NotificationData = | PlaylistFollowedData | UserFollowedData @@ -683,7 +723,8 @@ export type NotificationData = | PlaylistDumpAddedData | DumpUpvotedData | UserMentionedData - | DumpCommentedData; + | DumpCommentedData + | CommentLikedData; export interface Notification { id: string; diff --git a/api/routes/comments.ts b/api/routes/comments.ts index ee07730..87af813 100644 --- a/api/routes/comments.ts +++ b/api/routes/comments.ts @@ -9,12 +9,15 @@ import { } from "../model/interfaces.ts"; import { authMiddleware } from "../middleware/auth.ts"; import { parseOptionalAuth } from "../lib/auth.ts"; +import { parsePagination } from "../lib/pagination.ts"; import { createComment, deleteComment, + getCommentDumpId, getComments, updateComment, } from "../services/comment-service.ts"; +import { getCommentLikers } from "../services/comment-like-service.ts"; import { getDump } from "../services/dump-service.ts"; import { broadcastCommentCreated, @@ -83,6 +86,27 @@ router.patch("/comments/:commentId", authMiddleware, async (ctx) => { ctx.response.body = responseBody; }); +// GET /api/comments/:commentId/likers — optional auth (to access private dump's comment likers) +router.get("/comments/:commentId/likers", async (ctx) => { + const requestingUserId = await parseOptionalAuth(ctx) ?? undefined; + const dumpId = getCommentDumpId(ctx.params.commentId); + getDump(dumpId, requestingUserId); // enforces privacy, 404s if not visible + const { page, limit } = parsePagination(ctx.request.url.searchParams); + const { items, total } = getCommentLikers( + ctx.params.commentId, + page, + limit, + ); + ctx.response.body = { + success: true, + data: { + items: items.map(({ passwordHash: _, email: _e, ...pub }) => pub), + total, + hasMore: page * limit < total, + }, + }; +}); + // DELETE /api/comments/:commentId — auth required router.delete("/comments/:commentId", authMiddleware, (ctx) => { const userId = ctx.state.user.userId; diff --git a/api/routes/dumps.ts b/api/routes/dumps.ts index d3b335d..befb50c 100644 --- a/api/routes/dumps.ts +++ b/api/routes/dumps.ts @@ -23,6 +23,7 @@ import { replaceFileDump, updateDump, } from "../services/dump-service.ts"; +import { getDumpVoters } from "../services/vote-service.ts"; const router = new Router({ prefix: "/api/dumps" }); @@ -84,6 +85,21 @@ router.get("/:dumpId", async (ctx) => { ctx.response.body = responseBody; }); +router.get("/:dumpId/voters", async (ctx) => { + const requestingUserId = await parseOptionalAuth(ctx) ?? undefined; + const dump = getDump(ctx.params.dumpId, requestingUserId); + const { page, limit } = parsePagination(ctx.request.url.searchParams); + const { items, total } = getDumpVoters(dump.id, page, limit); + ctx.response.body = { + success: true, + data: { + items: items.map(({ passwordHash: _, email: _e, ...pub }) => pub), + total, + hasMore: page * limit < total, + }, + }; +}); + router.get("/", async (ctx) => { const requestingUserId = await parseOptionalAuth(ctx) ?? undefined; const { page, limit } = parsePagination(ctx.request.url.searchParams); diff --git a/api/routes/ws.ts b/api/routes/ws.ts index d4877a3..7058391 100644 --- a/api/routes/ws.ts +++ b/api/routes/ws.ts @@ -2,6 +2,7 @@ import { Router } from "@oak/oak"; import { ALLOWED_ORIGINS } from "../config.ts"; import { verifyJWT } from "../lib/jwt.ts"; import { + broadcastCommentLikeUpdate, broadcastPresence, broadcastVoteUpdate, getOnlineUsers, @@ -15,6 +16,11 @@ import { getUserVotes, removeVote, } from "../services/vote-service.ts"; +import { + castCommentLike, + getUserCommentLikes, + removeCommentLike, +} from "../services/comment-like-service.ts"; import { getUnreadCount } from "../services/notification-service.ts"; import { getUserById } from "../services/user-service.ts"; import { @@ -63,6 +69,9 @@ router.get("/ws", async (ctx) => { try { const myVotes = authPayload ? getUserVotes(authPayload.userId) : []; + const myCommentLikes = authPayload + ? getUserCommentLikes(authPayload.userId) + : []; const unreadNotificationCount = authPayload ? getUnreadCount(authPayload.userId) : 0; @@ -70,6 +79,7 @@ router.get("/ws", async (ctx) => { type: "welcome", users: getOnlineUsers(), myVotes, + myCommentLikes, unreadNotificationCount, })); } catch (err) { @@ -98,6 +108,12 @@ router.get("/ws", async (ctx) => { case "vote_remove": handleVote(client, msg.dumpId, "remove"); break; + case "comment_like_cast": + handleCommentLike(client, msg.commentId, "cast"); + break; + case "comment_like_remove": + handleCommentLike(client, msg.commentId, "remove"); + break; } }); @@ -141,4 +157,37 @@ function handleVote( } } +function handleCommentLike( + client: WsClient, + commentId: string, + action: "cast" | "remove", +): void { + const { socket } = client; + + if (!client.userId) { + socket.send( + JSON.stringify({ type: "error", message: "Authentication required" }), + ); + return; + } + + try { + const newCount = action === "cast" + ? castCommentLike(commentId, client.userId) + : removeCommentLike(commentId, client.userId); + + socket.send(JSON.stringify({ + type: "comment_like_ack", + commentId, + action, + likeCount: newCount, + })); + + broadcastCommentLikeUpdate(commentId, newCount, client.userId, action); + } catch (err) { + const message = err instanceof APIException ? err.message : "Like failed"; + socket.send(JSON.stringify({ type: "error", message })); + } +} + export default router; diff --git a/api/services/comment-like-service.ts b/api/services/comment-like-service.ts new file mode 100644 index 0000000..63d9845 --- /dev/null +++ b/api/services/comment-like-service.ts @@ -0,0 +1,107 @@ +import { + APIErrorCode, + APIException, + type User, +} from "../model/interfaces.ts"; +import { db, isUserRow, userRowToApi } from "../model/db.ts"; +import { notifyCommentOwnerLike } from "./notification-service.ts"; + +export function castCommentLike(commentId: string, userId: string): number { + let likeCount: number; + try { + db.exec("BEGIN;"); + db.prepare( + `INSERT INTO comment_likes (comment_id, user_id, created_at) VALUES (?, ?, ?);`, + ).run(commentId, userId, new Date().toISOString()); + db.prepare( + `UPDATE comments SET like_count = like_count + 1 WHERE id = ?;`, + ).run(commentId); + const row = db.prepare( + `SELECT like_count FROM comments WHERE id = ?;`, + ).get(commentId) as { like_count: number } | undefined; + db.exec("COMMIT;"); + likeCount = row?.like_count ?? 0; + } catch (err) { + try { + db.exec("ROLLBACK;"); + } catch { /* ignore if no active transaction */ } + if (err instanceof Error && err.message.includes("UNIQUE constraint")) { + throw new APIException( + APIErrorCode.VALIDATION_ERROR, + 409, + "Already liked", + ); + } + throw err; + } + // Notification is best-effort — must not prevent like_ack from being sent + try { + notifyCommentOwnerLike(userId, commentId); + } catch { /* ignore */ } + return likeCount; +} + +export function removeCommentLike(commentId: string, userId: string): number { + try { + db.exec("BEGIN;"); + const result = db.prepare( + `DELETE FROM comment_likes WHERE comment_id = ? AND user_id = ?;`, + ).run(commentId, userId); + if (result.changes === 0) { + db.exec("ROLLBACK;"); + throw new APIException(APIErrorCode.NOT_FOUND, 404, "Like not found"); + } + db.prepare( + `UPDATE comments SET like_count = like_count - 1 WHERE id = ?;`, + ).run(commentId); + const row = db.prepare( + `SELECT like_count FROM comments WHERE id = ?;`, + ).get(commentId) as { like_count: number } | undefined; + db.exec("COMMIT;"); + return row?.like_count ?? 0; + } catch (err) { + if (err instanceof APIException) throw err; + db.exec("ROLLBACK;"); + throw err; + } +} + +export function getUserCommentLikes(userId: string): string[] { + const rows = db.prepare( + `SELECT comment_id FROM comment_likes WHERE user_id = ?;`, + ).all(userId) as { comment_id: string }[]; + return rows.map((r) => r.comment_id); +} + +export function getCommentLikers( + commentId: string, + page: number, + limit: number, +): { items: User[]; total: number } { + const offset = (page - 1) * limit; + + const totalRow = db.prepare( + `SELECT COUNT(*) as count FROM comment_likes WHERE comment_id = ?;`, + ).get(commentId) as { count: number } | undefined; + + const rawRows = db.prepare( + `SELECT u.id, u.username, u.password_hash, u.is_admin, u.created_at, u.updated_at, + u.avatar_mime, u.description, u.invited_by, u.email, + i.username as invited_by_username + FROM users u + LEFT JOIN users i ON i.id = u.invited_by + INNER JOIN comment_likes cl ON cl.comment_id = ? AND cl.user_id = u.id + ORDER BY cl.created_at DESC + LIMIT ? OFFSET ?;`, + ).all(commentId, limit, offset); + + if (!rawRows.every(isUserRow)) { + throw new APIException( + APIErrorCode.SERVER_ERROR, + 500, + "Malformed user data", + ); + } + + return { items: rawRows.map(userRowToApi), total: totalRow?.count ?? 0 }; +} diff --git a/api/services/comment-service.ts b/api/services/comment-service.ts index fe4cc7f..998f1bd 100644 --- a/api/services/comment-service.ts +++ b/api/services/comment-service.ts @@ -12,7 +12,7 @@ import { import { linkAttachments } from "./attachment-service.ts"; const SELECT_COLS = - `c.id, c.dump_id, c.user_id, c.parent_id, c.body, c.created_at, c.updated_at, c.deleted, + `c.id, c.dump_id, c.user_id, c.parent_id, c.body, c.created_at, c.updated_at, c.deleted, c.like_count, u.username as author_username, u.avatar_mime as author_avatar_mime`; function fetchComment(commentId: string): Comment { @@ -32,6 +32,16 @@ function fetchComment(commentId: string): Comment { return commentRowToApi(row); } +export function getCommentDumpId(commentId: string): string { + const row = db.prepare(`SELECT dump_id FROM comments WHERE id = ?;`).get( + commentId, + ) as { dump_id: string } | undefined; + if (!row) { + throw new APIException(APIErrorCode.NOT_FOUND, 404, "Comment not found"); + } + return row.dump_id; +} + export function getComments(dumpId: string): Comment[] { const rows = db.prepare( `SELECT ${SELECT_COLS} FROM comments c JOIN users u ON c.user_id = u.id diff --git a/api/services/notification-service.ts b/api/services/notification-service.ts index 17597b9..cb3ae10 100644 --- a/api/services/notification-service.ts +++ b/api/services/notification-service.ts @@ -306,6 +306,38 @@ export function notifyDumpOwnerNewComment( ); } +export function notifyCommentOwnerLike( + likerId: string, + commentId: string, +): void { + const likerRow = db.prepare( + `SELECT username FROM users WHERE id = ?;`, + ).get(likerId) as { username: string } | undefined; + + const commentRow = db.prepare( + `SELECT c.user_id, c.dump_id, d.title as dump_title + FROM comments c JOIN dumps d ON c.dump_id = d.id WHERE c.id = ?;`, + ).get(commentId) as + | { user_id: string; dump_id: string; dump_title: string } + | undefined; + + if (!likerRow || !commentRow) return; + if (likerId === commentRow.user_id) return; // no self-notification + + createNotification( + commentRow.user_id, + "comment_liked", + { + likerId, + likerUsername: likerRow.username, + commentId, + dumpId: commentRow.dump_id, + dumpTitle: commentRow.dump_title, + }, + `like:${commentId}:${likerId}`, + ); +} + export function notifyPlaylistFollowersNewDump( playlistId: string, playlistTitle: string, diff --git a/api/services/vote-service.ts b/api/services/vote-service.ts index a409a4e..7f53327 100644 --- a/api/services/vote-service.ts +++ b/api/services/vote-service.ts @@ -1,5 +1,9 @@ -import { APIErrorCode, APIException } from "../model/interfaces.ts"; -import { db } from "../model/db.ts"; +import { + APIErrorCode, + APIException, + type User, +} from "../model/interfaces.ts"; +import { db, isUserRow, userRowToApi } from "../model/db.ts"; import { notifyDumpOwnerUpvote } from "./notification-service.ts"; export function castVote(dumpId: string, userId: string): number { @@ -68,3 +72,36 @@ export function getUserVotes(userId: string): string[] { ).all(userId) as { dump_id: string }[]; return rows.map((r) => r.dump_id); } + +export function getDumpVoters( + dumpId: string, + page: number, + limit: number, +): { items: User[]; total: number } { + const offset = (page - 1) * limit; + + const totalRow = db.prepare( + `SELECT COUNT(*) as count FROM votes WHERE dump_id = ?;`, + ).get(dumpId) as { count: number } | undefined; + + const rawRows = db.prepare( + `SELECT u.id, u.username, u.password_hash, u.is_admin, u.created_at, u.updated_at, + u.avatar_mime, u.description, u.invited_by, u.email, + i.username as invited_by_username + FROM users u + LEFT JOIN users i ON i.id = u.invited_by + INNER JOIN votes v ON v.dump_id = ? AND v.user_id = u.id + ORDER BY v.created_at DESC + LIMIT ? OFFSET ?;`, + ).all(dumpId, limit, offset); + + if (!rawRows.every(isUserRow)) { + throw new APIException( + APIErrorCode.SERVER_ERROR, + 500, + "Malformed user data", + ); + } + + return { items: rawRows.map(userRowToApi), total: totalRow?.count ?? 0 }; +} diff --git a/api/services/ws-service.ts b/api/services/ws-service.ts index 48fda55..00d6c66 100644 --- a/api/services/ws-service.ts +++ b/api/services/ws-service.ts @@ -117,6 +117,23 @@ export function broadcastVoteUpdate( } } +export function broadcastCommentLikeUpdate( + commentId: string, + likeCount: number, + likerId: string, + action: "cast" | "remove", +): void { + for (const client of clients) { + send(client.socket, { + type: "comment_likes_update", + commentId, + likeCount, + likerId, + action, + }); + } +} + function sendToPlaylistAudience( playlist: Pick, data: ServerToClientMessage, diff --git a/api/sql/migrate.ts b/api/sql/migrate.ts new file mode 100644 index 0000000..62203a3 --- /dev/null +++ b/api/sql/migrate.ts @@ -0,0 +1,43 @@ +import type { DatabaseSync } from "node:sqlite"; +import { up as up0001CommentLikes } from "./migrations/0001_comment_likes.ts"; + +interface Migration { + name: string; + up: (db: DatabaseSync) => void; +} + +// Append-only — never edit or reorder a migration once it has shipped. +// Each `up` must be idempotent: it also runs against fresh databases created +// straight from schema.sql, where the change it makes already exists. +const MIGRATIONS: Migration[] = [ + { name: "0001_comment_likes", up: up0001CommentLikes }, +]; + +export function runMigrations(db: DatabaseSync): void { + db.exec(`CREATE TABLE IF NOT EXISTS schema_migrations ( + name TEXT PRIMARY KEY, + applied_at TEXT NOT NULL + );`); + + const applied = new Set( + (db.prepare(`SELECT name FROM schema_migrations;`).all() as { + name: string; + }[]).map((r) => r.name), + ); + + for (const { name, up } of MIGRATIONS) { + if (applied.has(name)) continue; + db.exec("BEGIN;"); + try { + up(db); + db.prepare( + `INSERT INTO schema_migrations (name, applied_at) VALUES (?, ?);`, + ).run(name, new Date().toISOString()); + db.exec("COMMIT;"); + console.log(`[migrate] applied ${name}`); + } catch (err) { + db.exec("ROLLBACK;"); + throw err; + } + } +} diff --git a/api/sql/migrations/0001_comment_likes.ts b/api/sql/migrations/0001_comment_likes.ts new file mode 100644 index 0000000..714e44b --- /dev/null +++ b/api/sql/migrations/0001_comment_likes.ts @@ -0,0 +1,27 @@ +import type { DatabaseSync } from "node:sqlite"; + +// Idempotent: safe to run against a fresh db (already created from schema.sql +// with these columns/tables) or an existing one that predates them. +export function up(db: DatabaseSync): void { + const commentCols = db.prepare(`PRAGMA table_info(comments);`).all() as { + name: string; + }[]; + if (!commentCols.some((c) => c.name === "like_count")) { + db.exec( + `ALTER TABLE comments ADD COLUMN like_count INTEGER NOT NULL DEFAULT 0;`, + ); + } + + db.exec(`CREATE TABLE IF NOT EXISTS comment_likes ( + comment_id TEXT NOT NULL, + user_id TEXT NOT NULL, + created_at TEXT NOT NULL, + PRIMARY KEY (comment_id, user_id), + FOREIGN KEY (comment_id) REFERENCES comments(id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE + );`); + + db.exec( + `CREATE INDEX IF NOT EXISTS idx_comment_likes_user ON comment_likes(user_id);`, + ); +} diff --git a/api/sql/schema.sql b/api/sql/schema.sql index dc16fff..51e41cf 100644 --- a/api/sql/schema.sql +++ b/api/sql/schema.sql @@ -71,17 +71,28 @@ CREATE TABLE comments ( created_at TEXT NOT NULL, updated_at TEXT, deleted INTEGER NOT NULL DEFAULT 0, + like_count INTEGER NOT NULL DEFAULT 0, FOREIGN KEY (dump_id) REFERENCES dumps(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (parent_id) REFERENCES comments(id) ON DELETE CASCADE ); +CREATE TABLE comment_likes ( + comment_id TEXT NOT NULL, + user_id TEXT NOT NULL, + created_at TEXT NOT NULL, + PRIMARY KEY (comment_id, user_id), + FOREIGN KEY (comment_id) REFERENCES comments(id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +); + CREATE INDEX idx_dumps_user ON dumps(user_id); CREATE INDEX idx_votes_user ON votes(user_id); CREATE INDEX idx_playlists_user ON playlists(user_id); CREATE INDEX idx_playlist_dumps_order ON playlist_dumps(playlist_id, position); CREATE INDEX idx_playlist_dumps_dump ON playlist_dumps(dump_id); CREATE INDEX idx_comments_dump ON comments(dump_id, created_at); +CREATE INDEX idx_comment_likes_user ON comment_likes(user_id); CREATE TABLE follows ( id TEXT PRIMARY KEY, diff --git a/deno.json b/deno.json index 64b0569..d6c943b 100644 --- a/deno.json +++ b/deno.json @@ -26,9 +26,10 @@ "imports": { "@db/sqlite": "jsr:@db/sqlite@^0.13.0", "@oak/oak": "jsr:@oak/oak@^17.2.0", - "@panva/jose": "jsr:@panva/jose@^6.2.1", + "@panva/jose": "jsr:@panva/jose@^6.2.3", "@tajpouria/cors": "jsr:@tajpouria/cors@^1.2.1", - "nodemailer": "npm:nodemailer@^8.0.4", - "marked": "npm:marked@^15.0.0" - } + "nodemailer": "npm:nodemailer@^9.0.1", + "marked": "npm:marked@^18.0.5" + }, + "allowScripts": ["npm:@swc/core@1.15.41", "npm:esbuild@0.25.12"] } diff --git a/deno.lock b/deno.lock index c28570b..f08913a 100644 --- a/deno.lock +++ b/deno.lock @@ -5,7 +5,7 @@ "jsr:@denosaurs/plug@1": "1.1.0", "jsr:@oak/commons@1": "1.0.1", "jsr:@oak/oak@^17.2.0": "17.2.0", - "jsr:@panva/jose@^6.2.1": "6.2.2", + "jsr:@panva/jose@^6.2.3": "6.2.3", "jsr:@std/assert@1": "1.0.19", "jsr:@std/bytes@1": "1.0.6", "jsr:@std/crypto@1": "1.0.5", @@ -20,38 +20,37 @@ "jsr:@std/path@1.0": "1.0.9", "jsr:@std/path@^1.1.4": "1.1.4", "jsr:@tajpouria/cors@^1.2.1": "1.2.1", - "npm:@eslint/js@^9.39.4": "9.39.4", - "npm:@lingui/cli@*": "5.9.4_typescript@5.9.3", - "npm:@lingui/cli@6.0.0-next.3": "6.0.0-next.3", - "npm:@lingui/conf@6.0.0-next.3": "6.0.0-next.3", - "npm:@lingui/core@6.0.0-next.3": "6.0.0-next.3", - "npm:@lingui/format-po@6.0.0-next.3": "6.0.0-next.3", - "npm:@lingui/react@6.0.0-next.3": "6.0.0-next.3_react@19.2.4", - "npm:@lingui/swc-plugin@6.0.0-next.2": "6.0.0-next.2_@lingui+core@6.0.0-next.3", - "npm:@lingui/vite-plugin@6.0.0-next.3": "6.0.0-next.3_vite@8.0.1__@types+node@24.12.0__jiti@2.6.1_jiti@2.6.1", - "npm:@types/node@^24.12.0": "24.12.0", - "npm:@types/react-dom@^19.2.3": "19.2.3_@types+react@19.2.14", - "npm:@types/react@^19.2.14": "19.2.14", - "npm:@vitejs/plugin-react-swc@^4.3.0": "4.3.0_vite@8.0.1__@types+node@24.12.0__jiti@2.6.1_jiti@2.6.1", - "npm:eslint-plugin-react-hooks@^7.0.1": "7.0.1_eslint@9.39.4__jiti@2.6.1_jiti@2.6.1", - "npm:eslint-plugin-react-refresh@~0.5.2": "0.5.2_eslint@9.39.4__jiti@2.6.1_jiti@2.6.1", - "npm:eslint@^9.39.4": "9.39.4_jiti@2.6.1", - "npm:frimousse@0.3": "0.3.0_react@19.2.4_typescript@5.9.3", - "npm:globals@^17.4.0": "17.4.0", - "npm:jiti@^2.6.1": "2.6.1", - "npm:marked@15": "15.0.12", - "npm:nodemailer@*": "8.0.4", - "npm:nodemailer@^8.0.4": "8.0.4", + "npm:@eslint/js@^10.0.1": "10.0.1_eslint@10.5.0__jiti@2.7.0_jiti@2.7.0", + "npm:@lingui/cli@*": "6.4.0", + "npm:@lingui/cli@6.4.0": "6.4.0", + "npm:@lingui/conf@6.4.0": "6.4.0", + "npm:@lingui/core@6.4.0": "6.4.0", + "npm:@lingui/format-po@6.4.0": "6.4.0", + "npm:@lingui/react@6.4.0": "6.4.0_react@19.2.7", + "npm:@lingui/swc-plugin@6.4.0": "6.4.0_@lingui+core@6.4.0", + "npm:@lingui/vite-plugin@6.4.0": "6.4.0_@babel+core@7.29.7_vite@8.0.16__@types+node@26.0.0__jiti@2.7.0_@types+node@26.0.0_jiti@2.7.0", + "npm:@types/node@26": "26.0.0", + "npm:@types/react-dom@^19.2.3": "19.2.3_@types+react@19.2.17", + "npm:@types/react@^19.2.17": "19.2.17", + "npm:@vitejs/plugin-react-swc@^4.3.1": "4.3.1_vite@8.0.16__@types+node@26.0.0__jiti@2.7.0_@types+node@26.0.0_jiti@2.7.0", + "npm:eslint-plugin-react-hooks@^7.1.1": "7.1.1_eslint@10.5.0__jiti@2.7.0_jiti@2.7.0", + "npm:eslint-plugin-react-refresh@~0.5.3": "0.5.3_eslint@10.5.0__jiti@2.7.0_jiti@2.7.0", + "npm:eslint@^10.5.0": "10.5.0_jiti@2.7.0", + "npm:frimousse@0.3": "0.3.0_react@19.2.7_typescript@6.0.3", + "npm:globals@^17.6.0": "17.6.0", + "npm:jiti@^2.7.0": "2.7.0", + "npm:marked@^18.0.5": "18.0.5", + "npm:nodemailer@^9.0.1": "9.0.1", "npm:path-to-regexp@^6.3.0": "6.3.0", - "npm:react-dom@^19.2.4": "19.2.4_react@19.2.4", - "npm:react-markdown@^10.1.0": "10.1.0_@types+react@19.2.14_react@19.2.4", - "npm:react-router@^7.13.1": "7.13.1_react@19.2.4_react-dom@19.2.4__react@19.2.4", - "npm:react@^19.2.4": "19.2.4", + "npm:react-dom@^19.2.7": "19.2.7_react@19.2.7", + "npm:react-markdown@^10.1.0": "10.1.0_@types+react@19.2.17_react@19.2.7", + "npm:react-router@^8.0.1": "8.0.1_react@19.2.7_react-dom@19.2.7__react@19.2.7", + "npm:react@^19.2.7": "19.2.7", "npm:remark-gfm@^4.0.1": "4.0.1", - "npm:typescript-eslint@^8.56.1": "8.57.1_eslint@9.39.4__jiti@2.6.1_typescript@5.9.3_jiti@2.6.1", - "npm:typescript@~5.9.3": "5.9.3", - "npm:vite@*": "8.0.1_@types+node@24.12.0_jiti@2.6.1", - "npm:vite@8": "8.0.1_@types+node@24.12.0_jiti@2.6.1" + "npm:typescript-eslint@^8.61.1": "8.61.1_eslint@10.5.0__jiti@2.7.0_typescript@6.0.3_jiti@2.7.0", + "npm:typescript@~6.0.3": "6.0.3", + "npm:vite@*": "8.0.16_@types+node@26.0.0_jiti@2.7.0", + "npm:vite@^8.0.16": "8.0.16_@types+node@26.0.0_jiti@2.7.0" }, "jsr": { "@db/sqlite@0.13.0": { @@ -93,8 +92,8 @@ "npm:path-to-regexp" ] }, - "@panva/jose@6.2.2": { - "integrity": "bfe1178a9d2f53effa5ee6c1786b6534a39690b03969472b3ad690600d8898ec" + "@panva/jose@6.2.3": { + "integrity": "57cc772f01b2ac60f847a7e6ec815059762768186266ed833fb59953e9611312" }, "@std/assert@1.0.19": { "integrity": "eaada96ee120cb980bc47e040f82814d786fe8162ecc53c91d8df60b8755991e" @@ -144,19 +143,19 @@ } }, "npm": { - "@babel/code-frame@7.29.0": { - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "@babel/code-frame@7.29.7": { + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dependencies": [ "@babel/helper-validator-identifier", "js-tokens", "picocolors" ] }, - "@babel/compat-data@7.29.0": { - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==" + "@babel/compat-data@7.29.7": { + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==" }, - "@babel/core@7.29.0": { - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "@babel/core@7.29.7": { + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dependencies": [ "@babel/code-frame", "@babel/generator", @@ -175,8 +174,8 @@ "semver@6.3.1" ] }, - "@babel/generator@7.29.1": { - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "@babel/generator@7.29.7": { + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dependencies": [ "@babel/parser", "@babel/types", @@ -185,28 +184,28 @@ "jsesc" ] }, - "@babel/helper-compilation-targets@7.28.6": { - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "@babel/helper-compilation-targets@7.29.7": { + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dependencies": [ "@babel/compat-data", "@babel/helper-validator-option", "browserslist", - "lru-cache@5.1.1", + "lru-cache", "semver@6.3.1" ] }, - "@babel/helper-globals@7.28.0": { - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==" + "@babel/helper-globals@7.29.7": { + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==" }, - "@babel/helper-module-imports@7.28.6": { - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "@babel/helper-module-imports@7.29.7": { + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dependencies": [ "@babel/traverse", "@babel/types" ] }, - "@babel/helper-module-transforms@7.28.6_@babel+core@7.29.0": { - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "@babel/helper-module-transforms@7.29.7_@babel+core@7.29.7": { + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dependencies": [ "@babel/core", "@babel/helper-module-imports", @@ -214,42 +213,39 @@ "@babel/traverse" ] }, - "@babel/helper-string-parser@7.27.1": { - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==" + "@babel/helper-string-parser@7.29.7": { + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==" }, - "@babel/helper-validator-identifier@7.28.5": { - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==" + "@babel/helper-validator-identifier@7.29.7": { + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==" }, - "@babel/helper-validator-option@7.27.1": { - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==" + "@babel/helper-validator-option@7.29.7": { + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==" }, - "@babel/helpers@7.29.2": { - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "@babel/helpers@7.29.7": { + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dependencies": [ "@babel/template", "@babel/types" ] }, - "@babel/parser@7.29.2": { - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "@babel/parser@7.29.7": { + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dependencies": [ "@babel/types" ], "bin": true }, - "@babel/runtime@7.29.2": { - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==" - }, - "@babel/template@7.28.6": { - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "@babel/template@7.29.7": { + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dependencies": [ "@babel/code-frame", "@babel/parser", "@babel/types" ] }, - "@babel/traverse@7.29.0": { - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "@babel/traverse@7.29.7": { + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dependencies": [ "@babel/code-frame", "@babel/generator", @@ -260,8 +256,8 @@ "debug" ] }, - "@babel/types@7.29.0": { - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "@babel/types@7.29.7": { + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dependencies": [ "@babel/helper-string-parser", "@babel/helper-validator-identifier" @@ -270,21 +266,21 @@ "@colors/colors@1.5.0": { "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==" }, - "@emnapi/core@1.9.1": { - "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==", + "@emnapi/core@1.10.0": { + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", "dependencies": [ "@emnapi/wasi-threads", "tslib" ] }, - "@emnapi/runtime@1.9.1": { - "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "@emnapi/runtime@1.10.0": { + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", "dependencies": [ "tslib" ] }, - "@emnapi/wasi-threads@1.2.0": { - "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "@emnapi/wasi-threads@1.2.1": { + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "dependencies": [ "tslib" ] @@ -419,7 +415,7 @@ "os": ["win32"], "cpu": ["x64"] }, - "@eslint-community/eslint-utils@4.9.1_eslint@9.39.4__jiti@2.6.1_jiti@2.6.1": { + "@eslint-community/eslint-utils@4.9.1_eslint@10.5.0__jiti@2.7.0_jiti@2.7.0": { "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dependencies": [ "eslint", @@ -429,72 +425,68 @@ "@eslint-community/regexpp@4.12.2": { "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==" }, - "@eslint/config-array@0.21.2": { - "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "@eslint/config-array@0.23.5": { + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dependencies": [ "@eslint/object-schema", "debug", - "minimatch@3.1.5" + "minimatch" ] }, - "@eslint/config-helpers@0.4.2": { - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "@eslint/config-helpers@0.6.0": { + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", "dependencies": [ "@eslint/core" ] }, - "@eslint/core@0.17.0": { - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "@eslint/core@1.2.1": { + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", "dependencies": [ "@types/json-schema" ] }, - "@eslint/eslintrc@3.3.5": { - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "@eslint/js@10.0.1_eslint@10.5.0__jiti@2.7.0_jiti@2.7.0": { + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", "dependencies": [ - "ajv", - "debug", - "espree", - "globals@14.0.0", - "ignore@5.3.2", - "import-fresh", - "js-yaml", - "minimatch@3.1.5", - "strip-json-comments" + "eslint" + ], + "optionalPeers": [ + "eslint" ] }, - "@eslint/js@9.39.4": { - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==" + "@eslint/object-schema@3.0.5": { + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==" }, - "@eslint/object-schema@2.1.7": { - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==" - }, - "@eslint/plugin-kit@0.4.1": { - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "@eslint/plugin-kit@0.7.2": { + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", "dependencies": [ "@eslint/core", "levn" ] }, - "@humanfs/core@0.19.1": { - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==" + "@humanfs/core@0.19.2": { + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dependencies": [ + "@humanfs/types" + ] }, - "@humanfs/node@0.16.7": { - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "@humanfs/node@0.16.8": { + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dependencies": [ "@humanfs/core", + "@humanfs/types", "@humanwhocodes/retry" ] }, + "@humanfs/types@0.15.0": { + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==" + }, "@humanwhocodes/module-importer@1.0.1": { "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==" }, "@humanwhocodes/retry@0.4.3": { "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==" }, - "@isaacs/cliui@9.0.0": { - "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==" - }, "@jest/schemas@29.6.3": { "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", "dependencies": [ @@ -539,79 +531,35 @@ "@jridgewell/sourcemap-codec" ] }, - "@lingui/babel-plugin-extract-messages@5.9.4": { - "integrity": "sha512-sFH5lufIBCOLwjM2hyByMIi7gaGjAPhU7md8XMQYgcEjUVtzjBQvZ9APGDdDQ5BB8xRDyqF2kvaJpJvWZu19zA==" - }, - "@lingui/babel-plugin-extract-messages@6.0.0-next.3": { - "integrity": "sha512-6lr2C5NjGSdnzY5qsWlfteBN4CtN4OlfMB4JceS0+mfatVTGd+koaFAE4rLB6xGO79EOoI3c2PqYHGiED8Vdtw==" - }, - "@lingui/babel-plugin-lingui-macro@5.9.4_typescript@5.9.3": { - "integrity": "sha512-Gj+H48MQWY6rV40TBVG7U91/KETznbXOJpJsf8U4merBRPZgOMCy6VuWZGy1i+YJZJF/LiberlsCCEiiPbBRqg==", + "@lingui/babel-plugin-extract-messages@6.4.0": { + "integrity": "sha512-b9NatQFU1h0muPCC0QGdSVKE/OEdR4w9FQrKAOFYwH0eF7pD2ArafqyqG6xsw2E+7w4PlZQjzOhihQMxI8a6sA==", "dependencies": [ - "@babel/core", - "@babel/runtime", - "@babel/types", - "@lingui/conf@5.9.4_typescript@5.9.3", - "@lingui/core@5.9.4_@lingui+babel-plugin-lingui-macro@5.9.4__typescript@5.9.3", - "@lingui/message-utils@5.9.4" + "@lingui/conf" ] }, - "@lingui/babel-plugin-lingui-macro@6.0.0-next.3": { - "integrity": "sha512-vGXSKF4HHuCQCIIk0hc5sgHD+u7mhAZmksMTmT52ghks2lrLvD+DFUwnRCxMVaJoEilQu3TMXIVS4wwzf3hB1g==", + "@lingui/babel-plugin-lingui-macro@6.4.0": { + "integrity": "sha512-V15kARtjzWgLga/6LOTMMki5pv3F42m9BX8jdI1mBg4yCo1cS0B3szfoBqoveFs7x+nh0iuEPAKcM9lVdSYadw==", "dependencies": [ "@babel/core", "@babel/types", - "@lingui/conf@6.0.0-next.3", - "@lingui/message-utils@6.0.0-next.3" + "@lingui/conf", + "@lingui/message-utils" ] }, - "@lingui/cli@5.9.4_typescript@5.9.3": { - "integrity": "sha512-0QAsZCWu6PZhxYmeQfoa6cJbNRRsTkeNQ1jTow/GzBYpFlO9iXw8dCG5cBh5pHHjzjoX3koxiKyUTFyLBmKNiQ==", - "dependencies": [ - "@babel/core", - "@babel/generator", - "@babel/parser", - "@babel/runtime", - "@babel/types", - "@lingui/babel-plugin-extract-messages@5.9.4", - "@lingui/babel-plugin-lingui-macro@5.9.4_typescript@5.9.3", - "@lingui/conf@5.9.4_typescript@5.9.3", - "@lingui/core@5.9.4_@lingui+babel-plugin-lingui-macro@5.9.4__typescript@5.9.3", - "@lingui/format-po@5.9.4_typescript@5.9.3", - "@lingui/message-utils@5.9.4", - "chokidar@3.5.1", - "cli-table", - "commander@10.0.1", - "convert-source-map", - "date-fns", - "esbuild", - "glob", - "micromatch", - "ms", - "normalize-path", - "ora@5.4.1", - "picocolors", - "pofile", - "pseudolocale", - "source-map", - "threads" - ], - "bin": true - }, - "@lingui/cli@6.0.0-next.3": { - "integrity": "sha512-HbVS5nkqR3/GPZyDAp3d2Km/YdQE9YIn5iKAy/WDOMpVMyR34Mi+O3uFyFoMugnB17qVqjmoAUVVxFlTc6CIKw==", + "@lingui/cli@6.4.0": { + "integrity": "sha512-OqtEPHFmgQlyroQMmpnda6QRnfAqlAYnRVZpZSX3rZpwwaHI1pD7T1EQoK8n7kin9TGhitc1J33wFom7o/+yyg==", "dependencies": [ "@babel/core", "@babel/generator", "@babel/parser", "@babel/types", - "@lingui/babel-plugin-extract-messages@6.0.0-next.3", - "@lingui/babel-plugin-lingui-macro@6.0.0-next.3", - "@lingui/conf@6.0.0-next.3", - "@lingui/core@6.0.0-next.3", - "@lingui/format-po@6.0.0-next.3", - "@lingui/message-utils@6.0.0-next.3", - "chokidar@5.0.0", + "@lingui/babel-plugin-extract-messages", + "@lingui/babel-plugin-lingui-macro", + "@lingui/conf", + "@lingui/core", + "@lingui/format-po", + "@lingui/message-utils", + "chokidar", "cli-table3", "commander@14.0.3", "esbuild", @@ -619,25 +567,15 @@ "micromatch", "ms", "normalize-path", - "ora@9.3.0", + "ora", "pseudolocale", "source-map", "tinypool" ], "bin": true }, - "@lingui/conf@5.9.4_typescript@5.9.3": { - "integrity": "sha512-crF3AQgYXg52Caz4ffJKSTXWUU/4iOGOBRnSeOkw8lsOtOYlPTaWxeSGyDTEwaGCFl6P/1aew+pvHOSCxOAyrg==", - "dependencies": [ - "@babel/runtime", - "cosmiconfig", - "jest-validate", - "jiti", - "picocolors" - ] - }, - "@lingui/conf@6.0.0-next.3": { - "integrity": "sha512-GZjVN4sP+WOp9+SRdjvYPn7kOXe2jBUNNETYFxN7hb7pEXS1TmehgruNjDr/td4TRDKBxXLI+OaJMlZWaDQE8g==", + "@lingui/conf@6.4.0": { + "integrity": "sha512-C+THkLbda72//6dL7QAVyaxIxOnag8mGWKqrMsyIUHgZpStE05KiF8HDwTCMNiaeQmQPym/D8fVm7m8jCau3EA==", "dependencies": [ "jest-validate", "jiti", @@ -645,76 +583,54 @@ "normalize-path" ] }, - "@lingui/core@5.9.4_@lingui+babel-plugin-lingui-macro@5.9.4__typescript@5.9.3": { - "integrity": "sha512-MsYYc8ue/w1C8bgAbC3h4cNik64bqZ6xGxMjsVdoGQBUe+b/ij+rOEiuJXbwvlo4GXBsvsan7EzeH7sx11IsYQ==", + "@lingui/core@6.4.0": { + "integrity": "sha512-DVbgp9tn07t3iByH6snsn1WaM8PxjDacOqf45oLVXyIti98PmXadO2UkO+NgCybHSqm3+7GGV4zTr4777Cc9nA==", "dependencies": [ - "@babel/runtime", - "@lingui/babel-plugin-lingui-macro@5.9.4_typescript@5.9.3", - "@lingui/message-utils@5.9.4" - ], - "optionalPeers": [ - "@lingui/babel-plugin-lingui-macro@5.9.4_typescript@5.9.3" + "@lingui/babel-plugin-lingui-macro", + "@lingui/message-utils" ] }, - "@lingui/core@6.0.0-next.3": { - "integrity": "sha512-ugIBl1cwxlHg7tY98qT9h1M5v6CurDBre/Vjzfv3F2RXC4J7iB4LGJ2QSbVIYAQVIK/H0pgrv1Lh1L8IwgEPZQ==", + "@lingui/format-po@6.4.0": { + "integrity": "sha512-tu5aNalW9u+xjmcAlNEjfw4ahzMHFeC4nKz+zYHcv44Drpy9/bWnrr5j7Pi4rc9u4PILtXikwAsLh28u284WiA==", "dependencies": [ - "@lingui/babel-plugin-lingui-macro@6.0.0-next.3", - "@lingui/message-utils@6.0.0-next.3" + "@lingui/conf", + "@lingui/message-utils", + "pofile-ts" ] }, - "@lingui/format-po@5.9.4_typescript@5.9.3": { - "integrity": "sha512-B+e8YF6S5EOUPF6i3gaSX69pPs/QkP6MIE97vYA48W9Lty7KFOHuYBk/YzCY9CSQaF7gW3GAI5ZsXX2+ZLVyZw==", - "dependencies": [ - "@lingui/conf@5.9.4_typescript@5.9.3", - "@lingui/message-utils@5.9.4", - "date-fns", - "pofile" - ] - }, - "@lingui/format-po@6.0.0-next.3": { - "integrity": "sha512-fFtNFzVaSAefPIJHuoCUSTXy/up9OfJTed3gJm9CgpovxaGVBQfuDQtS2lSghaWiLtGo9UqdkROPrSzYdTqulQ==", - "dependencies": [ - "@lingui/conf@6.0.0-next.3", - "@lingui/message-utils@6.0.0-next.3", - "pofile" - ] - }, - "@lingui/message-utils@5.9.4": { - "integrity": "sha512-YzAVzILdsqdUqwHmryl7rfwZXRHYs6QY2wPLH5gxrV7wlieiCaskaKPeSk2SuN/gmC8im1GDrQHcwgKapFU3Sg==", - "dependencies": [ - "@messageformat/parser", - "js-sha256" - ] - }, - "@lingui/message-utils@6.0.0-next.3": { - "integrity": "sha512-oXTGhXyFoZua0ahVNDwhs7/3tXVAL8NPz7SOTRYORyxDREQwHYTqYXHijHRGquBvL5QEHy28uYSQvy5Szojn8A==", + "@lingui/message-utils@6.4.0": { + "integrity": "sha512-Bkt2V7vI57/CEVyJCO+93jssN2MhLJ07M6S0d16tHvigNftP5Ndl+2xX1HKWD6eozidc5lOeTZMtrEL9jzXEsg==", "dependencies": [ "@messageformat/date-skeleton", "@messageformat/parser", "js-sha256" ] }, - "@lingui/react@6.0.0-next.3_react@19.2.4": { - "integrity": "sha512-tSu+Y8Xsi5oHdjXZaa5o76B4UHdVmrLdQUVDxgMXx+E4s/D30cTkuVtqoKr3kdY/StiEU7aKb08qUdfE52CWyA==", + "@lingui/react@6.4.0_react@19.2.7": { + "integrity": "sha512-9ui7O/K/X+AXN7TdSEbKq//sNLAJ42dk1GftzAcHL58jKv3CnEyKOPJb2S0L/Ez6li8I0O4KKgRn1BK0Qk/Jqg==", "dependencies": [ - "@lingui/babel-plugin-lingui-macro@6.0.0-next.3", - "@lingui/core@6.0.0-next.3", + "@lingui/babel-plugin-lingui-macro", + "@lingui/core", "react" ] }, - "@lingui/swc-plugin@6.0.0-next.2_@lingui+core@6.0.0-next.3": { - "integrity": "sha512-rD20Y7gpquPUw7e/V33lOcsTa28kuuP2GpIK74iQ46KFWm067DKNUkGfcfeEHG6ORBJck6W+KvqeOZ/B4vID1Q==", + "@lingui/swc-plugin@6.4.0_@lingui+core@6.4.0": { + "integrity": "sha512-Naz0MVa23DHFLSbmQjUWzCwzH3vSLkw8Uvqln5Bhgw2raRKT2X4O+zHTBpegeYwRCMsdPCQZXymNxpfu46/0vg==", "dependencies": [ - "@lingui/core@6.0.0-next.3" + "@lingui/conf", + "@lingui/core" ] }, - "@lingui/vite-plugin@6.0.0-next.3_vite@8.0.1__@types+node@24.12.0__jiti@2.6.1_jiti@2.6.1": { - "integrity": "sha512-Ewmg3jcBTp8VEfqkgsr6/YJJ7cphLtLSW+RGkCJREko6pa/a4V3U7hA/xKo2Z2Q1u95E8ngfWJO3qDXLGDb+DA==", + "@lingui/vite-plugin@6.4.0_@babel+core@7.29.7_vite@8.0.16__@types+node@26.0.0__jiti@2.7.0_@types+node@26.0.0_jiti@2.7.0": { + "integrity": "sha512-15ogZWydII5TbWwTJJGfJF9OpATxiIbJGearfBv+tj13uv3TlKwLGdmpn/GV8COY8v0irKr7dfdyfxYvxLxSZQ==", "dependencies": [ - "@lingui/cli@6.0.0-next.3", - "@lingui/conf@6.0.0-next.3", + "@babel/core", + "@lingui/cli", + "@lingui/conf", "vite" + ], + "optionalPeers": [ + "@babel/core" ] }, "@messageformat/date-skeleton@1.1.0": { @@ -726,165 +642,164 @@ "moo" ] }, - "@napi-rs/wasm-runtime@1.1.1": { - "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "@napi-rs/wasm-runtime@1.1.5_@emnapi+core@1.10.0_@emnapi+runtime@1.10.0": { + "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", "dependencies": [ "@emnapi/core", "@emnapi/runtime", "@tybys/wasm-util" ] }, - "@oxc-project/types@0.120.0": { - "integrity": "sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==" + "@oxc-project/types@0.133.0": { + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==" }, - "@rolldown/binding-android-arm64@1.0.0-rc.10": { - "integrity": "sha512-jOHxwXhxmFKuXztiu1ORieJeTbx5vrTkcOkkkn2d35726+iwhrY1w/+nYY/AGgF12thg33qC3R1LMBF5tHTZHg==", + "@rolldown/binding-android-arm64@1.0.3": { + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", "os": ["android"], "cpu": ["arm64"] }, - "@rolldown/binding-darwin-arm64@1.0.0-rc.10": { - "integrity": "sha512-gED05Teg/vtTZbIJBc4VNMAxAFDUPkuO/rAIyyxZjTj1a1/s6z5TII/5yMGZ0uLRCifEtwUQn8OlYzuYc0m70w==", + "@rolldown/binding-darwin-arm64@1.0.3": { + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", "os": ["darwin"], "cpu": ["arm64"] }, - "@rolldown/binding-darwin-x64@1.0.0-rc.10": { - "integrity": "sha512-rI15NcM1mA48lqrIxVkHfAqcyFLcQwyXWThy+BQ5+mkKKPvSO26ir+ZDp36AgYoYVkqvMcdS8zOE6SeBsR9e8A==", + "@rolldown/binding-darwin-x64@1.0.3": { + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", "os": ["darwin"], "cpu": ["x64"] }, - "@rolldown/binding-freebsd-x64@1.0.0-rc.10": { - "integrity": "sha512-XZRXHdTa+4ME1MuDVp021+doQ+z6Ei4CCFmNc5/sKbqb8YmkiJdj8QKlV3rCI0AJtAeSB5n0WGPuJWNL9p/L2w==", + "@rolldown/binding-freebsd-x64@1.0.3": { + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", "os": ["freebsd"], "cpu": ["x64"] }, - "@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.10": { - "integrity": "sha512-R0SQMRluISSLzFE20sPWYHVmJdDQnRyc/FzSCN72BqQmh2SOZUFG+N3/vBZpR4C6WpEUVYJLrYUXaj43sJsNLA==", + "@rolldown/binding-linux-arm-gnueabihf@1.0.3": { + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", "os": ["linux"], "cpu": ["arm"] }, - "@rolldown/binding-linux-arm64-gnu@1.0.0-rc.10": { - "integrity": "sha512-Y1reMrV/o+cwpduYhJuOE3OMKx32RMYCidf14y+HssARRmhDuWXJ4yVguDg2R/8SyyGNo+auzz64LnPK9Hq6jg==", + "@rolldown/binding-linux-arm64-gnu@1.0.3": { + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", "os": ["linux"], "cpu": ["arm64"] }, - "@rolldown/binding-linux-arm64-musl@1.0.0-rc.10": { - "integrity": "sha512-vELN+HNb2IzuzSBUOD4NHmP9yrGwl1DVM29wlQvx1OLSclL0NgVWnVDKl/8tEks79EFek/kebQKnNJkIAA4W2g==", + "@rolldown/binding-linux-arm64-musl@1.0.3": { + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", "os": ["linux"], "cpu": ["arm64"] }, - "@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.10": { - "integrity": "sha512-ZqrufYTgzxbHwpqOjzSsb0UV/aV2TFIY5rP8HdsiPTv/CuAgCRjM6s9cYFwQ4CNH+hf9Y4erHW1GjZuZ7WoI7w==", + "@rolldown/binding-linux-ppc64-gnu@1.0.3": { + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", "os": ["linux"], "cpu": ["ppc64"] }, - "@rolldown/binding-linux-s390x-gnu@1.0.0-rc.10": { - "integrity": "sha512-gSlmVS1FZJSRicA6IyjoRoKAFK7IIHBs7xJuHRSmjImqk3mPPWbR7RhbnfH2G6bcmMEllCt2vQ/7u9e6bBnByg==", + "@rolldown/binding-linux-s390x-gnu@1.0.3": { + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", "os": ["linux"], "cpu": ["s390x"] }, - "@rolldown/binding-linux-x64-gnu@1.0.0-rc.10": { - "integrity": "sha512-eOCKUpluKgfObT2pHjztnaWEIbUabWzk3qPZ5PuacuPmr4+JtQG4k2vGTY0H15edaTnicgU428XW/IH6AimcQw==", + "@rolldown/binding-linux-x64-gnu@1.0.3": { + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", "os": ["linux"], "cpu": ["x64"] }, - "@rolldown/binding-linux-x64-musl@1.0.0-rc.10": { - "integrity": "sha512-Xdf2jQbfQowJnLcgYfD/m0Uu0Qj5OdxKallD78/IPPfzaiaI4KRAwZzHcKQ4ig1gtg1SuzC7jovNiM2TzQsBXA==", + "@rolldown/binding-linux-x64-musl@1.0.3": { + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", "os": ["linux"], "cpu": ["x64"] }, - "@rolldown/binding-openharmony-arm64@1.0.0-rc.10": { - "integrity": "sha512-o1hYe8hLi1EY6jgPFyxQgQ1wcycX+qz8eEbVmot2hFkgUzPxy9+kF0u0NIQBeDq+Mko47AkaFFaChcvZa9UX9Q==", + "@rolldown/binding-openharmony-arm64@1.0.3": { + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", "os": ["openharmony"], "cpu": ["arm64"] }, - "@rolldown/binding-wasm32-wasi@1.0.0-rc.10": { - "integrity": "sha512-Ugv9o7qYJudqQO5Y5y2N2SOo6S4WiqiNOpuQyoPInnhVzCY+wi/GHltcLHypG9DEUYMB0iTB/huJrpadiAcNcA==", + "@rolldown/binding-wasm32-wasi@1.0.3": { + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", "dependencies": [ + "@emnapi/core", + "@emnapi/runtime", "@napi-rs/wasm-runtime" ], "cpu": ["wasm32"] }, - "@rolldown/binding-win32-arm64-msvc@1.0.0-rc.10": { - "integrity": "sha512-7UODQb4fQUNT/vmgDZBl3XOBAIOutP5R3O/rkxg0aLfEGQ4opbCgU5vOw/scPe4xOqBwL9fw7/RP1vAMZ6QlAQ==", + "@rolldown/binding-win32-arm64-msvc@1.0.3": { + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", "os": ["win32"], "cpu": ["arm64"] }, - "@rolldown/binding-win32-x64-msvc@1.0.0-rc.10": { - "integrity": "sha512-PYxKHMVHOb5NJuDL53vBUl1VwUjymDcYI6rzpIni0C9+9mTiJedvUxSk7/RPp7OOAm3v+EjgMu9bIy3N6b408w==", + "@rolldown/binding-win32-x64-msvc@1.0.3": { + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", "os": ["win32"], "cpu": ["x64"] }, - "@rolldown/pluginutils@1.0.0-rc.10": { - "integrity": "sha512-UkVDEFk1w3mveXeKgaTuYfKWtPbvgck1dT8TUG3bnccrH0XtLTuAyfCoks4Q/M5ZGToSVJTIQYCzy2g/atAOeg==" - }, - "@rolldown/pluginutils@1.0.0-rc.7": { - "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==" + "@rolldown/pluginutils@1.0.1": { + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==" }, "@sinclair/typebox@0.27.10": { "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==" }, - "@swc/core-darwin-arm64@1.15.21": { - "integrity": "sha512-SA8SFg9dp0qKRH8goWsax6bptFE2EdmPf2YRAQW9WoHGf3XKM1bX0nd5UdwxmC5hXsBUZAYf7xSciCler6/oyA==", + "@swc/core-darwin-arm64@1.15.41": { + "integrity": "sha512-kREh6J5paQFvP3i7f/4FbqRNOJREutVFVOkder4GVyCBQ39YmER55cW/y1NNjwrchzFqgYswFn0mMDCqbqKzrw==", "os": ["darwin"], "cpu": ["arm64"] }, - "@swc/core-darwin-x64@1.15.21": { - "integrity": "sha512-//fOVntgowz9+V90lVsNCtyyrtbHp3jWH6Rch7MXHXbcvbLmbCTmssl5DeedUWLLGiAAW1wksBdqdGYOTjaNLw==", + "@swc/core-darwin-x64@1.15.41": { + "integrity": "sha512-N8B56ESFazZAWZyIkecADSPCwlLEinW7QLMEeotCpv4J7VXwfH+OLkmRL8o96UZ+1355fwHxDTS6/wK7yucvkA==", "os": ["darwin"], "cpu": ["x64"] }, - "@swc/core-linux-arm-gnueabihf@1.15.21": { - "integrity": "sha512-meNI4Sh6h9h8DvIfEc0l5URabYMSuNvyisLmG6vnoYAS43s8ON3NJR8sDHvdP7NJTrLe0q/x2XCn6yL/BeHcZg==", + "@swc/core-linux-arm-gnueabihf@1.15.41": { + "integrity": "sha512-6XrId2fyle0mS5xxON8rU84mPd2Cq1kDJRj+4BnQKTd7u+2kSA6Ww+JkOP0iTNqOqt9OXhPOEAjBHAuonWcdCg==", "os": ["linux"], "cpu": ["arm"] }, - "@swc/core-linux-arm64-gnu@1.15.21": { - "integrity": "sha512-QrXlNQnHeXqU2EzLlnsPoWEh8/GtNJLvfMiPsDhk+ht6Xv8+vhvZ5YZ/BokNWSIZiWPKLAqR0M7T92YF5tmD3g==", + "@swc/core-linux-arm64-gnu@1.15.41": { + "integrity": "sha512-ynLIarxlkVnqHn1D0fKOVht6mNU5ks6lrH+MY3kkS+XFaGGgDxFZVjWKJlkYTKm3RCvBTfA8Ng5fLufXheMRKQ==", "os": ["linux"], "cpu": ["arm64"] }, - "@swc/core-linux-arm64-musl@1.15.21": { - "integrity": "sha512-8/yGCMO333ultDaMQivE5CjO6oXDPeeg1IV4sphojPkb0Pv0i6zvcRIkgp60xDB+UxLr6VgHgt+BBgqS959E9g==", + "@swc/core-linux-arm64-musl@1.15.41": { + "integrity": "sha512-dXu/5vd4gh8symyhRF+4G7gOPkjmb4pONhh7sl+6GSiW0LOKZlfu5kXmyFbTz9smOT7jgr002qY9b1nujjXt2A==", "os": ["linux"], "cpu": ["arm64"] }, - "@swc/core-linux-ppc64-gnu@1.15.21": { - "integrity": "sha512-ucW0HzPx0s1dgRvcvuLSPSA/2Kk/VYTv9st8qe1Kc22Gu0Q0rH9+6TcBTmMuNIp0Xs4BPr1uBttmbO1wEGI49Q==", + "@swc/core-linux-ppc64-gnu@1.15.41": { + "integrity": "sha512-XGO6zVPXoPE0gf/XnI4jBbafNT13AYgoh6ns0JCSdOetI/kqVf0vhpz7NuNgAzZrMVCsmieqjPoTwViDgh4mOQ==", "os": ["linux"], "cpu": ["ppc64"] }, - "@swc/core-linux-s390x-gnu@1.15.21": { - "integrity": "sha512-ulTnOGc5I7YRObE/9NreAhQg94QkiR5qNhhcUZ1iFAYjzg/JGAi1ch+s/Ixe61pMIr8bfVrF0NOaB0f8wjaAfA==", + "@swc/core-linux-s390x-gnu@1.15.41": { + "integrity": "sha512-0WUglRwyZtW+iMi7J3iFdrCxreZZIKf4egTwEQfIYRsqFax69A0OrFj+NIoFSE03xBT/IFRrg+S8K6f9Ky+4hA==", "os": ["linux"], "cpu": ["s390x"] }, - "@swc/core-linux-x64-gnu@1.15.21": { - "integrity": "sha512-D0RokxtM+cPvSqJIKR6uja4hbD+scI9ezo95mBhfSyLUs9wnPPl26sLp1ZPR/EXRdYm3F3S6RUtVi+8QXhT24Q==", + "@swc/core-linux-x64-gnu@1.15.41": { + "integrity": "sha512-VxkuQK59c0tHm6uJZCUrS3cyA2JhGGfdU6e41SZz0x/JS+4Sm7C1mIc97In14vkZJopEt7yXA2TouCqZDSygEA==", "os": ["linux"], "cpu": ["x64"] }, - "@swc/core-linux-x64-musl@1.15.21": { - "integrity": "sha512-nER8u7VeRfmU6fMDzl1NQAbbB/G7O2avmvCOwIul1uGkZ2/acbPH+DCL9h5+0yd/coNcxMBTL6NGepIew+7C2w==", + "@swc/core-linux-x64-musl@1.15.41": { + "integrity": "sha512-/0qXIu1ZxggLuovLb22vFfKHq2AA4n6Whw5UwmVCHk4pkw7KWnPIQpMCEqUMPsNkFJig7PPp/TSYFu8ZEb2rtQ==", "os": ["linux"], "cpu": ["x64"] }, - "@swc/core-win32-arm64-msvc@1.15.21": { - "integrity": "sha512-+/AgNBnjYugUA8C0Do4YzymgvnGbztv7j8HKSQLvR/DQgZPoXQ2B3PqB2mTtGh/X5DhlJWiqnunN35JUgWcAeQ==", + "@swc/core-win32-arm64-msvc@1.15.41": { + "integrity": "sha512-Y481sMNZM6rECh9VO4+y26N1lWEDAyxnBZskUf37fl90uHE946VHfmiVQWT0uMFOhyJJFovGTRuF4W82dwewUg==", "os": ["win32"], "cpu": ["arm64"] }, - "@swc/core-win32-ia32-msvc@1.15.21": { - "integrity": "sha512-IkSZj8PX/N4HcaFhMQtzmkV8YSnuNoJ0E6OvMwFiOfejPhiKXvl7CdDsn1f4/emYEIDO3fpgZW9DTaCRMDxaDA==", + "@swc/core-win32-ia32-msvc@1.15.41": { + "integrity": "sha512-BAchBD5qeUzy3hiPSLJtaaoSm4blCLyYffOF1bGE4ETcV+OisqjUAwDQMJj++4bTpvMCDzwC+Bj3PmQyBCtscw==", "os": ["win32"], "cpu": ["ia32"] }, - "@swc/core-win32-x64-msvc@1.15.21": { - "integrity": "sha512-zUyWso7OOENB6e1N1hNuNn8vbvLsTdKQ5WKLgt/JcBNfJhKy/6jmBmqI3GXk/MyvQKd5SLvP7A0F36p7TeDqvw==", + "@swc/core-win32-x64-msvc@1.15.41": { + "integrity": "sha512-WOkA+fJ/ViVBQDsSV9JC52NACTe5PhlurA6viASDZGb7HR3KS01ZG7RZ+Bg6SVQFIoq3gSbTsskQVe6EbHFAYw==", "os": ["win32"], "cpu": ["x64"] }, - "@swc/core@1.15.21": { - "integrity": "sha512-fkk7NJcBscrR3/F8jiqlMptRHP650NxqDnspBMrRe5d8xOoCy9MLL5kOBLFXjFLfMo3KQQHhk+/jUULOMlR1uQ==", + "@swc/core@1.15.41": { + "integrity": "sha512-03nQq/082QRJJiOvp3FGbgxTGyyxMxohPTjhk/W9bD2J0tk4ukITI7goOhOO2WbaHn/lsPmo/zf8+DIXhwpgYQ==", "dependencies": [ "@swc/counter", "@swc/types" @@ -908,14 +823,14 @@ "@swc/counter@0.1.3": { "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" }, - "@swc/types@0.1.26": { - "integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==", + "@swc/types@0.1.27": { + "integrity": "sha512-K6h3iUlqeM946U4sXFYeahefR1YBbXJvko+hv8WS8/0BNJ4OHiHRywMnQUJCqkR7Y9+hqQ1TvEpiKqUhz7NEFg==", "dependencies": [ "@swc/counter" ] }, - "@tybys/wasm-util@0.10.1": { - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "@tybys/wasm-util@0.10.2": { + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", "dependencies": [ "tslib" ] @@ -926,6 +841,9 @@ "@types/ms" ] }, + "@types/esrecurse@4.3.1": { + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==" + }, "@types/estree-jsx@1.0.5": { "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", "dependencies": [ @@ -968,20 +886,20 @@ "@types/ms@2.1.0": { "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" }, - "@types/node@24.12.0": { - "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", + "@types/node@26.0.0": { + "integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==", "dependencies": [ "undici-types" ] }, - "@types/react-dom@19.2.3_@types+react@19.2.14": { + "@types/react-dom@19.2.3_@types+react@19.2.17": { "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dependencies": [ "@types/react" ] }, - "@types/react@19.2.14": { - "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "@types/react@19.2.17": { + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "dependencies": [ "csstype" ] @@ -1001,8 +919,8 @@ "@types/yargs-parser" ] }, - "@typescript-eslint/eslint-plugin@8.57.1_@typescript-eslint+parser@8.57.1__eslint@9.39.4___jiti@2.6.1__typescript@5.9.3__jiti@2.6.1_eslint@9.39.4__jiti@2.6.1_typescript@5.9.3_jiti@2.6.1": { - "integrity": "sha512-Gn3aqnvNl4NGc6x3/Bqk1AOn0thyTU9bqDRhiRnUWezgvr2OnhYCWCgC8zXXRVqBsIL1pSDt7T9nJUe0oM0kDQ==", + "@typescript-eslint/eslint-plugin@8.61.1_@typescript-eslint+parser@8.61.1__eslint@10.5.0___jiti@2.7.0__typescript@6.0.3__jiti@2.7.0_eslint@10.5.0__jiti@2.7.0_typescript@6.0.3_jiti@2.7.0": { + "integrity": "sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==", "dependencies": [ "@eslint-community/regexpp", "@typescript-eslint/parser", @@ -1017,8 +935,8 @@ "typescript" ] }, - "@typescript-eslint/parser@8.57.1_eslint@9.39.4__jiti@2.6.1_typescript@5.9.3_jiti@2.6.1": { - "integrity": "sha512-k4eNDan0EIMTT/dUKc/g+rsJ6wcHYhNPdY19VoX/EOtaAG8DLtKCykhrUnuHPYvinn5jhAPgD2Qw9hXBwrahsw==", + "@typescript-eslint/parser@8.61.1_eslint@10.5.0__jiti@2.7.0_typescript@6.0.3_jiti@2.7.0": { + "integrity": "sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==", "dependencies": [ "@typescript-eslint/scope-manager", "@typescript-eslint/types", @@ -1029,8 +947,8 @@ "typescript" ] }, - "@typescript-eslint/project-service@8.57.1_typescript@5.9.3": { - "integrity": "sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg==", + "@typescript-eslint/project-service@8.61.1_typescript@6.0.3": { + "integrity": "sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==", "dependencies": [ "@typescript-eslint/tsconfig-utils", "@typescript-eslint/types", @@ -1038,21 +956,21 @@ "typescript" ] }, - "@typescript-eslint/scope-manager@8.57.1": { - "integrity": "sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg==", + "@typescript-eslint/scope-manager@8.61.1": { + "integrity": "sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==", "dependencies": [ "@typescript-eslint/types", "@typescript-eslint/visitor-keys" ] }, - "@typescript-eslint/tsconfig-utils@8.57.1_typescript@5.9.3": { - "integrity": "sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg==", + "@typescript-eslint/tsconfig-utils@8.61.1_typescript@6.0.3": { + "integrity": "sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==", "dependencies": [ "typescript" ] }, - "@typescript-eslint/type-utils@8.57.1_eslint@9.39.4__jiti@2.6.1_typescript@5.9.3_jiti@2.6.1": { - "integrity": "sha512-+Bwwm0ScukFdyoJsh2u6pp4S9ktegF98pYUU0hkphOOqdMB+1sNQhIz8y5E9+4pOioZijrkfNO/HUJVAFFfPKA==", + "@typescript-eslint/type-utils@8.61.1_eslint@10.5.0__jiti@2.7.0_typescript@6.0.3_jiti@2.7.0": { + "integrity": "sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==", "dependencies": [ "@typescript-eslint/types", "@typescript-eslint/typescript-estree", @@ -1063,26 +981,26 @@ "typescript" ] }, - "@typescript-eslint/types@8.57.1": { - "integrity": "sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==" + "@typescript-eslint/types@8.61.1": { + "integrity": "sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==" }, - "@typescript-eslint/typescript-estree@8.57.1_typescript@5.9.3": { - "integrity": "sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g==", + "@typescript-eslint/typescript-estree@8.61.1_typescript@6.0.3": { + "integrity": "sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==", "dependencies": [ "@typescript-eslint/project-service", "@typescript-eslint/tsconfig-utils", "@typescript-eslint/types", "@typescript-eslint/visitor-keys", "debug", - "minimatch@10.2.4", - "semver@7.7.4", + "minimatch", + "semver@7.8.5", "tinyglobby", "ts-api-utils", "typescript" ] }, - "@typescript-eslint/utils@8.57.1_eslint@9.39.4__jiti@2.6.1_typescript@5.9.3_jiti@2.6.1": { - "integrity": "sha512-XUNSJ/lEVFttPMMoDVA2r2bwrl8/oPx8cURtczkSEswY5T3AeLmCy+EKWQNdL4u0MmAHOjcWrqJp2cdvgjn8dQ==", + "@typescript-eslint/utils@8.61.1_eslint@10.5.0__jiti@2.7.0_typescript@6.0.3_jiti@2.7.0": { + "integrity": "sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==", "dependencies": [ "@eslint-community/eslint-utils", "@typescript-eslint/scope-manager", @@ -1092,36 +1010,37 @@ "typescript" ] }, - "@typescript-eslint/visitor-keys@8.57.1": { - "integrity": "sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A==", + "@typescript-eslint/visitor-keys@8.61.1": { + "integrity": "sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==", "dependencies": [ "@typescript-eslint/types", "eslint-visitor-keys@5.0.1" ] }, "@ungap/structured-clone@1.3.0": { - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==" + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "deprecated": true }, - "@vitejs/plugin-react-swc@4.3.0_vite@8.0.1__@types+node@24.12.0__jiti@2.6.1_jiti@2.6.1": { - "integrity": "sha512-mOkXCII839dHyAt/gpoSlm28JIVDwhZ6tnG6wJxUy2bmOx7UaPjvOyIDf3SFv5s7Eo7HVaq6kRcu6YMEzt5Z7w==", + "@vitejs/plugin-react-swc@4.3.1_vite@8.0.16__@types+node@26.0.0__jiti@2.7.0_@types+node@26.0.0_jiti@2.7.0": { + "integrity": "sha512-PaeokKjAGraNN+s5SIApgsktnJprIyt3zgEIu7awnEdfn29QiB2crTcCzyi2XGpX9rUnTc0cKU07Wm0N0g7H2w==", "dependencies": [ - "@rolldown/pluginutils@1.0.0-rc.7", + "@rolldown/pluginutils", "@swc/core", "vite" ] }, - "acorn-jsx@5.3.2_acorn@8.16.0": { + "acorn-jsx@5.3.2_acorn@8.17.0": { "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dependencies": [ "acorn" ] }, - "acorn@8.16.0": { - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "acorn@8.17.0": { + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "bin": true }, - "ajv@6.14.0": { - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "ajv@6.15.0": { + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dependencies": [ "fast-deep-equal", "fast-json-stable-stringify", @@ -1144,54 +1063,20 @@ "ansi-styles@5.2.0": { "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" }, - "anymatch@3.1.3": { - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dependencies": [ - "normalize-path", - "picomatch@2.3.1" - ] - }, - "argparse@2.0.1": { - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, "bail@2.0.2": { "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==" }, - "balanced-match@1.0.2": { - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, "balanced-match@4.0.4": { "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==" }, - "base64-js@1.5.1": { - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" - }, "baseline-browser-mapping@2.10.16": { "integrity": "sha512-Lyf3aK28zpsD1yQMiiHD4RvVb6UdMoo8xzG2XzFIfR9luPzOpcBlAsT/qfB1XWS1bxWT+UtE4WmQgsp297FYOA==", "bin": true }, - "binary-extensions@2.3.0": { - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==" - }, - "bl@4.1.0": { - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dependencies": [ - "buffer", - "inherits", - "readable-stream" - ] - }, - "brace-expansion@1.1.12": { - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dependencies": [ - "balanced-match@1.0.2", - "concat-map" - ] - }, "brace-expansion@5.0.4": { "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", "dependencies": [ - "balanced-match@4.0.4" + "balanced-match" ] }, "braces@3.0.3": { @@ -1211,16 +1096,6 @@ ], "bin": true }, - "buffer@5.7.1": { - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dependencies": [ - "base64-js", - "ieee754" - ] - }, - "callsites@3.1.0": { - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" - }, "camelcase@6.3.0": { "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==" }, @@ -1252,42 +1127,18 @@ "character-reference-invalid@2.0.1": { "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==" }, - "chokidar@3.5.1": { - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dependencies": [ - "anymatch", - "braces", - "glob-parent@5.1.2", - "is-binary-path", - "is-glob", - "normalize-path", - "readdirp@3.5.0" - ], - "optionalDependencies": [ - "fsevents" - ] - }, "chokidar@5.0.0": { "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "dependencies": [ - "readdirp@5.0.0" - ] - }, - "cli-cursor@3.1.0": { - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dependencies": [ - "restore-cursor@3.1.0" + "readdirp" ] }, "cli-cursor@5.0.0": { "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dependencies": [ - "restore-cursor@5.1.0" + "restore-cursor" ] }, - "cli-spinners@2.9.2": { - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==" - }, "cli-spinners@3.4.0": { "integrity": "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==" }, @@ -1300,15 +1151,6 @@ "@colors/colors" ] }, - "cli-table@0.3.11": { - "integrity": "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==", - "dependencies": [ - "colors" - ] - }, - "clone@1.0.4": { - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==" - }, "color-convert@2.0.1": { "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dependencies": [ @@ -1318,9 +1160,6 @@ "color-name@1.1.4": { "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "colors@1.0.3": { - "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==" - }, "comma-separated-tokens@2.0.3": { "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==" }, @@ -1330,27 +1169,11 @@ "commander@14.0.3": { "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==" }, - "concat-map@0.0.1": { - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, "convert-source-map@2.0.0": { "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, - "cookie@1.1.1": { - "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==" - }, - "cosmiconfig@8.3.6_typescript@5.9.3": { - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", - "dependencies": [ - "import-fresh", - "js-yaml", - "parse-json", - "path-type", - "typescript" - ], - "optionalPeers": [ - "typescript" - ] + "cookie-es@3.1.1": { + "integrity": "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==" }, "cross-spawn@7.0.6": { "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", @@ -1363,9 +1186,6 @@ "csstype@3.2.3": { "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" }, - "date-fns@3.6.0": { - "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==" - }, "debug@4.4.3": { "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dependencies": [ @@ -1381,12 +1201,6 @@ "deep-is@0.1.4": { "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" }, - "defaults@1.0.4": { - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dependencies": [ - "clone" - ] - }, "dequal@2.0.3": { "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" }, @@ -1405,12 +1219,6 @@ "emoji-regex@8.0.0": { "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, - "error-ex@1.3.4": { - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "dependencies": [ - "is-arrayish" - ] - }, "esbuild@0.25.12": { "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", "optionalDependencies": [ @@ -1453,8 +1261,8 @@ "escape-string-regexp@5.0.0": { "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==" }, - "eslint-plugin-react-hooks@7.0.1_eslint@9.39.4__jiti@2.6.1_jiti@2.6.1": { - "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "eslint-plugin-react-hooks@7.1.1_eslint@10.5.0__jiti@2.7.0_jiti@2.7.0": { + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", "dependencies": [ "@babel/core", "@babel/parser", @@ -1464,15 +1272,17 @@ "zod-validation-error" ] }, - "eslint-plugin-react-refresh@0.5.2_eslint@9.39.4__jiti@2.6.1_jiti@2.6.1": { - "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==", + "eslint-plugin-react-refresh@0.5.3_eslint@10.5.0__jiti@2.7.0_jiti@2.7.0": { + "integrity": "sha512-5EMmLCV98Pi4o/f/3DP/v/tNqLHMIc9I8LKClNDWhZ9JTho89/kQcitCXQBMG7sAfVRK0Ie3T2EDOzp1YXYiVA==", "dependencies": [ "eslint" ] }, - "eslint-scope@8.4.0": { - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "eslint-scope@9.1.2": { + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dependencies": [ + "@types/esrecurse", + "@types/estree", "esrecurse", "estraverse" ] @@ -1480,48 +1290,41 @@ "eslint-visitor-keys@3.4.3": { "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==" }, - "eslint-visitor-keys@4.2.1": { - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==" - }, "eslint-visitor-keys@5.0.1": { "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==" }, - "eslint@9.39.4_jiti@2.6.1": { - "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "eslint@10.5.0_jiti@2.7.0": { + "integrity": "sha512-1y+7C+vi12bUK1IpZeaV3gsH9fHLBmPvYmPx42pvT/E9yG0IC8g3PUZZgp0+JLJl7ZDK0flc2gc+Aw9dpCvIsQ==", "dependencies": [ "@eslint-community/eslint-utils", "@eslint-community/regexpp", "@eslint/config-array", "@eslint/config-helpers", "@eslint/core", - "@eslint/eslintrc", - "@eslint/js", "@eslint/plugin-kit", "@humanfs/node", "@humanwhocodes/module-importer", "@humanwhocodes/retry", "@types/estree", "ajv", - "chalk@4.1.2", "cross-spawn", "debug", "escape-string-regexp@4.0.0", "eslint-scope", - "eslint-visitor-keys@4.2.1", + "eslint-visitor-keys@5.0.1", "espree", "esquery", "esutils", "fast-deep-equal", "file-entry-cache", "find-up", - "glob-parent@6.0.2", + "glob-parent", "ignore@5.3.2", "imurmurhash", "is-glob", "jiti", "json-stable-stringify-without-jsonify", - "lodash.merge", - "minimatch@3.1.5", + "minimatch", "natural-compare", "optionator" ], @@ -1530,15 +1333,12 @@ ], "bin": true }, - "esm@3.2.25": { - "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==" - }, - "espree@10.4.0": { - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "espree@11.2.0": { + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "dependencies": [ "acorn", "acorn-jsx", - "eslint-visitor-keys@4.2.1" + "eslint-visitor-keys@5.0.1" ] }, "esquery@1.7.0": { @@ -1574,13 +1374,13 @@ "fast-levenshtein@2.0.6": { "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, - "fdir@6.5.0_picomatch@4.0.3": { + "fdir@6.5.0_picomatch@4.0.4": { "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dependencies": [ - "picomatch@4.0.3" + "picomatch@4.0.4" ], "optionalPeers": [ - "picomatch@4.0.3" + "picomatch@4.0.4" ] }, "file-entry-cache@8.0.0": { @@ -1612,14 +1412,7 @@ "flatted@3.4.2": { "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==" }, - "foreground-child@3.3.1": { - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dependencies": [ - "cross-spawn", - "signal-exit@4.1.0" - ] - }, - "frimousse@0.3.0_react@19.2.4_typescript@5.9.3": { + "frimousse@0.3.0_react@19.2.7_typescript@6.0.3": { "integrity": "sha512-kO6LMoKY/cLAYEhXXtqLRaLIE6L/DagpFPrUZaLv3LsUa1/8Iza3HhwZcgN8eZ+weXnhv69eoclNUPohcCa/IQ==", "dependencies": [ "react", @@ -1637,14 +1430,8 @@ "gensync@1.0.0-beta.2": { "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" }, - "get-east-asian-width@1.5.0": { - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==" - }, - "glob-parent@5.1.2": { - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": [ - "is-glob" - ] + "get-east-asian-width@1.6.0": { + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==" }, "glob-parent@6.0.2": { "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", @@ -1652,24 +1439,8 @@ "is-glob" ] }, - "glob@11.1.0": { - "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", - "dependencies": [ - "foreground-child", - "jackspeak", - "minimatch@10.2.4", - "minipass", - "package-json-from-dist", - "path-scurry" - ], - "deprecated": true, - "bin": true - }, - "globals@14.0.0": { - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==" - }, - "globals@17.4.0": { - "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==" + "globals@17.6.0": { + "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==" }, "has-flag@4.0.0": { "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" @@ -1712,28 +1483,15 @@ "html-url-attributes@3.0.1": { "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==" }, - "ieee754@1.2.1": { - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" - }, "ignore@5.3.2": { "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==" }, "ignore@7.0.5": { "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==" }, - "import-fresh@3.3.1": { - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dependencies": [ - "parent-module", - "resolve-from" - ] - }, "imurmurhash@0.1.4": { "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" }, - "inherits@2.0.4": { - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, "inline-style-parser@0.2.7": { "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==" }, @@ -1747,15 +1505,6 @@ "is-decimal" ] }, - "is-arrayish@0.2.1": { - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" - }, - "is-binary-path@2.1.0": { - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dependencies": [ - "binary-extensions" - ] - }, "is-decimal@2.0.1": { "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==" }, @@ -1774,36 +1523,21 @@ "is-hexadecimal@2.0.1": { "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==" }, - "is-interactive@1.0.0": { - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==" - }, "is-interactive@2.0.0": { "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==" }, "is-number@7.0.0": { "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, - "is-observable@2.1.0": { - "integrity": "sha512-DailKdLb0WU+xX8K5w7VsJhapwHLZ9jjmazqCJq4X12CTgqq73TKnbRcnSLuXYPOoLQgV5IrD7ePiX/h1vnkBw==" - }, "is-plain-obj@4.1.0": { "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==" }, - "is-unicode-supported@0.1.0": { - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==" - }, "is-unicode-supported@2.1.0": { "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==" }, "isexe@2.0.0": { "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, - "jackspeak@4.2.3": { - "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", - "dependencies": [ - "@isaacs/cliui" - ] - }, "jest-get-type@29.6.3": { "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==" }, @@ -1818,8 +1552,8 @@ "pretty-format" ] }, - "jiti@2.6.1": { - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "jiti@2.7.0": { + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", "bin": true }, "js-sha256@0.10.1": { @@ -1828,13 +1562,6 @@ "js-tokens@4.0.0": { "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, - "js-yaml@4.1.1": { - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dependencies": [ - "argparse" - ], - "bin": true - }, "jsesc@3.1.0": { "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "bin": true @@ -1842,9 +1569,6 @@ "json-buffer@3.0.1": { "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, - "json-parse-even-better-errors@2.3.1": { - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, "json-schema-traverse@0.4.1": { "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, @@ -1948,38 +1672,22 @@ "lilconfig@3.1.3": { "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==" }, - "lines-and-columns@1.2.4": { - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" - }, "locate-path@6.0.0": { "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dependencies": [ "p-locate" ] }, - "lodash.merge@4.6.2": { - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" - }, - "log-symbols@4.1.0": { - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dependencies": [ - "chalk@4.1.2", - "is-unicode-supported@0.1.0" - ] - }, "log-symbols@7.0.1": { "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", "dependencies": [ - "is-unicode-supported@2.1.0", + "is-unicode-supported", "yoctocolors" ] }, "longest-streak@3.1.0": { "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==" }, - "lru-cache@11.2.7": { - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==" - }, "lru-cache@5.1.1": { "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dependencies": [ @@ -1989,8 +1697,8 @@ "markdown-table@3.0.4": { "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==" }, - "marked@15.0.12": { - "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "marked@18.0.5": { + "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==", "bin": true }, "mdast-util-find-and-replace@3.0.2": { @@ -2406,35 +2114,23 @@ "picomatch@2.3.1" ] }, - "mimic-fn@2.1.0": { - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" - }, "mimic-function@5.0.1": { "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==" }, "minimatch@10.2.4": { "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dependencies": [ - "brace-expansion@5.0.4" + "brace-expansion" ] }, - "minimatch@3.1.5": { - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dependencies": [ - "brace-expansion@1.1.12" - ] - }, - "minipass@7.1.3": { - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==" - }, "moo@0.5.3": { "integrity": "sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==" }, "ms@2.1.3": { "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "nanoid@3.3.11": { - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "nanoid@3.3.14": { + "integrity": "sha512-U9kYi5bpVMEI31yC8iw4bJJp0avcHXA0W8/wNfLfnvJYzihQo2ZRPYPvpAAd570HAcCBjCTN7vnr+v4StKl1IQ==", "bin": true }, "natural-compare@1.4.0": { @@ -2443,21 +2139,12 @@ "node-releases@2.0.36": { "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==" }, - "nodemailer@8.0.4": { - "integrity": "sha512-k+jf6N8PfQJ0Fe8ZhJlgqU5qJU44Lpvp2yvidH3vp1lPnVQMgi4yEEMPXg5eJS1gFIJTVq1NHBk7Ia9ARdSBdQ==" + "nodemailer@9.0.1": { + "integrity": "sha512-Gwv8SQewT616ZM/URn0H54b8PWo/Wum7md3EW2aWy1lO27+WZCX+Xyak3J+NlmHUjDh5ME+uesJUDRbR3Ye8Bw==" }, "normalize-path@3.0.0": { "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, - "observable-fns@0.6.1": { - "integrity": "sha512-9gRK4+sRWzeN6AOewNBTLXir7Zl/i3GB6Yl26gK4flxz8BXVpD3kt8amREmWNb0mxYOGDotvE5a4N+PtGGKdkg==" - }, - "onetime@5.1.2": { - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dependencies": [ - "mimic-fn" - ] - }, "onetime@7.0.0": { "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dependencies": [ @@ -2475,31 +2162,17 @@ "word-wrap" ] }, - "ora@5.4.1": { - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dependencies": [ - "bl", - "chalk@4.1.2", - "cli-cursor@3.1.0", - "cli-spinners@2.9.2", - "is-interactive@1.0.0", - "is-unicode-supported@0.1.0", - "log-symbols@4.1.0", - "strip-ansi@6.0.1", - "wcwidth" - ] - }, - "ora@9.3.0": { - "integrity": "sha512-lBX72MWFduWEf7v7uWf5DHp9Jn5BI8bNPGuFgtXMmr2uDz2Gz2749y3am3agSDdkhHPHYmmxEGSKH85ZLGzgXw==", + "ora@9.4.0": { + "integrity": "sha512-84cglkRILFxdtA8hAvLNdMrtBpPNBTrQ9/ulg0FA7xLMnD6mifv+enAIeRmvtv+WgdCE+LPGOfQmtJRrVaIVhQ==", "dependencies": [ "chalk@5.6.2", - "cli-cursor@5.0.0", - "cli-spinners@3.4.0", - "is-interactive@2.0.0", - "is-unicode-supported@2.1.0", - "log-symbols@7.0.1", + "cli-cursor", + "cli-spinners", + "is-interactive", + "is-unicode-supported", + "log-symbols", "stdin-discarder", - "string-width@8.2.0" + "string-width@8.2.1" ] }, "p-limit@3.1.0": { @@ -2514,15 +2187,6 @@ "p-limit" ] }, - "package-json-from-dist@1.0.1": { - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" - }, - "parent-module@1.0.1": { - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dependencies": [ - "callsites" - ] - }, "parse-entities@4.0.2": { "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", "dependencies": [ @@ -2535,48 +2199,29 @@ "is-hexadecimal" ] }, - "parse-json@5.2.0": { - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dependencies": [ - "@babel/code-frame", - "error-ex", - "json-parse-even-better-errors", - "lines-and-columns" - ] - }, "path-exists@4.0.0": { "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" }, "path-key@3.1.1": { "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, - "path-scurry@2.0.2": { - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "dependencies": [ - "lru-cache@11.2.7", - "minipass" - ] - }, "path-to-regexp@6.3.0": { "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==" }, - "path-type@4.0.0": { - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" - }, "picocolors@1.1.1": { "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "picomatch@2.3.1": { "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, - "picomatch@4.0.3": { - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==" + "picomatch@4.0.4": { + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, - "pofile@1.1.4": { - "integrity": "sha512-r6Q21sKsY1AjTVVjOuU02VYKVNQGJNQHjTIvs4dEbeuuYfxgYk/DGD2mqqq4RDaVkwdSq0VEtmQUOPe/wH8X3g==" + "pofile-ts@4.0.3": { + "integrity": "sha512-sz1pnjgEfPyZ+QvaeX3NtCmbYnEvG01LZRLoN/uXoLtPZtxCIH5IctL7yXXc0fFyk/fqV6K8g3hlNfr6IJwupA==" }, - "postcss@8.5.8": { - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "postcss@8.5.15": { + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dependencies": [ "nanoid", "picocolors", @@ -2607,8 +2252,8 @@ "punycode@2.3.1": { "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" }, - "react-dom@19.2.4_react@19.2.4": { - "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "react-dom@19.2.7_react@19.2.7": { + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", "dependencies": [ "react", "scheduler" @@ -2617,7 +2262,7 @@ "react-is@18.3.1": { "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, - "react-markdown@10.1.0_@types+react@19.2.14_react@19.2.4": { + "react-markdown@10.1.0_@types+react@19.2.17_react@19.2.7": { "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", "dependencies": [ "@types/hast", @@ -2635,34 +2280,19 @@ "vfile" ] }, - "react-router@7.13.1_react@19.2.4_react-dom@19.2.4__react@19.2.4": { - "integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==", + "react-router@8.0.1_react@19.2.7_react-dom@19.2.7__react@19.2.7": { + "integrity": "sha512-5EL/fANovVUhRK50NLS8RYfX0BxrimoKsHWUPPy8v5UEl8i6vzF7e4POo3u+AhPItDwccUAJjMfIOmydxBJmQw==", "dependencies": [ - "cookie", + "cookie-es", "react", - "react-dom", - "set-cookie-parser" + "react-dom" ], "optionalPeers": [ "react-dom" ] }, - "react@19.2.4": { - "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==" - }, - "readable-stream@3.6.2": { - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": [ - "inherits", - "string_decoder", - "util-deprecate" - ] - }, - "readdirp@3.5.0": { - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dependencies": [ - "picomatch@2.3.1" - ] + "react@19.2.7": { + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==" }, "readdirp@5.0.0": { "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==" @@ -2705,28 +2335,18 @@ "unified" ] }, - "resolve-from@4.0.0": { - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" - }, - "restore-cursor@3.1.0": { - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dependencies": [ - "onetime@5.1.2", - "signal-exit@3.0.7" - ] - }, "restore-cursor@5.1.0": { "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dependencies": [ - "onetime@7.0.0", - "signal-exit@4.1.0" + "onetime", + "signal-exit" ] }, - "rolldown@1.0.0-rc.10": { - "integrity": "sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA==", + "rolldown@1.0.3": { + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", "dependencies": [ "@oxc-project/types", - "@rolldown/pluginutils@1.0.0-rc.10" + "@rolldown/pluginutils" ], "optionalDependencies": [ "@rolldown/binding-android-arm64", @@ -2747,9 +2367,6 @@ ], "bin": true }, - "safe-buffer@5.2.1": { - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, "scheduler@0.27.0": { "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==" }, @@ -2757,13 +2374,10 @@ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": true }, - "semver@7.7.4": { - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "semver@7.8.5": { + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "bin": true }, - "set-cookie-parser@2.7.2": { - "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==" - }, "shebang-command@2.0.0": { "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dependencies": [ @@ -2773,9 +2387,6 @@ "shebang-regex@3.0.0": { "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, - "signal-exit@3.0.7": { - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, "signal-exit@4.1.0": { "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" }, @@ -2788,8 +2399,8 @@ "space-separated-tokens@2.0.2": { "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==" }, - "stdin-discarder@0.3.1": { - "integrity": "sha512-reExS1kSGoElkextOcPkel4NE99S0BWxjUHQeDFnR8S993JxpPX7KU4MNmO19NXhlJp+8dmdCbKQVNgLJh2teA==" + "stdin-discarder@0.3.2": { + "integrity": "sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==" }, "string-width@4.2.3": { "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", @@ -2799,19 +2410,13 @@ "strip-ansi@6.0.1" ] }, - "string-width@8.2.0": { - "integrity": "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==", + "string-width@8.2.1": { + "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", "dependencies": [ "get-east-asian-width", "strip-ansi@7.2.0" ] }, - "string_decoder@1.3.0": { - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": [ - "safe-buffer" - ] - }, "stringify-entities@4.0.4": { "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", "dependencies": [ @@ -2831,9 +2436,6 @@ "ansi-regex@6.2.2" ] }, - "strip-json-comments@3.1.1": { - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" - }, "style-to-js@1.1.21": { "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", "dependencies": [ @@ -2852,29 +2454,11 @@ "has-flag" ] }, - "threads@1.7.0": { - "integrity": "sha512-Mx5NBSHX3sQYR6iI9VYbgHKBLisyB+xROCBGjjWm1O9wb9vfLxdaGtmT/KCjUqMsSNW6nERzCW3T6H43LqjDZQ==", - "dependencies": [ - "callsites", - "debug", - "is-observable", - "observable-fns" - ], - "optionalDependencies": [ - "tiny-worker" - ] - }, - "tiny-worker@2.3.0": { - "integrity": "sha512-pJ70wq5EAqTAEl9IkGzA+fN0836rycEuz2Cn6yeZ6FRzlVS5IDOkFHpIoEsksPRQV34GDqXm65+OlnZqUSyK2g==", - "dependencies": [ - "esm" - ] - }, - "tinyglobby@0.2.15": { - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "tinyglobby@0.2.17": { + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dependencies": [ "fdir", - "picomatch@4.0.3" + "picomatch@4.0.4" ] }, "tinypool@2.1.0": { @@ -2892,7 +2476,7 @@ "trough@2.2.0": { "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==" }, - "ts-api-utils@2.5.0_typescript@5.9.3": { + "ts-api-utils@2.5.0_typescript@6.0.3": { "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dependencies": [ "typescript" @@ -2907,8 +2491,8 @@ "prelude-ls" ] }, - "typescript-eslint@8.57.1_eslint@9.39.4__jiti@2.6.1_typescript@5.9.3_jiti@2.6.1": { - "integrity": "sha512-fLvZWf+cAGw3tqMCYzGIU6yR8K+Y9NT2z23RwOjlNFF2HwSB3KhdEFI5lSBv8tNmFkkBShSjsCjzx1vahZfISA==", + "typescript-eslint@8.61.1_eslint@10.5.0__jiti@2.7.0_typescript@6.0.3_jiti@2.7.0": { + "integrity": "sha512-V7PayAfJokV3pEHgN7/v03D1SpujhRfQtYLbLIiBfDDncdg4PAiRBfoS4cnCANK4jmAPncczi59QO3afiXUlNw==", "dependencies": [ "@typescript-eslint/eslint-plugin", "@typescript-eslint/parser", @@ -2918,12 +2502,12 @@ "typescript" ] }, - "typescript@5.9.3": { - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "typescript@6.0.3": { + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "bin": true }, - "undici-types@7.16.0": { - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==" + "undici-types@8.3.0": { + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==" }, "unified@11.0.5": { "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", @@ -2985,9 +2569,6 @@ "punycode" ] }, - "util-deprecate@1.0.2": { - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, "vfile-message@4.0.3": { "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", "dependencies": [ @@ -3002,13 +2583,13 @@ "vfile-message" ] }, - "vite@8.0.1_@types+node@24.12.0_jiti@2.6.1": { - "integrity": "sha512-wt+Z2qIhfFt85uiyRt5LPU4oVEJBXj8hZNWKeqFG4gRG/0RaRGJ7njQCwzFVjO+v4+Ipmf5CY7VdmZRAYYBPHw==", + "vite@8.0.16_@types+node@26.0.0_jiti@2.7.0": { + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", "dependencies": [ "@types/node", "jiti", "lightningcss", - "picomatch@4.0.3", + "picomatch@4.0.4", "postcss", "rolldown", "tinyglobby" @@ -3022,12 +2603,6 @@ ], "bin": true }, - "wcwidth@1.0.1": { - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dependencies": [ - "defaults" - ] - }, "which@2.0.2": { "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dependencies": [ @@ -3047,14 +2622,14 @@ "yoctocolors@2.1.2": { "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==" }, - "zod-validation-error@4.0.2_zod@4.3.6": { + "zod-validation-error@4.0.2_zod@4.4.3": { "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", "dependencies": [ "zod" ] }, - "zod@4.3.6": { - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==" + "zod@4.4.3": { + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==" }, "zwitch@2.0.4": { "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==" @@ -3064,39 +2639,39 @@ "dependencies": [ "jsr:@db/sqlite@0.13", "jsr:@oak/oak@^17.2.0", - "jsr:@panva/jose@^6.2.1", + "jsr:@panva/jose@^6.2.3", "jsr:@tajpouria/cors@^1.2.1", - "npm:marked@15", - "npm:nodemailer@^8.0.4" + "npm:marked@^18.0.5", + "npm:nodemailer@^9.0.1" ], "packageJson": { "dependencies": [ - "npm:@eslint/js@^9.39.4", - "npm:@lingui/cli@6.0.0-next.3", - "npm:@lingui/conf@6.0.0-next.3", - "npm:@lingui/core@6.0.0-next.3", - "npm:@lingui/format-po@6.0.0-next.3", - "npm:@lingui/react@6.0.0-next.3", - "npm:@lingui/swc-plugin@6.0.0-next.2", - "npm:@lingui/vite-plugin@6.0.0-next.3", - "npm:@types/node@^24.12.0", + "npm:@eslint/js@^10.0.1", + "npm:@lingui/cli@6.4.0", + "npm:@lingui/conf@6.4.0", + "npm:@lingui/core@6.4.0", + "npm:@lingui/format-po@6.4.0", + "npm:@lingui/react@6.4.0", + "npm:@lingui/swc-plugin@6.4.0", + "npm:@lingui/vite-plugin@6.4.0", + "npm:@types/node@26", "npm:@types/react-dom@^19.2.3", - "npm:@types/react@^19.2.14", - "npm:@vitejs/plugin-react-swc@^4.3.0", - "npm:eslint-plugin-react-hooks@^7.0.1", - "npm:eslint-plugin-react-refresh@~0.5.2", - "npm:eslint@^9.39.4", + "npm:@types/react@^19.2.17", + "npm:@vitejs/plugin-react-swc@^4.3.1", + "npm:eslint-plugin-react-hooks@^7.1.1", + "npm:eslint-plugin-react-refresh@~0.5.3", + "npm:eslint@^10.5.0", "npm:frimousse@0.3", - "npm:globals@^17.4.0", - "npm:jiti@^2.6.1", - "npm:react-dom@^19.2.4", + "npm:globals@^17.6.0", + "npm:jiti@^2.7.0", + "npm:react-dom@^19.2.7", "npm:react-markdown@^10.1.0", - "npm:react-router@^7.13.1", - "npm:react@^19.2.4", + "npm:react-router@^8.0.1", + "npm:react@^19.2.7", "npm:remark-gfm@^4.0.1", - "npm:typescript-eslint@^8.56.1", - "npm:typescript@~5.9.3", - "npm:vite@8" + "npm:typescript-eslint@^8.61.1", + "npm:typescript@~6.0.3", + "npm:vite@^8.0.16" ] } } diff --git a/package.json b/package.json index 8c2082b..ab60c23 100644 --- a/package.json +++ b/package.json @@ -10,33 +10,33 @@ "preview": "vite preview" }, "dependencies": { - "@lingui/cli": "6.0.0-next.3", - "@lingui/conf": "6.0.0-next.3", - "@lingui/core": "6.0.0-next.3", - "@lingui/format-po": "6.0.0-next.3", - "@lingui/react": "6.0.0-next.3", - "@types/react": "^19.2.14", - "@lingui/swc-plugin": "6.0.0-next.2", - "@vitejs/plugin-react-swc": "^4.3.0", + "@lingui/cli": "6.4.0", + "@lingui/conf": "6.4.0", + "@lingui/core": "6.4.0", + "@lingui/format-po": "6.4.0", + "@lingui/react": "6.4.0", + "@types/react": "^19.2.17", + "@lingui/swc-plugin": "6.4.0", + "@vitejs/plugin-react-swc": "^4.3.1", "frimousse": "^0.3.0", - "jiti": "^2.6.1", - "react": "^19.2.4", - "react-dom": "^19.2.4", + "jiti": "^2.7.0", + "react": "^19.2.7", + "react-dom": "^19.2.7", "react-markdown": "^10.1.0", - "react-router": "^7.13.1", + "react-router": "^8.0.1", "remark-gfm": "^4.0.1" }, "devDependencies": { - "@lingui/vite-plugin": "6.0.0-next.3", - "@eslint/js": "^9.39.4", - "@types/node": "^24.12.0", + "@lingui/vite-plugin": "6.4.0", + "@eslint/js": "^10.0.1", + "@types/node": "^26.0.0", "@types/react-dom": "^19.2.3", - "eslint": "^9.39.4", - "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-react-refresh": "^0.5.2", - "globals": "^17.4.0", - "typescript": "~5.9.3", - "typescript-eslint": "^8.56.1", - "vite": "^8.0.0" + "eslint": "^10.5.0", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.3", + "globals": "^17.6.0", + "typescript": "~6.0.3", + "typescript-eslint": "^8.61.1", + "vite": "^8.0.16" } } diff --git a/src/App.css b/src/App.css index 47aa639..3c11fa8 100644 --- a/src/App.css +++ b/src/App.css @@ -1272,6 +1272,19 @@ body.has-player .fab-new { cursor: default; } +.vote-count-clickable { + display: inline-block; + cursor: pointer; + /* Padding+matching negative margin enlarges the click target without + shifting layout — the box grows but its visual position is unchanged. */ + padding: 0.4rem; + margin: -0.4rem; +} + +.vote-count-clickable:hover { + text-decoration: underline; +} + /* ── Dump OP line ── */ .dump-op { display: flex; @@ -2176,6 +2189,63 @@ body.has-player .fab-new { background: var(--color-header-user-bg-hover); } +.user-list-popover { + /* position/top/left set inline (fixed, anchored to the trigger's viewport rect) */ + min-width: 180px; + max-height: 280px; + overflow-y: auto; + background: var(--color-surface); + border: 2px solid var(--color-border-subtle); + border-radius: 10px; + padding: 0.35rem; + display: flex; + flex-direction: column; + gap: 0.15rem; + z-index: 100; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); +} + +.user-list-popover-item { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.35rem 0.5rem; + border-radius: 7px; + text-decoration: none; + color: var(--color-text); + font-size: 0.85rem; + font-weight: 600; + transition: background 0.12s; +} + +.user-list-popover-item:hover { + background: var(--color-header-user-bg-hover); +} + +.user-list-popover-status { + margin: 0; + padding: 0.5rem 0.6rem; + font-size: 0.8rem; + color: var(--color-text-muted); +} + +.user-list-popover-load-more { + background: none; + border: none; + cursor: pointer; + padding: 0.4rem 0.6rem; + font-size: 0.8rem; + font-weight: 600; + color: var(--color-accent); + text-align: left; + font-family: inherit; +} + +.user-list-popover-load-more:disabled { + opacity: 0.5; + cursor: default; +} + /* ── Auth card ── */ .auth-card { width: 100%; @@ -3560,6 +3630,31 @@ body.has-player .fab-new { background: color-mix(in srgb, var(--color-danger) 10%, transparent); } +.comment-action-btn--liked { + color: var(--color-danger); + border-color: color-mix(in srgb, var(--color-danger) 40%, transparent); + background: color-mix(in srgb, var(--color-danger) 10%, transparent); +} + +.comment-action-btn--liked:hover { + color: var(--color-danger); + border-color: color-mix(in srgb, var(--color-danger) 60%, transparent); + background: color-mix(in srgb, var(--color-danger) 16%, transparent); +} + +.like-count-clickable { + display: inline-block; + cursor: pointer; + /* Padding+matching negative margin enlarges the click target without + shifting layout — the box grows but its visual position is unchanged. */ + padding: 0.3rem; + margin: -0.3rem; +} + +.like-count-clickable:hover { + text-decoration: underline; +} + .comment-replies { padding-left: max(0.4rem, calc(1.25rem - var(--depth, 0) * 0.1rem)); margin-left: max(0.25rem, calc(1.1rem - var(--depth, 0) * 0.09rem)); diff --git a/src/components/CommentThread.tsx b/src/components/CommentThread.tsx index be2a8a7..1963601 100644 --- a/src/components/CommentThread.tsx +++ b/src/components/CommentThread.tsx @@ -14,10 +14,12 @@ import { deserializeComment, parseAPIResponse } from "../model.ts"; import { Avatar } from "./Avatar.tsx"; import { Markdown } from "./Markdown.tsx"; import { TextEditor, type TextEditorHandle } from "./TextEditor.tsx"; +import { LikeButton } from "./LikeButton.tsx"; import { relativeTime } from "../utils/relativeTime.ts"; import { ErrorCard } from "./ErrorCard.tsx"; import { Tooltip } from "./Tooltip.tsx"; import { ConfirmModal } from "./ConfirmModal.tsx"; +import { useWS } from "../hooks/useWS.ts"; interface CommentThreadProps { dumpId: string; @@ -75,6 +77,9 @@ function CommentNode({ const replyEditorRef = useRef(null); const editEditorRef = useRef(null); + const { likeCounts, myLikes, castCommentLike, removeCommentLike } = + useWS(); + const children = tree.get(comment.id) ?? []; async function handleReply(e?: React.SubmitEvent) { @@ -285,6 +290,14 @@ function CommentNode({ ) : {comment.body}}
+ {currentUser && !editOpen && ( + {popoverOpen && ( + setPopoverOpen(false)} + fetchUrl={`${API_URL}/api/comments/${commentId}/likers`} + /> + )} + + ); +} diff --git a/src/components/UserListPopover.tsx b/src/components/UserListPopover.tsx new file mode 100644 index 0000000..597ad88 --- /dev/null +++ b/src/components/UserListPopover.tsx @@ -0,0 +1,196 @@ +import { useEffect, useRef, useState } from "react"; +import { createPortal } from "react-dom"; +import { Link } from "react-router"; +import { t } from "@lingui/core/macro"; +import { Trans } from "@lingui/react/macro"; +import { Avatar } from "./Avatar.tsx"; +import { useAuth } from "../hooks/useAuth.ts"; +import { DEFAULT_PAGE_SIZE } from "../config/api.ts"; +import { + deserializePublicUser, + type PaginatedData, + parseAPIResponse, + type PublicUser, + type RawPublicUser, +} from "../model.ts"; + +interface UserListPopoverProps { + anchorRef: React.RefObject; + onClose: () => void; + fetchUrl: string; +} + +type State = + | { status: "loading" } + | { status: "error"; error: string } + | { + status: "loaded"; + users: PublicUser[]; + page: number; + hasMore: boolean; + loadingMore: boolean; + }; + +export function UserListPopover( + { anchorRef, onClose, fetchUrl }: UserListPopoverProps, +) { + const { token } = useAuth(); + const [state, setState] = useState({ status: "loading" }); + const [rect, setRect] = useState(null); + const popoverRef = useRef(null); + + // Position via the anchor's viewport rect (mirrors Tooltip.tsx) — fixed + + // portaled to so the popover can't be clipped by a card's overflow. + useEffect(() => { + setRect(anchorRef.current?.getBoundingClientRect() ?? null); + }, [anchorRef]); + + useEffect(() => { + function onMouseDown(e: MouseEvent) { + const target = e.target as Node; + if ( + popoverRef.current?.contains(target) || + anchorRef.current?.contains(target) + ) { + return; + } + onClose(); + } + function onKeyDown(e: KeyboardEvent) { + if (e.key === "Escape") onClose(); + } + function onScroll() { + onClose(); + } + document.addEventListener("mousedown", onMouseDown); + document.addEventListener("keydown", onKeyDown); + window.addEventListener("scroll", onScroll, true); + return () => { + document.removeEventListener("mousedown", onMouseDown); + document.removeEventListener("keydown", onKeyDown); + window.removeEventListener("scroll", onScroll, true); + }; + }, [onClose, anchorRef]); + + useEffect(() => { + const controller = new AbortController(); + fetch(`${fetchUrl}?page=1&limit=${DEFAULT_PAGE_SIZE}`, { + signal: controller.signal, + headers: token ? { Authorization: `Bearer ${token}` } : {}, + }) + .then((r) => r.json()) + .then((body) => { + const apiResponse = parseAPIResponse>( + body, + ); + if (!apiResponse.success) { + setState({ status: "error", error: apiResponse.error.message }); + return; + } + setState({ + status: "loaded", + users: apiResponse.data.items.map(deserializePublicUser), + page: 1, + hasMore: apiResponse.data.hasMore, + loadingMore: false, + }); + }) + .catch(() => { + if (controller.signal.aborted) return; + setState({ status: "error", error: t`Could not load.` }); + }); + return () => controller.abort(); + }, [fetchUrl, token]); + + const handleLoadMore = () => { + if (state.status !== "loaded" || state.loadingMore) return; + const nextPage = state.page + 1; + setState({ ...state, loadingMore: true }); + fetch(`${fetchUrl}?page=${nextPage}&limit=${DEFAULT_PAGE_SIZE}`, { + headers: token ? { Authorization: `Bearer ${token}` } : {}, + }) + .then((r) => r.json()) + .then((body) => { + const apiResponse = parseAPIResponse>( + body, + ); + setState((prev) => { + if (prev.status !== "loaded") return prev; + if (!apiResponse.success) return { ...prev, loadingMore: false }; + return { + status: "loaded", + users: [ + ...prev.users, + ...apiResponse.data.items.map(deserializePublicUser), + ], + page: nextPage, + hasMore: apiResponse.data.hasMore, + loadingMore: false, + }; + }); + }); + }; + + if (!rect) return null; + + return createPortal( +
+ {state.status === "loading" && ( +

+ Loading… +

+ )} + {state.status === "error" && ( +

{state.error}

+ )} + {state.status === "loaded" && state.users.length === 0 && ( +

+ No one yet. +

+ )} + {state.status === "loaded" && state.users.length > 0 && ( + <> + {state.users.map((u) => ( + + + {u.username} + + ))} + {state.hasMore && ( + + )} + + )} +
, + document.body, + ); +} diff --git a/src/components/VoteButton.tsx b/src/components/VoteButton.tsx index 98f9279..30e9d12 100644 --- a/src/components/VoteButton.tsx +++ b/src/components/VoteButton.tsx @@ -1,3 +1,7 @@ +import { useRef, useState } from "react"; +import { API_URL } from "../config/api.ts"; +import { UserListPopover } from "./UserListPopover.tsx"; + interface VoteButtonProps { dumpId: string; count: number; @@ -10,16 +14,42 @@ interface VoteButtonProps { export function VoteButton( { dumpId, count, voted, disabled, onCast, onRemove }: VoteButtonProps, ) { + const [popoverOpen, setPopoverOpen] = useState(false); + const countRef = useRef(null); + return ( - + <> + + {popoverOpen && ( + setPopoverOpen(false)} + fetchUrl={`${API_URL}/api/dumps/${dumpId}/voters`} + /> + )} + ); } diff --git a/src/contexts/WSContext.ts b/src/contexts/WSContext.ts index 893a9b1..a71a01e 100644 --- a/src/contexts/WSContext.ts +++ b/src/contexts/WSContext.ts @@ -14,6 +14,12 @@ export interface VoteEvent { action: "cast" | "remove"; } +export interface LikeEvent { + commentId: string; + likerId: string; + action: "cast" | "remove"; +} + export interface PlaylistEvent { type: "created" | "updated" | "deleted" | "dumps_updated"; playlistId: string; @@ -39,9 +45,12 @@ export interface WSContextValue { onlineUsers: OnlineUser[]; voteCounts: Record; myVotes: Set; + likeCounts: Record; + myLikes: Set; recentDumps: Dump[]; deletedDumpIds: Set; lastVoteEvent: VoteEvent | null; + lastLikeEvent: LikeEvent | null; lastDumpEvent: Dump | null; lastPlaylistEvent: PlaylistEvent | null; deletedPlaylistIds: Set; @@ -51,6 +60,8 @@ export interface WSContextValue { lastNotification: Notification | null; castVote: (dumpId: string) => void; removeVote: (dumpId: string) => void; + castCommentLike: (commentId: string) => void; + removeCommentLike: (commentId: string) => void; injectDump: (dump: Dump) => void; clearUnreadNotifications: () => void; } @@ -61,9 +72,12 @@ export const WSContext = createContext({ onlineUsers: [], voteCounts: {}, myVotes: new Set(), + likeCounts: {}, + myLikes: new Set(), recentDumps: [], deletedDumpIds: new Set(), lastVoteEvent: null, + lastLikeEvent: null, lastDumpEvent: null, lastPlaylistEvent: null, deletedPlaylistIds: new Set(), @@ -73,6 +87,8 @@ export const WSContext = createContext({ lastNotification: null, castVote: () => {}, removeVote: () => {}, + castCommentLike: () => {}, + removeCommentLike: () => {}, injectDump: () => {}, clearUnreadNotifications: () => {}, }); diff --git a/src/contexts/WSProvider.tsx b/src/contexts/WSProvider.tsx index ea3c7a2..4eb9036 100644 --- a/src/contexts/WSProvider.tsx +++ b/src/contexts/WSProvider.tsx @@ -9,6 +9,7 @@ import { } from "react"; import { type CommentEvent, + type LikeEvent, type PlaylistEvent, type UserEvent, type VoteEvent, @@ -46,6 +47,11 @@ interface PendingVote { rollback: () => void; } +interface PendingLike { + timeout: ReturnType; + rollback: () => void; +} + // Minimal runtime check: verify the `type` field is a known string so we can // safely cast to the discriminated union and let TypeScript narrow from there. function parseWSMessage(data: string): IncomingWSMessage | null { @@ -80,9 +86,12 @@ export function WSProvider({ children }: WSProviderProps) { const [onlineUsers, setOnlineUsers] = useState([]); const [voteCounts, setVoteCounts] = useState>({}); const [myVotes, setMyVotes] = useState>(new Set()); + const [likeCounts, setLikeCounts] = useState>({}); + const [myLikes, setMyLikes] = useState>(new Set()); const [recentDumps, setRecentDumps] = useState([]); const [deletedDumpIds, setDeletedDumpIds] = useState>(new Set()); const [lastVoteEvent, setLastVoteEvent] = useState(null); + const [lastLikeEvent, setLastLikeEvent] = useState(null); const [lastDumpEvent, setLastDumpEvent] = useState(null); const [lastPlaylistEvent, setLastPlaylistEvent] = useState< PlaylistEvent | null @@ -102,6 +111,8 @@ export function WSProvider({ children }: WSProviderProps) { // Refs to avoid stale closures in event handlers const voteCountsRef = useRef(voteCounts); const myVotesRef = useRef(myVotes); + const likeCountsRef = useRef(likeCounts); + const myLikesRef = useRef(myLikes); const userIdRef = useRef(userId); // Stable ref for logout so the effect doesn't reconnect when the function // reference changes on re-renders. @@ -109,6 +120,8 @@ export function WSProvider({ children }: WSProviderProps) { useLayoutEffect(() => { voteCountsRef.current = voteCounts; myVotesRef.current = myVotes; + likeCountsRef.current = likeCounts; + myLikesRef.current = myLikes; userIdRef.current = userId; onForceLogoutRef.current = logout; }); @@ -118,6 +131,10 @@ export function WSProvider({ children }: WSProviderProps) { const pendingRef = useRef>( new Map(), ); + // Tracks pending optimistic comment likes: commentId → pending rollback handler + const pendingLikesRef = useRef>( + new Map(), + ); const clearPendingVote = useCallback((dumpId: string) => { const pending = pendingRef.current.get(dumpId); @@ -145,6 +162,32 @@ export function WSProvider({ children }: WSProviderProps) { pendingRef.current.set(dumpId, { timeout, rollback }); }, [clearPendingVote]); + const clearPendingLike = useCallback((commentId: string) => { + const pending = pendingLikesRef.current.get(commentId); + if (!pending) return; + clearTimeout(pending.timeout); + pendingLikesRef.current.delete(commentId); + }, []); + + const clearAllPendingLikes = useCallback(() => { + for (const pending of pendingLikesRef.current.values()) { + clearTimeout(pending.timeout); + } + pendingLikesRef.current.clear(); + }, []); + + const schedulePendingLike = useCallback(( + commentId: string, + rollback: () => void, + ) => { + clearPendingLike(commentId); + const timeout = setTimeout(() => { + pendingLikesRef.current.delete(commentId); + rollback(); + }, ACK_TIMEOUT); + pendingLikesRef.current.set(commentId, { timeout, rollback }); + }, [clearPendingLike]); + useEffect(() => { let closed = false; let backoff = 500; @@ -195,10 +238,12 @@ export function WSProvider({ children }: WSProviderProps) { backoff = 500; // reset backoff on successful connect setOnlineUsers(msg.users); setMyVotes(new Set(msg.myVotes)); + setMyLikes(new Set(msg.myCommentLikes)); setUnreadNotificationCount(msg.unreadNotificationCount); // welcome provides authoritative server state — cancel any // in-flight revert timers, they are now superseded. clearAllPendingVotes(); + clearAllPendingLikes(); break; case "presence_update": @@ -223,6 +268,24 @@ export function WSProvider({ children }: WSProviderProps) { break; } + case "comment_likes_update": { + const { commentId, likeCount, likerId, action } = msg; + setLikeCounts((prev) => ({ ...prev, [commentId]: likeCount })); + setLastLikeEvent({ commentId, likerId, action }); + // Keep myLikes in sync across tabs: if this like event belongs to + // the current user (from another tab), update myLikes accordingly. + if (likerId === userIdRef.current) { + clearPendingLike(commentId); + setMyLikes((prev) => { + const next = new Set(prev); + if (action === "cast") next.add(commentId); + else next.delete(commentId); + return next; + }); + } + break; + } + case "dump_created": { const dump = deserializeDump(msg.dump); setRecentDumps((prev) => [dump, ...prev]); @@ -269,6 +332,21 @@ export function WSProvider({ children }: WSProviderProps) { break; } + case "comment_like_ack": { + const { commentId, action, likeCount } = msg; + clearPendingLike(commentId); + // Reconcile with authoritative count + setLikeCounts((prev) => ({ ...prev, [commentId]: likeCount })); + // Confirm like state + setMyLikes((prev) => { + const next = new Set(prev); + if (action === "cast") next.add(commentId); + else next.delete(commentId); + return next; + }); + break; + } + case "playlist_created": case "playlist_updated": { const playlist = deserializePlaylist(msg.playlist); @@ -373,6 +451,7 @@ export function WSProvider({ children }: WSProviderProps) { connect(); const pending = pendingRef.current; + const pendingLikes = pendingLikesRef.current; return () => { closed = true; if (reconnectTimer) clearTimeout(reconnectTimer); @@ -383,8 +462,18 @@ export function WSProvider({ children }: WSProviderProps) { clearTimeout(pendingVote.timeout); } pending.clear(); + for (const pendingLike of pendingLikes.values()) { + clearTimeout(pendingLike.timeout); + } + pendingLikes.clear(); }; - }, [clearAllPendingVotes, clearPendingVote, token]); + }, [ + clearAllPendingVotes, + clearPendingVote, + clearAllPendingLikes, + clearPendingLike, + token, + ]); const castVote = useCallback((dumpId: string) => { // Optimistic update @@ -455,6 +544,78 @@ export function WSProvider({ children }: WSProviderProps) { // If socket is not OPEN, the revert timer will handle cleanup after ACK_TIMEOUT }, [schedulePendingVote]); + const castCommentLike = useCallback((commentId: string) => { + // Optimistic update + const prevCount = likeCountsRef.current[commentId] ?? 0; + const prevLiked = myLikesRef.current.has(commentId); + if (prevLiked) return; // already liked + + setMyLikes((prev) => { + const n = new Set(prev); + n.add(commentId); + return n; + }); + setLikeCounts((prev) => ({ ...prev, [commentId]: prevCount + 1 })); + + // Schedule revert if no authoritative confirmation arrives. + schedulePendingLike(commentId, () => { + setMyLikes((prev) => { + const n = new Set(prev); + n.delete(commentId); + return n; + }); + setLikeCounts((prev) => ({ ...prev, [commentId]: prevCount })); + }); + + if (socketRef.current?.readyState === WebSocket.OPEN) { + socketRef.current.send( + JSON.stringify( + { type: "comment_like_cast", commentId } satisfies OutgoingWSMessage, + ), + ); + } + // If socket is not OPEN, the revert timer will handle cleanup after ACK_TIMEOUT + }, [schedulePendingLike]); + + const removeCommentLike = useCallback((commentId: string) => { + // Optimistic update + const prevCount = likeCountsRef.current[commentId] ?? 0; + const prevLiked = myLikesRef.current.has(commentId); + if (!prevLiked) return; // not liked + + setMyLikes((prev) => { + const n = new Set(prev); + n.delete(commentId); + return n; + }); + setLikeCounts((prev) => ({ + ...prev, + [commentId]: Math.max(0, prevCount - 1), + })); + + // Schedule revert if no authoritative confirmation arrives. + schedulePendingLike(commentId, () => { + setMyLikes((prev) => { + const n = new Set(prev); + n.add(commentId); + return n; + }); + setLikeCounts((prev) => ({ ...prev, [commentId]: prevCount })); + }); + + if (socketRef.current?.readyState === WebSocket.OPEN) { + socketRef.current.send( + JSON.stringify( + { + type: "comment_like_remove", + commentId, + } satisfies OutgoingWSMessage, + ), + ); + } + // If socket is not OPEN, the revert timer will handle cleanup after ACK_TIMEOUT + }, [schedulePendingLike]); + const injectDump = useCallback((dump: Dump) => { setRecentDumps((prev) => { if (prev.some((d) => d.id === dump.id)) return prev; @@ -472,9 +633,12 @@ export function WSProvider({ children }: WSProviderProps) { onlineUsers, voteCounts, myVotes, + likeCounts, + myLikes, recentDumps, deletedDumpIds, lastVoteEvent, + lastLikeEvent, lastDumpEvent, lastPlaylistEvent, deletedPlaylistIds, @@ -484,6 +648,8 @@ export function WSProvider({ children }: WSProviderProps) { lastNotification, castVote, removeVote, + castCommentLike, + removeCommentLike, injectDump, clearUnreadNotifications, }), [ @@ -492,9 +658,12 @@ export function WSProvider({ children }: WSProviderProps) { onlineUsers, voteCounts, myVotes, + likeCounts, + myLikes, recentDumps, deletedDumpIds, lastVoteEvent, + lastLikeEvent, lastDumpEvent, lastPlaylistEvent, deletedPlaylistIds, @@ -504,6 +673,8 @@ export function WSProvider({ children }: WSProviderProps) { lastNotification, castVote, removeVote, + castCommentLike, + removeCommentLike, injectDump, clearUnreadNotifications, ]); diff --git a/src/model.ts b/src/model.ts index 52b647f..7d00f77 100644 --- a/src/model.ts +++ b/src/model.ts @@ -162,6 +162,7 @@ export interface Comment { createdAt: Date; updatedAt?: Date; deleted: boolean; + likeCount: number; authorUsername: string; authorAvatarMime?: string; } @@ -249,7 +250,8 @@ export type NotificationType = | "playlist_dump_added" | "dump_upvoted" | "user_mentioned" - | "dump_commented"; + | "dump_commented" + | "comment_liked"; export interface PlaylistFollowedData { followerId: string; @@ -301,6 +303,14 @@ export interface DumpCommentedData { dumpTitle: string; } +export interface CommentLikedData { + likerId: string; + likerUsername: string; + commentId: string; + dumpId: string; + dumpTitle: string; +} + export type NotificationData = | PlaylistFollowedData | UserFollowedData @@ -308,7 +318,8 @@ export type NotificationData = | PlaylistDumpAddedData | DumpUpvotedData | UserMentionedData - | DumpCommentedData; + | DumpCommentedData + | CommentLikedData; export interface Notification { id: string; @@ -350,6 +361,7 @@ export interface WSWelcomeMessage { type: "welcome"; users: OnlineUser[]; myVotes: string[]; + myCommentLikes: string[]; unreadNotificationCount: number; } export interface WSPresenceUpdateMessage { @@ -369,6 +381,19 @@ export interface WSVoteAckMessage { action: "cast" | "remove"; voteCount: number; } +export interface WSCommentLikesUpdateMessage { + type: "comment_likes_update"; + commentId: string; + likeCount: number; + likerId: string; + action: "cast" | "remove"; +} +export interface WSCommentLikeAckMessage { + type: "comment_like_ack"; + commentId: string; + action: "cast" | "remove"; + likeCount: number; +} export interface WSDumpCreatedMessage { type: "dump_created"; dump: RawDump; @@ -435,6 +460,8 @@ export type IncomingWSMessage = | WSPresenceUpdateMessage | WSVotesUpdateMessage | WSVoteAckMessage + | WSCommentLikesUpdateMessage + | WSCommentLikeAckMessage | WSDumpCreatedMessage | WSDumpUpdatedMessage | WSDumpDeletedMessage @@ -465,11 +492,21 @@ export interface WSVoteRemoveMessage { type: "vote_remove"; dumpId: string; } +export interface WSCommentLikeCastMessage { + type: "comment_like_cast"; + commentId: string; +} +export interface WSCommentLikeRemoveMessage { + type: "comment_like_remove"; + commentId: string; +} export type OutgoingWSMessage = | WSPongMessage | WSVoteCastMessage - | WSVoteRemoveMessage; + | WSVoteRemoveMessage + | WSCommentLikeCastMessage + | WSCommentLikeRemoveMessage; /** * Follows diff --git a/src/pages/Notifications.tsx b/src/pages/Notifications.tsx index c873a2c..291aeff 100644 --- a/src/pages/Notifications.tsx +++ b/src/pages/Notifications.tsx @@ -10,6 +10,7 @@ import { ErrorCard } from "../components/ErrorCard.tsx"; import { Tooltip } from "../components/Tooltip.tsx"; import { useWS } from "../hooks/useWS.ts"; import type { + CommentLikedData, DumpCommentedData, DumpUpvotedData, Notification, @@ -43,7 +44,8 @@ type NotifIconKind = | "dump" | "playlist" | "mention" - | "comment"; + | "comment" + | "like"; function notifIconKind(type: Notification["type"]): NotifIconKind { switch (type) { @@ -61,6 +63,8 @@ function notifIconKind(type: Notification["type"]): NotifIconKind { return "mention"; case "dump_commented": return "comment"; + case "comment_liked": + return "like"; } } @@ -76,6 +80,12 @@ const FollowSvg = () => ( ); +const HeartSvg = () => ( + + + +); + function NotifIcon({ type }: { type: Notification["type"] }) { const kind = notifIconKind(type); const glyphs: Record = { @@ -85,6 +95,7 @@ function NotifIcon({ type }: { type: Notification["type"] }) { playlist: "📜", mention: "@", comment: "💬", + like: , }; return ( @@ -110,6 +121,10 @@ function notificationLink(n: Notification): string { return `/dumps/${(data as DumpCommentedData).dumpId}#comment-${ (data as DumpCommentedData).commentId }`; + case "comment_liked": + return `/dumps/${(data as CommentLikedData).dumpId}#comment-${ + (data as CommentLikedData).commentId + }`; case "user_mentioned": { const d = data as UserMentionedData; if (d.contextType === "comment") { @@ -183,6 +198,16 @@ function notificationContent(n: Notification): React.ReactNode { ); } + case "comment_liked": { + const d = data as CommentLikedData; + return ( + + {d.likerUsername} + {" liked your comment on "} + {d.dumpTitle} + + ); + } case "user_mentioned": { const d = data as UserMentionedData; const where = d.contextTitle ||