From 27d4b4c0ea17e50cf1e8c40a403bd93e7140d61b Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Sat, 22 Feb 2025 15:41:28 +0100 Subject: [PATCH 1/3] feat(twas): refracto to use intl api Co-Authored-By: Leo Kettmeir --- frontend/deno.json | 1 - frontend/deno.lock | 9 +---- frontend/islands/new.tsx | 4 +- .../account/(_components)/AccountLayout.tsx | 4 +- frontend/routes/account/tickets.tsx | 6 +-- frontend/routes/account/tokens/index.tsx | 14 +++---- frontend/routes/admin/audit.tsx | 4 +- frontend/routes/admin/packages.tsx | 8 ++-- frontend/routes/admin/publishingTasks.tsx | 14 +++++-- frontend/routes/admin/scopes/index.tsx | 4 +- frontend/routes/admin/tickets.tsx | 8 ++-- frontend/routes/admin/users.tsx | 4 +- .../package/(_components)/PackageHeader.tsx | 4 +- frontend/routes/package/og.ts | 4 +- frontend/routes/package/versions.tsx | 7 ++-- frontend/routes/status.tsx | 4 +- frontend/routes/ticket.tsx | 4 +- frontend/utils/timeAgo.ts | 37 +++++++++++++++++++ 18 files changed, 89 insertions(+), 51 deletions(-) create mode 100644 frontend/utils/timeAgo.ts diff --git a/frontend/deno.json b/frontend/deno.json index 8eb41dca..055607e2 100644 --- a/frontend/deno.json +++ b/frontend/deno.json @@ -28,7 +28,6 @@ "@std/front-matter": "jsr:@std/front-matter@1", "@std/semver": "jsr:@std/semver@1", - "twas": "npm:twas@^2.1.3", "$imagescript": "https://deno.land/x/imagescript@1.3.0/mod.ts", "@deno/gfm": "jsr:@deno/gfm@0.11", diff --git a/frontend/deno.lock b/frontend/deno.lock index 2c572e06..772bc078 100644 --- a/frontend/deno.lock +++ b/frontend/deno.lock @@ -70,8 +70,7 @@ "npm:prismjs@^1.29.0": "1.29.0", "npm:sanitize-html@^2.13.0": "2.16.0", "npm:tailwindcss@3.4": "3.4.14_postcss@8.4.47", - "npm:tailwindcss@^3.4.1": "3.4.14_postcss@8.4.47", - "npm:twas@^2.1.3": "2.1.3" + "npm:tailwindcss@^3.4.1": "3.4.14_postcss@8.4.47" }, "jsr": { "@deno/gfm@0.11.0": { @@ -1740,9 +1739,6 @@ "ts-interface-checker@0.1.13": { "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" }, - "twas@2.1.3": { - "integrity": "sha512-4Spnweu5OEBG9ZZIfabEh0js2x1p+34QsLLz+vHjER/nQX0L/+b7H6gelZbT+Ewi2KVNHcBy5gGhib9DeVAqpA==" - }, "undici-types@5.26.5": { "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, @@ -1959,8 +1955,7 @@ "npm:preact-render-to-string@6.3.1", "npm:preact@10", "npm:prismjs@^1.29.0", - "npm:tailwindcss@3.4", - "npm:twas@^2.1.3" + "npm:tailwindcss@3.4" ] } } diff --git a/frontend/islands/new.tsx b/frontend/islands/new.tsx index 1f3cacbb..668ced91 100644 --- a/frontend/islands/new.tsx +++ b/frontend/islands/new.tsx @@ -8,8 +8,8 @@ import { import { Package, Scope, User } from "../utils/api_types.ts"; import { api, path } from "../utils/api.ts"; import { ComponentChildren } from "preact"; -import twas from "twas"; import { TicketModal } from "./TicketModal.tsx"; +import { timeAgo } from "../utils/timeAgo.ts"; interface IconColorProps { done: Signal; @@ -463,7 +463,7 @@ export function CreatePackage({ scope, name, pkg, fromCli }: {

{pkg.value.description || No description}

- Created {twas(new Date(pkg.value.createdAt).getTime())}. + Created {timeAgo(pkg.value.createdAt)}.bc4 (feat(twas): refracto to use intl api)

{fromCli && (

diff --git a/frontend/routes/account/(_components)/AccountLayout.tsx b/frontend/routes/account/(_components)/AccountLayout.tsx index 385e4159..4e1611bb 100644 --- a/frontend/routes/account/(_components)/AccountLayout.tsx +++ b/frontend/routes/account/(_components)/AccountLayout.tsx @@ -1,6 +1,6 @@ // Copyright 2024 the JSR authors. All rights reserved. MIT license. import { ComponentChildren } from "preact"; -import twas from "twas"; +import { timeAgo } from "../../../utils/timeAgo.ts"; import { AccountNav, AccountNavTab } from "./AccountNav.tsx"; import { FullUser, User } from "../../../utils/api_types.ts"; import { GitHubUserLink } from "../../../islands/GithubUserLink.tsx"; @@ -25,7 +25,7 @@ export function AccountLayout({ user, active, children }: AccountLayoutProps) { {user.name}

- Created account {twas(new Date(user.createdAt).getTime())} + Created account {timeAgo(new Date(user.createdAt))}

diff --git a/frontend/routes/account/tickets.tsx b/frontend/routes/account/tickets.tsx index 95b83a6b..963793f1 100644 --- a/frontend/routes/account/tickets.tsx +++ b/frontend/routes/account/tickets.tsx @@ -5,9 +5,9 @@ import { define } from "../../util.ts"; import { Table, TableData, TableRow } from "../../components/Table.tsx"; import { Ticket } from "../../utils/api_types.ts"; import { path } from "../../utils/api.ts"; -import twas from "twas"; import { TbCheck, TbClock } from "tb-icons"; import { TicketTitle } from "../../components/TicketTitle.tsx"; +import { timeAgo } from "../../utils/timeAgo.ts"; export default define.page(function AccountInvitesPage({ data, @@ -56,12 +56,12 @@ export default define.page(function AccountInvitesPage({ - {twas(new Date(ticket.createdAt).getTime())} + {timeAgo(ticket.createdAt)} - {twas(new Date(ticket.updatedAt).getTime())} + {timeAgo(ticket.updatedAt)} view diff --git a/frontend/routes/account/tokens/index.tsx b/frontend/routes/account/tokens/index.tsx index 5b219c57..a52ce09e 100644 --- a/frontend/routes/account/tokens/index.tsx +++ b/frontend/routes/account/tokens/index.tsx @@ -5,7 +5,7 @@ import { define } from "../../../util.ts"; import { path } from "../../../utils/api.ts"; import { Token } from "../../../utils/api_types.ts"; import { AccountLayout } from "../(_components)/AccountLayout.tsx"; -import twas from "twas"; +import { timeAgo } from "../../../utils/timeAgo.ts"; import { RevokeToken } from "./(_islands)/RevokeToken.tsx"; import TbPlus from "tb-icons/TbPlus"; @@ -105,7 +105,7 @@ function PersonalTokenRow({ token }: { token: Token }) { Active {expiresAt === null ? "forever" : `– expires ${ - twas(new Date().getTime(), expiresAt.getTime()).replace( + timeAgo(expiresAt).replace( "ago", "from now", ) @@ -114,12 +114,12 @@ function PersonalTokenRow({ token }: { token: Token }) { ) : ( - Inactive - expired {twas(expiresAt.getTime())} + Inactive - expired {timeAgo(expiresAt)} )}

- Created {twas(new Date(token.createdAt).getTime())} + Created {timeAgo(new Date(token.createdAt))}

@@ -160,7 +160,7 @@ function SessionRow({ token }: { token: Token }) { Active {expiresAt === null ? "forever" : `– expires ${ - twas(new Date().getTime(), expiresAt.getTime()).replace( + timeAgo(expiresAt).replace( "ago", "from now", ) @@ -169,7 +169,7 @@ function SessionRow({ token }: { token: Token }) { ) : ( - Inactive - expired {twas(expiresAt.getTime())} + Inactive - expired {timeAgo(expiresAt)} )} @@ -178,7 +178,7 @@ function SessionRow({ token }: { token: Token }) {

- Created {twas(new Date(token.createdAt).getTime())} + Created {timeAgo(new Date(token.createdAt))}

diff --git a/frontend/routes/admin/audit.tsx b/frontend/routes/admin/audit.tsx index 6493fb50..a64080f8 100644 --- a/frontend/routes/admin/audit.tsx +++ b/frontend/routes/admin/audit.tsx @@ -5,7 +5,7 @@ import { AuditLog, List } from "../../utils/api_types.ts"; import { AdminNav } from "./(_components)/AdminNav.tsx"; import { AuditURLQuerySearch } from "./(_islands)/AuditURLQuerySearch.tsx"; import { define } from "../../util.ts"; -import twas from "twas"; +import { timeAgo } from "../../utils/timeAgo.ts"; export default define.page(function Users({ data, url }) { return ( @@ -60,7 +60,7 @@ export default define.page(function Users({ data, url }) { )} align="right" > - {twas(new Date(log.createdAt).getTime())} + {timeAgo(log.createdAt)} ))} diff --git a/frontend/routes/admin/packages.tsx b/frontend/routes/admin/packages.tsx index f60be40d..4f76ad96 100644 --- a/frontend/routes/admin/packages.tsx +++ b/frontend/routes/admin/packages.tsx @@ -5,7 +5,7 @@ import { List, Package } from "../../utils/api_types.ts"; import { AdminNav } from "./(_components)/AdminNav.tsx"; import { URLQuerySearch } from "./(_components)/URLQuerySearch.tsx"; import { define } from "../../util.ts"; -import twas from "twas"; +import { timeAgo } from "../../utils/timeAgo.ts"; import { CopyButton } from "./(_islands)/CopyButton.tsx"; export default define.page(function Packages({ data, url }) { @@ -82,19 +82,19 @@ export default define.page(function Packages({ data, url }) { ? new Date(pkg.whenFeatured).toISOString().slice(0, 10) : ""} > - {pkg.whenFeatured && twas(new Date(pkg.whenFeatured).getTime())} + {pkg.whenFeatured && timeAgo(pkg.whenFeatured)} - {twas(new Date(pkg.updatedAt).getTime())} + {timeAgo(pkg.updatedAt)} - {twas(new Date(pkg.createdAt).getTime())} + {timeAgo(pkg.createdAt)} ))} diff --git a/frontend/routes/admin/publishingTasks.tsx b/frontend/routes/admin/publishingTasks.tsx index efdd7c30..5c2bc7c6 100644 --- a/frontend/routes/admin/publishingTasks.tsx +++ b/frontend/routes/admin/publishingTasks.tsx @@ -5,7 +5,7 @@ import { AdminNav } from "./(_components)/AdminNav.tsx"; import { path } from "../../utils/api.ts"; import { List, PublishingTask } from "../../utils/api_types.ts"; import { URLQuerySearch } from "./(_components)/URLQuerySearch.tsx"; -import twas from "twas"; +import { timeAgo } from "../../utils/timeAgo.ts"; import PublishingTaskRequeue from "../../islands/PublishingTaskRequeue.tsx"; import { CopyButton } from "./(_islands)/CopyButton.tsx"; @@ -110,6 +110,14 @@ export default define.page(function PublishingTasks({ {publishingTask.packageVersion} + + {timeAgo(publishingTask.createdAt)} + (function PublishingTasks({ )} align="right" > - {twas(new Date(publishingTask.updatedAt).getTime())} + {timeAgo(publishingTask.updatedAt)} (function PublishingTasks({ )} align="right" > - {twas(new Date(publishingTask.createdAt).getTime())} + {timeAgo(publishingTask.createdAt)} diff --git a/frontend/routes/admin/scopes/index.tsx b/frontend/routes/admin/scopes/index.tsx index 271fe4b7..74b30fd2 100644 --- a/frontend/routes/admin/scopes/index.tsx +++ b/frontend/routes/admin/scopes/index.tsx @@ -6,9 +6,9 @@ import { AdminNav } from "../(_components)/AdminNav.tsx"; import { URLQuerySearch } from "../(_components)/URLQuerySearch.tsx"; import { define } from "../../../util.ts"; import TbArrowRight from "tb-icons/TbArrowRight"; -import twas from "twas"; import { CopyButton } from "../(_islands)/CopyButton.tsx"; import { EditModal } from "../(_islands)/EditModal.tsx"; +import { timeAgo } from "../../../utils/timeAgo.ts"; export default define.page(function Scopes({ data, url }) { return ( @@ -85,7 +85,7 @@ export default define.page(function Scopes({ data, url }) { title={new Date(scope.createdAt).toISOString().slice(0, 10)} align="right" > - {twas(new Date(scope.createdAt).getTime())} + {timeAgo(scope.createdAt)} (function Tickets({ data, @@ -88,7 +88,7 @@ export default define.page(function Tickets({ )} align="right" > - {twas(new Date(ticket.updatedAt).getTime())} + {timeAgo(ticket.updatedAt)} (function Tickets({ )} align="right" > - {twas(new Date(ticket.createdAt).getTime())} + {timeAgo(ticket.createdAt)} - + view diff --git a/frontend/routes/admin/users.tsx b/frontend/routes/admin/users.tsx index 4962459d..62c6cfc9 100644 --- a/frontend/routes/admin/users.tsx +++ b/frontend/routes/admin/users.tsx @@ -5,9 +5,9 @@ import { FullUser, List } from "../../utils/api_types.ts"; import { AdminNav } from "./(_components)/AdminNav.tsx"; import { URLQuerySearch } from "./(_components)/URLQuerySearch.tsx"; import { define } from "../../util.ts"; -import twas from "twas"; import { CopyButton } from "./(_islands)/CopyButton.tsx"; import { EditModal } from "./(_islands)/EditModal.tsx"; +import { timeAgo } from "../../utils/timeAgo.ts"; export default define.page(function Users({ data, url }) { return ( @@ -62,7 +62,7 @@ export default define.page(function Users({ data, url }) { title={new Date(user.createdAt).toISOString().slice(0, 10)} align="right" > - {twas(new Date(user.createdAt).getTime())} + {timeAgo(user.createdAt)} {`${ - twas(new Date(selectedVersion.createdAt).getTime()) + timeAgo(new Date(selectedVersion.createdAt)) } (${selectedVersion.version})`} diff --git a/frontend/routes/package/og.ts b/frontend/routes/package/og.ts index ce84177f..bdc61a54 100644 --- a/frontend/routes/package/og.ts +++ b/frontend/routes/package/og.ts @@ -2,7 +2,7 @@ import { HttpError, RouteConfig } from "fresh"; import { Image } from "$imagescript"; -import twas from "twas"; +import { timeAgo } from "../../utils/timeAgo.ts"; import { packageDataWithVersion } from "../../utils/data.ts"; import { define } from "../../util.ts"; @@ -263,7 +263,7 @@ export const handler = define.handlers({ const publishDateText = Image.renderText( dmmonoFont, 25, - twas(new Date(selectedVersion.createdAt).getTime()), + timeAgo(new Date(selectedVersion.createdAt)), COLOR_GRAY, ); const result = new Image( diff --git a/frontend/routes/package/versions.tsx b/frontend/routes/package/versions.tsx index da4a9d5a..7b40f362 100644 --- a/frontend/routes/package/versions.tsx +++ b/frontend/routes/package/versions.tsx @@ -7,7 +7,7 @@ import type { } from "../../utils/api_types.ts"; import { define } from "../../util.ts"; import { compare, equals, format, lessThan, parse, SemVer } from "@std/semver"; -import twas from "twas"; +import { timeAgo } from "../../utils/timeAgo.ts"; import { packageData } from "../../utils/data.ts"; import { PackageHeader } from "./(_components)/PackageHeader.tsx"; import { PackageNav, Params } from "./(_components)/PackageNav.tsx"; @@ -250,7 +250,7 @@ function Version({ {" "} )} - {twas(new Date(version.createdAt).getTime())} + {timeAgo(new Date(version.createdAt))} )} @@ -292,8 +292,7 @@ function Version({ : } {ordinalNumber(tasks.length - i)} publishing attempt{" "} - {statusVerb[task.status]}{" "} - {twas(new Date(task.updatedAt).getTime())} + {statusVerb[task.status]} {timeAgo(task.updatedAt)} Details diff --git a/frontend/routes/status.tsx b/frontend/routes/status.tsx index e2ec5d15..02e815d4 100644 --- a/frontend/routes/status.tsx +++ b/frontend/routes/status.tsx @@ -9,7 +9,7 @@ import { path } from "../utils/api.ts"; import { packageData } from "../utils/data.ts"; import { PackageHeader } from "./package/(_components)/PackageHeader.tsx"; import { PackageNav } from "./package/(_components)/PackageNav.tsx"; -import twas from "twas"; +import { timeAgo } from "../utils/timeAgo.ts"; import PublishingTaskRequeue from "../islands/PublishingTaskRequeue.tsx"; import { TbAlertCircle, TbCheck, TbClockHour3 } from "tb-icons"; import { scopeIAM } from "../utils/iam.ts"; @@ -50,7 +50,7 @@ export default define.page(function PackageListPage({

Created:{" "} - {twas(new Date(data.publishingTask.createdAt).getTime())} + {timeAgo(new Date(data.publishingTask.createdAt))}

{data.publishingTask.user && (

diff --git a/frontend/routes/ticket.tsx b/frontend/routes/ticket.tsx index ac314627..eadfa6bb 100644 --- a/frontend/routes/ticket.tsx +++ b/frontend/routes/ticket.tsx @@ -3,10 +3,10 @@ import { HttpError, RouteConfig } from "fresh"; import { define } from "../util.ts"; import type { Ticket, TicketKind } from "../utils/api_types.ts"; import { path } from "../utils/api.ts"; -import twas from "twas"; import { TicketMessageInput } from "../islands/TicketMessageInput.tsx"; import { TbArrowLeft, TbCheck, TbClock } from "tb-icons"; import { TicketTitle } from "../components/TicketTitle.tsx"; +import { timeAgo } from "../utils/timeAgo.ts"; export default define.page(function Ticket({ data, @@ -94,7 +94,7 @@ export default define.page(function Ticket({

- {twas(new Date(message.createdAt).getTime())} + {timeAgo(message.createdAt)}
diff --git a/frontend/utils/timeAgo.ts b/frontend/utils/timeAgo.ts
new file mode 100644
index 00000000..f7fe5c6a
--- /dev/null
+++ b/frontend/utils/timeAgo.ts
@@ -0,0 +1,37 @@
+// Copyright 2024 the JSR authors. All rights reserved. MIT license.
+export function timeAgo(date: Date | string): string {
+  const now = new Date();
+  const past = new Date(date);
+  const diff = Math.abs(now.getTime() - past.getTime());
+
+  const duration = {
+    years: Math.floor(diff / (1000 * 60 * 60 * 24 * 365)),
+    months: Math.floor(
+      (diff % (1000 * 60 * 60 * 24 * 365)) / (1000 * 60 * 60 * 24 * 30),
+    ),
+    days: Math.floor(
+      (diff % (1000 * 60 * 60 * 24 * 30)) / (1000 * 60 * 60 * 24),
+    ),
+    hours: Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)),
+    minutes: Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)),
+    seconds: Math.floor((diff % (1000 * 60)) / 1000),
+  };
+
+  // Force english because JSR is an English-only project
+  // @ts-ignore - TS doesn't know about this API yet
+  const formatter = new Intl.DurationFormat("en", { style: "long" });
+
+  if (duration.years >= 1) {
+    return formatter.format({ years: duration.years }) + " ago";
+  } else if (duration.months >= 1) {
+    return formatter.format({ months: duration.months }) + " ago";
+  } else if (duration.days >= 1) {
+    return formatter.format({ days: duration.days }) + " ago";
+  } else if (duration.hours >= 1) {
+    return formatter.format({ hours: duration.hours }) + " ago";
+  } else if (duration.minutes >= 1) {
+    return formatter.format({ minutes: duration.minutes }) + " ago";
+  } else {
+    return formatter.format({ seconds: duration.seconds }) + " ago";
+  }
+}

From b8f58b5a0bd3fc469c32cf6ad2f59bd011e91e1f Mon Sep 17 00:00:00 2001
From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com>
Date: Fri, 25 Apr 2025 18:35:10 +0200
Subject: [PATCH 2/3] fix

---
 deno.lock                         | 177 +++++++++-
 frontend/deno.json                |   1 +
 frontend/deno.lock                | 563 +++++++++++++++---------------
 frontend/islands/new.tsx          |   4 +-
 frontend/routes/admin/tickets.tsx |   2 +-
 frontend/utils/timeAgo.ts         |  49 +--
 6 files changed, 473 insertions(+), 323 deletions(-)

diff --git a/deno.lock b/deno.lock
index 2a021e66..e31adbb8 100644
--- a/deno.lock
+++ b/deno.lock
@@ -1,11 +1,186 @@
 {
   "version": "4",
   "specifiers": {
-    "npm:github-slugger@2": "2.0.0"
+    "jsr:@deno/gfm@0.10": "0.10.0",
+    "jsr:@denosaurs/emoji@0.3": "0.3.1",
+    "jsr:@std/async@^1.0.8": "1.0.10",
+    "jsr:@std/collections@^1.0.9": "1.0.10",
+    "jsr:@std/front-matter@^1.0.5": "1.0.7",
+    "jsr:@std/path@^1.0.8": "1.0.8",
+    "jsr:@std/toml@^1.0.2": "1.0.2",
+    "jsr:@std/yaml@^1.0.5": "1.0.5",
+    "npm:github-slugger@2": "2.0.0",
+    "npm:he@^1.2.0": "1.2.0",
+    "npm:katex@0.16": "0.16.22",
+    "npm:marked-alert@2": "2.1.2_marked@12.0.2",
+    "npm:marked-footnote@^1.2.0": "1.2.4_marked@12.0.2",
+    "npm:marked-gfm-heading-id@^3.1.0": "3.2.0_marked@12.0.2",
+    "npm:marked@12": "12.0.2",
+    "npm:prismjs@^1.29.0": "1.30.0",
+    "npm:sanitize-html@^2.13.0": "2.16.0"
+  },
+  "jsr": {
+    "@deno/gfm@0.10.0": {
+      "integrity": "51708205e3559a4aeb6afb29d07c5bfafe7941f91bb360351ef6621de9a39527",
+      "dependencies": [
+        "jsr:@denosaurs/emoji",
+        "npm:github-slugger",
+        "npm:he",
+        "npm:katex",
+        "npm:marked",
+        "npm:marked-alert",
+        "npm:marked-footnote",
+        "npm:marked-gfm-heading-id",
+        "npm:prismjs",
+        "npm:sanitize-html"
+      ]
+    },
+    "@denosaurs/emoji@0.3.1": {
+      "integrity": "b0aed5f55dec99e83da7c9637fe0a36d1d6252b7c99deaaa3fc5dea3fcf3da8b"
+    },
+    "@std/async@1.0.10": {
+      "integrity": "2ff1b1c7d33d1416159989b0f69e59ec7ee8cb58510df01e454def2108b3dbec"
+    },
+    "@std/collections@1.0.10": {
+      "integrity": "903af106a3d92970d74e20f7ebff77d9658af9bef4403f1dc42a7801c0575899"
+    },
+    "@std/front-matter@1.0.7": {
+      "integrity": "a9e577403b3c0ef6a6836b54bd2034db49be765d99807dd629f2c4466d345c3e",
+      "dependencies": [
+        "jsr:@std/toml",
+        "jsr:@std/yaml"
+      ]
+    },
+    "@std/path@1.0.8": {
+      "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be"
+    },
+    "@std/toml@1.0.2": {
+      "integrity": "5892ba489c5b512265a384238a8fe8dddbbb9498b4b210ef1b9f0336a423a39b",
+      "dependencies": [
+        "jsr:@std/collections"
+      ]
+    },
+    "@std/yaml@1.0.5": {
+      "integrity": "71ba3d334305ee2149391931508b2c293a8490f94a337eef3a09cade1a2a2742"
+    }
   },
   "npm": {
+    "commander@8.3.0": {
+      "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="
+    },
+    "deepmerge@4.3.1": {
+      "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="
+    },
+    "dom-serializer@2.0.0": {
+      "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
+      "dependencies": [
+        "domelementtype",
+        "domhandler",
+        "entities"
+      ]
+    },
+    "domelementtype@2.3.0": {
+      "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="
+    },
+    "domhandler@5.0.3": {
+      "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
+      "dependencies": [
+        "domelementtype"
+      ]
+    },
+    "domutils@3.2.2": {
+      "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
+      "dependencies": [
+        "dom-serializer",
+        "domelementtype",
+        "domhandler"
+      ]
+    },
+    "entities@4.5.0": {
+      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="
+    },
+    "escape-string-regexp@4.0.0": {
+      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
+    },
     "github-slugger@2.0.0": {
       "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="
+    },
+    "he@1.2.0": {
+      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
+    },
+    "htmlparser2@8.0.2": {
+      "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
+      "dependencies": [
+        "domelementtype",
+        "domhandler",
+        "domutils",
+        "entities"
+      ]
+    },
+    "is-plain-object@5.0.0": {
+      "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
+    },
+    "katex@0.16.22": {
+      "integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==",
+      "dependencies": [
+        "commander"
+      ]
+    },
+    "marked-alert@2.1.2_marked@12.0.2": {
+      "integrity": "sha512-EFNRZ08d8L/iEIPLTlQMDjvwIsj03gxWCczYTht6DCiHJIZhMk4NK5gtPY9UqAYb09eV5VGT+jD4lp396E0I+w==",
+      "dependencies": [
+        "marked"
+      ]
+    },
+    "marked-footnote@1.2.4_marked@12.0.2": {
+      "integrity": "sha512-DB2Kl+wFh6YwZd70qABMY6WUkG1UuyqoNTFoDfGyG79Pz24neYtLBkB+45a7o72V7gkfvbC3CGzIYFobxfMT1Q==",
+      "dependencies": [
+        "marked"
+      ]
+    },
+    "marked-gfm-heading-id@3.2.0_marked@12.0.2": {
+      "integrity": "sha512-Xfxpr5lXLDLY10XqzSCA9l2dDaiabQUgtYM9hw8yunyVsB/xYBRpiic6BOiY/EAJw1ik1eWr1ET1HKOAPZBhXg==",
+      "dependencies": [
+        "github-slugger",
+        "marked"
+      ]
+    },
+    "marked@12.0.2": {
+      "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q=="
+    },
+    "nanoid@3.3.11": {
+      "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="
+    },
+    "parse-srcset@1.0.2": {
+      "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q=="
+    },
+    "picocolors@1.1.1": {
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+    },
+    "postcss@8.5.3": {
+      "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
+      "dependencies": [
+        "nanoid",
+        "picocolors",
+        "source-map-js"
+      ]
+    },
+    "prismjs@1.30.0": {
+      "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="
+    },
+    "sanitize-html@2.16.0": {
+      "integrity": "sha512-0s4caLuHHaZFVxFTG74oW91+j6vW7gKbGD6CD2+miP73CE6z6YtOBN0ArtLd2UGyi4IC7K47v3ENUbQX4jV3Mg==",
+      "dependencies": [
+        "deepmerge",
+        "escape-string-regexp",
+        "htmlparser2",
+        "is-plain-object",
+        "parse-srcset",
+        "postcss"
+      ]
+    },
+    "source-map-js@1.2.1": {
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
     }
   },
   "workspace": {
diff --git a/frontend/deno.json b/frontend/deno.json
index 055607e2..b5ec716a 100644
--- a/frontend/deno.json
+++ b/frontend/deno.json
@@ -23,6 +23,7 @@
     "preact-render-to-string": "npm:preact-render-to-string@6.3.1",
     "@preact/signals": "npm:@preact/signals@1.2.1",
 
+    "@std/datetime": "jsr:@std/datetime@^0.225.4",
     "@std/path": "jsr:@std/path@1",
     "@std/http": "jsr:@std/http@1",
     "@std/front-matter": "jsr:@std/front-matter@1",
diff --git a/frontend/deno.lock b/frontend/deno.lock
index 772bc078..eab41035 100644
--- a/frontend/deno.lock
+++ b/frontend/deno.lock
@@ -3,49 +3,40 @@
   "specifiers": {
     "jsr:@deno/gfm@0.11": "0.11.0",
     "jsr:@denosaurs/emoji@~0.3.1": "0.3.1",
-    "jsr:@fresh/core@^2.0.0-alpha.1": "2.0.0-alpha.25",
-    "jsr:@fresh/core@^2.0.0-alpha.25": "2.0.0-alpha.25",
+    "jsr:@fresh/core@^2.0.0-alpha.25": "2.0.0-alpha.29",
     "jsr:@fresh/plugin-tailwind@^0.0.1-alpha.7": "0.0.1-alpha.7",
-    "jsr:@luca/esbuild-deno-loader@0.11": "0.11.0",
-    "jsr:@preact-icons/common@^1.0.12": "1.0.12",
-    "jsr:@preact-icons/tb@^1.0.12": "1.0.12",
+    "jsr:@luca/esbuild-deno-loader@0.11": "0.11.1",
+    "jsr:@preact-icons/common@^1.0.12": "1.1.0",
+    "jsr:@preact-icons/tb@^1.0.12": "1.0.13",
     "jsr:@std/assert@0.221": "0.221.0",
-    "jsr:@std/bytes@^1.0.2": "1.0.3",
-    "jsr:@std/cli@^1.0.6": "1.0.10",
-    "jsr:@std/collections@^1.0.9": "1.0.9",
-    "jsr:@std/crypto@1": "1.0.3",
-    "jsr:@std/datetime@~0.225.2": "0.225.2",
-    "jsr:@std/encoding@1": "1.0.5",
-    "jsr:@std/encoding@^1.0.5": "1.0.5",
-    "jsr:@std/fmt@1": "1.0.6",
-    "jsr:@std/fmt@^1.0.3": "1.0.6",
-    "jsr:@std/fmt@^1.0.6": "1.0.6",
-    "jsr:@std/front-matter@1": "1.0.5",
-    "jsr:@std/fs@1": "1.0.5",
+    "jsr:@std/bytes@^1.0.2": "1.0.5",
+    "jsr:@std/crypto@1": "1.0.4",
+    "jsr:@std/datetime@~0.225.2": "0.225.4",
+    "jsr:@std/datetime@~0.225.4": "0.225.4",
+    "jsr:@std/encoding@1": "1.0.10",
+    "jsr:@std/encoding@^1.0.5": "1.0.10",
+    "jsr:@std/fmt@1": "1.0.7",
+    "jsr:@std/fmt@^1.0.6": "1.0.7",
+    "jsr:@std/front-matter@1": "1.0.9",
+    "jsr:@std/fs@1": "1.0.17",
     "jsr:@std/html@1": "1.0.3",
-    "jsr:@std/http@1": "1.0.9",
-    "jsr:@std/json@1": "1.0.0",
-    "jsr:@std/jsonc@1": "1.0.1",
-    "jsr:@std/media-types@1": "1.0.3",
-    "jsr:@std/media-types@^1.0.3": "1.0.3",
-    "jsr:@std/net@^1.0.4": "1.0.4",
+    "jsr:@std/http@1": "1.0.15",
+    "jsr:@std/jsonc@1": "1.0.2",
+    "jsr:@std/media-types@1": "1.1.0",
     "jsr:@std/path@0.221": "0.221.0",
-    "jsr:@std/path@1": "1.0.8",
-    "jsr:@std/path@^1.0.6": "1.0.8",
-    "jsr:@std/path@^1.0.7": "1.0.8",
-    "jsr:@std/semver@1": "1.0.3",
-    "jsr:@std/streams@^1.0.7": "1.0.8",
-    "jsr:@std/toml@^1.0.1": "1.0.2",
-    "jsr:@std/yaml@^1.0.5": "1.0.5",
-    "npm:@mdn/browser-compat-data@*": "5.6.13",
-    "npm:@orama/highlight@~0.1.8": "0.1.8",
+    "jsr:@std/path@1": "1.0.9",
+    "jsr:@std/path@^1.0.6": "1.0.9",
+    "jsr:@std/path@^1.0.9": "1.0.9",
+    "jsr:@std/semver@1": "1.0.5",
+    "jsr:@std/yaml@^1.0.5": "1.0.6",
+    "npm:@opentelemetry/api@^1.9.0": "1.9.0",
+    "npm:@orama/highlight@~0.1.8": "0.1.9",
     "npm:@orama/orama@2": "2.1.1",
     "npm:@oramacloud/client@1": "1.3.20",
-    "npm:@preact/signals@1.2.1": "1.2.1_preact@10.24.3",
-    "npm:@preact/signals@^1.2.3": "1.3.0_preact@10.24.3",
-    "npm:@types/node@*": "22.5.4",
-    "npm:@viz-js/viz@^3.11.0": "3.11.0",
-    "npm:apexcharts@^4.5.0": "4.5.0_@svgdotjs+svg.js@3.2.4_@svgdotjs+svg.select.js@4.0.2__@svgdotjs+svg.js@3.2.4",
+    "npm:@preact/signals@1.2.1": "1.2.1_preact@10.26.5",
+    "npm:@preact/signals@^1.2.3": "1.3.2_preact@10.22.1",
+    "npm:@viz-js/viz@^3.11.0": "3.12.0",
+    "npm:apexcharts@^4.5.0": "4.7.0_@svgdotjs+svg.js@3.2.4_@svgdotjs+svg.select.js@4.0.2__@svgdotjs+svg.js@3.2.4",
     "npm:autoprefixer@10.4.17": "10.4.17_postcss@8.4.35",
     "npm:cssnano@6.0.3": "6.0.3_postcss@8.4.35",
     "npm:esbuild-wasm@0.23.1": "0.23.1",
@@ -58,19 +49,18 @@
     "npm:marked-gfm-heading-id@^3.1.0": "3.2.0_marked@12.0.2",
     "npm:marked-smartypants@1.1.6": "1.1.6_marked@12.0.2",
     "npm:marked@12": "12.0.2",
-    "npm:postcss@8.4": "8.4.47",
+    "npm:postcss@8.4": "8.4.49",
     "npm:postcss@8.4.35": "8.4.35",
-    "npm:postmark@4.0.5": "4.0.5",
-    "npm:preact-render-to-string@6.3.1": "6.3.1_preact@10.24.3",
-    "npm:preact-render-to-string@^6.5.11": "6.5.11_preact@10.24.3",
-    "npm:preact@10": "10.24.3",
+    "npm:preact-render-to-string@6.3.1": "6.3.1_preact@10.26.5",
+    "npm:preact-render-to-string@^6.5.11": "6.5.13_preact@10.26.5",
+    "npm:preact@10": "10.26.5",
     "npm:preact@10.22.1": "10.22.1",
-    "npm:preact@^10.22.1": "10.24.3",
-    "npm:preact@^10.24.1": "10.24.3",
-    "npm:prismjs@^1.29.0": "1.29.0",
+    "npm:preact@^10.22.1": "10.26.5",
+    "npm:preact@^10.25.1": "10.26.5",
+    "npm:prismjs@^1.29.0": "1.30.0",
     "npm:sanitize-html@^2.13.0": "2.16.0",
-    "npm:tailwindcss@3.4": "3.4.14_postcss@8.4.47",
-    "npm:tailwindcss@^3.4.1": "3.4.14_postcss@8.4.47"
+    "npm:tailwindcss@3.4": "3.4.17_postcss@8.4.49",
+    "npm:tailwindcss@^3.4.1": "3.4.17_postcss@8.4.49"
   },
   "jsr": {
     "@deno/gfm@0.11.0": {
@@ -91,32 +81,32 @@
     "@denosaurs/emoji@0.3.1": {
       "integrity": "b0aed5f55dec99e83da7c9637fe0a36d1d6252b7c99deaaa3fc5dea3fcf3da8b"
     },
-    "@fresh/core@2.0.0-alpha.25": {
-      "integrity": "1069232989c4bc7f69ad424f6b97cdba1a7631d1307e1c2964aed748cd0cf74b",
+    "@fresh/core@2.0.0-alpha.29": {
+      "integrity": "5374a478c2d407da2a84a9802e00da2305de2945b0934e6a7b24a989a7b82e0f",
       "dependencies": [
         "jsr:@luca/esbuild-deno-loader",
         "jsr:@std/crypto",
-        "jsr:@std/datetime",
+        "jsr:@std/datetime@~0.225.2",
         "jsr:@std/encoding@1",
         "jsr:@std/fmt@1",
         "jsr:@std/fs",
         "jsr:@std/html",
         "jsr:@std/jsonc",
-        "jsr:@std/media-types@1",
+        "jsr:@std/media-types",
         "jsr:@std/path@1",
         "jsr:@std/semver",
+        "npm:@opentelemetry/api",
         "npm:@preact/signals@^1.2.3",
         "npm:esbuild",
         "npm:esbuild-wasm",
         "npm:preact-render-to-string@^6.5.11",
         "npm:preact@10",
-        "npm:preact@^10.24.1"
+        "npm:preact@^10.25.1"
       ]
     },
     "@fresh/plugin-tailwind@0.0.1-alpha.7": {
       "integrity": "b940991bdb76f0995dc58b25183f1001d72c4020e049d384ad3fb751556aa2a9",
       "dependencies": [
-        "jsr:@fresh/core@^2.0.0-alpha.1",
         "jsr:@std/path@0.221",
         "npm:autoprefixer",
         "npm:cssnano",
@@ -124,23 +114,22 @@
         "npm:tailwindcss@^3.4.1"
       ]
     },
-    "@luca/esbuild-deno-loader@0.11.0": {
-      "integrity": "c05a989aa7c4ee6992a27be5f15cfc5be12834cab7ff84cabb47313737c51a2c",
+    "@luca/esbuild-deno-loader@0.11.1": {
+      "integrity": "dc020d16d75b591f679f6b9288b10f38bdb4f24345edb2f5732affa1d9885267",
       "dependencies": [
         "jsr:@std/bytes",
         "jsr:@std/encoding@^1.0.5",
         "jsr:@std/path@^1.0.6"
       ]
     },
-    "@preact-icons/common@1.0.12": {
-      "integrity": "f7d7329d55c2753b0162438b120b638e5058fa406f49fe6b422e3b0694f6e0c1",
+    "@preact-icons/common@1.1.0": {
+      "integrity": "ed191a9f88ee0e35539b8de3482d1f8a223b3f14f3b0dbfcba063c70ca4332fc",
       "dependencies": [
-        "npm:preact@10",
         "npm:preact@^10.22.1"
       ]
     },
-    "@preact-icons/tb@1.0.12": {
-      "integrity": "8d041dc777f826784662afd123357c8c517b0785d881bfbb75b03de811e74d52",
+    "@preact-icons/tb@1.0.13": {
+      "integrity": "2ed1fe6b06a218680d707d1b54e35d7de3a1d543c5bf12b85e856c0582d614e1",
       "dependencies": [
         "jsr:@preact-icons/common",
         "npm:preact@10.22.1"
@@ -149,72 +138,44 @@
     "@std/assert@0.221.0": {
       "integrity": "a5f1aa6e7909dbea271754fd4ab3f4e687aeff4873b4cef9a320af813adb489a"
     },
-    "@std/bytes@1.0.3": {
-      "integrity": "e5d5b9e685966314e4edb4be60dfc4bd7624a075bfd4ec8109252b4320f76452"
-    },
-    "@std/cli@1.0.10": {
-      "integrity": "d047f6f4954a5c2827fe0963765ddd3d8b6cc7b7518682842645b95f571539dc"
-    },
-    "@std/collections@1.0.9": {
-      "integrity": "4f58104ead08a04a2199374247f07befe50ba01d9cca8cbb23ab9a0419921e71"
-    },
-    "@std/crypto@1.0.3": {
-      "integrity": "a2a32f51ddef632d299e3879cd027c630dcd4d1d9a5285d6e6788072f4e51e7f"
+    "@std/bytes@1.0.5": {
+      "integrity": "4465dd739d7963d964c809202ebea6d5c6b8e3829ef25c6a224290fbb8a1021e"
     },
-    "@std/datetime@0.225.2": {
-      "integrity": "45f0100554a912cd65f48089ef0a33aa1eb6ea21f08090840b539ab582827eaa"
+    "@std/crypto@1.0.4": {
+      "integrity": "cee245c453bd5366207f4d8aa25ea3e9c86cecad2be3fefcaa6cb17203d79340"
     },
-    "@std/encoding@1.0.5": {
-      "integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04"
+    "@std/datetime@0.225.4": {
+      "integrity": "682bc21738b941a4ed1465be6da01704e8010a3a6d9b615de9458202b84e00ec"
     },
-    "@std/fmt@1.0.3": {
-      "integrity": "97765c16aa32245ff4e2204ecf7d8562496a3cb8592340a80e7e554e0bb9149f"
+    "@std/encoding@1.0.10": {
+      "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1"
     },
-    "@std/fmt@1.0.6": {
-      "integrity": "a2c56a69a2369876ddb3ad6a500bb6501b5bad47bb3ea16bfb0c18974d2661fc"
+    "@std/fmt@1.0.7": {
+      "integrity": "2a727c043d8df62cd0b819b3fb709b64dd622e42c3b1bb817ea7e6cc606360fb"
     },
-    "@std/front-matter@1.0.5": {
-      "integrity": "abddc64030a33eb5bc524b8c73e7c417cea09177aaeb4abf75a56b540c4b6e60",
+    "@std/front-matter@1.0.9": {
+      "integrity": "ee6201d06674cbef137dda2252f62477450b48249e7d8d9ab57a30f85ff6f051",
       "dependencies": [
-        "jsr:@std/toml",
         "jsr:@std/yaml"
       ]
     },
-    "@std/fs@1.0.5": {
-      "integrity": "41806ad6823d0b5f275f9849a2640d87e4ef67c51ee1b8fb02426f55e02fd44e",
+    "@std/fs@1.0.17": {
+      "integrity": "1c00c632677c1158988ef7a004cb16137f870aafdb8163b9dce86ec652f3952b",
       "dependencies": [
-        "jsr:@std/path@^1.0.7"
+        "jsr:@std/path@^1.0.9"
       ]
     },
     "@std/html@1.0.3": {
       "integrity": "7a0ac35e050431fb49d44e61c8b8aac1ebd55937e0dc9ec6409aa4bab39a7988"
     },
-    "@std/http@1.0.9": {
-      "integrity": "d409fc319a5e8d4a154e576c758752e9700282d74f31357a12fec6420f9ecb6c",
-      "dependencies": [
-        "jsr:@std/cli",
-        "jsr:@std/encoding@^1.0.5",
-        "jsr:@std/fmt@^1.0.3",
-        "jsr:@std/media-types@^1.0.3",
-        "jsr:@std/net",
-        "jsr:@std/path@^1.0.7",
-        "jsr:@std/streams"
-      ]
-    },
-    "@std/json@1.0.0": {
-      "integrity": "985c1e544918d42e4e84072fc739ac4a19c3a5093292c99742ffcdd03fb6a268"
-    },
-    "@std/jsonc@1.0.1": {
-      "integrity": "6b36956e2a7cbb08ca5ad7fbec72e661e6217c202f348496ea88747636710dda",
-      "dependencies": [
-        "jsr:@std/json"
-      ]
+    "@std/http@1.0.15": {
+      "integrity": "435a4934b4e196e82a8233f724da525f7b7112f3566502f28815e94764c19159"
     },
-    "@std/media-types@1.0.3": {
-      "integrity": "b12d30a7852f7578f4d210622df713bbfd1cbdd9b4ec2eaf5c1845ab70bab159"
+    "@std/jsonc@1.0.2": {
+      "integrity": "909605dae3af22bd75b1cbda8d64a32cf1fd2cf6efa3f9e224aba6d22c0f44c7"
     },
-    "@std/net@1.0.4": {
-      "integrity": "2f403b455ebbccf83d8a027d29c5a9e3a2452fea39bb2da7f2c04af09c8bc852"
+    "@std/media-types@1.1.0": {
+      "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4"
     },
     "@std/path@0.221.0": {
       "integrity": "0a36f6b17314ef653a3a1649740cc8db51b25a133ecfe838f20b79a56ebe0095",
@@ -222,23 +183,14 @@
         "jsr:@std/assert"
       ]
     },
-    "@std/path@1.0.8": {
-      "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be"
+    "@std/path@1.0.9": {
+      "integrity": "260a49f11edd3db93dd38350bf9cd1b4d1366afa98e81b86167b4e3dd750129e"
     },
-    "@std/semver@1.0.3": {
-      "integrity": "7c139c6076a080eeaa4252c78b95ca5302818d7eafab0470d34cafd9930c13c8"
+    "@std/semver@1.0.5": {
+      "integrity": "529f79e83705714c105ad0ba55bec0f9da0f24d2f726b6cc1c15e505cc2c0624"
     },
-    "@std/streams@1.0.8": {
-      "integrity": "b41332d93d2cf6a82fe4ac2153b930adf1a859392931e2a19d9fabfb6f154fb3"
-    },
-    "@std/toml@1.0.2": {
-      "integrity": "5892ba489c5b512265a384238a8fe8dddbbb9498b4b210ef1b9f0336a423a39b",
-      "dependencies": [
-        "jsr:@std/collections"
-      ]
-    },
-    "@std/yaml@1.0.5": {
-      "integrity": "71ba3d334305ee2149391931508b2c293a8490f94a337eef3a09cade1a2a2742"
+    "@std/yaml@1.0.6": {
+      "integrity": "c9a5a914e1d51c46756cb10e356710035cfa905e713c90d3b711413fd3aead27"
     }
   },
   "npm": {
@@ -251,14 +203,14 @@
     "@babel/helper-validator-identifier@7.25.9": {
       "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="
     },
-    "@babel/parser@7.26.2": {
-      "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==",
+    "@babel/parser@7.27.0": {
+      "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
       "dependencies": [
         "@babel/types"
       ]
     },
-    "@babel/types@7.26.0": {
-      "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==",
+    "@babel/types@7.27.0": {
+      "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
       "dependencies": [
         "@babel/helper-string-parser",
         "@babel/helper-validator-identifier"
@@ -347,8 +299,8 @@
         "wrap-ansi@8.1.0"
       ]
     },
-    "@jridgewell/gen-mapping@0.3.5": {
-      "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
+    "@jridgewell/gen-mapping@0.3.8": {
+      "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
       "dependencies": [
         "@jridgewell/set-array",
         "@jridgewell/sourcemap-codec",
@@ -371,11 +323,8 @@
         "@jridgewell/sourcemap-codec"
       ]
     },
-    "@mdn/browser-compat-data@5.6.13": {
-      "integrity": "sha512-eZOraBiugKZewtS10c7M78OKwL6CBhVzqSQvb5j+WGw/7MdPRfHrP7LuiEoLCeHXfmE414geJjnHPGQ0iAnCyg=="
-    },
-    "@noble/hashes@1.5.0": {
-      "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA=="
+    "@noble/hashes@1.8.0": {
+      "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="
     },
     "@nodelib/fs.scandir@2.1.5": {
       "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
@@ -394,14 +343,17 @@
         "fastq"
       ]
     },
+    "@opentelemetry/api@1.9.0": {
+      "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="
+    },
     "@orama/cuid2@2.2.3": {
       "integrity": "sha512-Lcak3chblMejdlSHgYU2lS2cdOhDpU6vkfIJH4m+YKvqQyLqs1bB8+w6NT1MG5bO12NUK2GFc34Mn2xshMIQ1g==",
       "dependencies": [
         "@noble/hashes"
       ]
     },
-    "@orama/highlight@0.1.8": {
-      "integrity": "sha512-w3TvtWUKYlf/NoujoyEs38nJRi1lkwxdOXntXDYB9cfHzx+s+iPrps70YwFRRJu9TcHW8ffz503b0E6aAfsuvg=="
+    "@orama/highlight@0.1.9": {
+      "integrity": "sha512-eH4uZMea4R9x9vBFJyS/87oR4Y8oIKxF7e1SItJ9DhPlexZWMVZRlFYvHS1BM/563/dU240ct4ZaGHoYKiCkyQ=="
     },
     "@orama/orama@2.1.1": {
       "integrity": "sha512-euTV/2kya290SNkl5m8e/H1na8iDygk74nNtl4E0YZNyYIrEMwE1JwamoroMKGZw2Uz+in/8gH3m1+2YfP0j1w=="
@@ -423,18 +375,18 @@
     "@preact/signals-core@1.8.0": {
       "integrity": "sha512-OBvUsRZqNmjzCZXWLxkZfhcgT+Fk8DDcT/8vD6a1xhDemodyy87UJRJfASMuSD8FaAIeGgGm85ydXhm7lr4fyA=="
     },
-    "@preact/signals@1.2.1_preact@10.24.3": {
+    "@preact/signals@1.2.1_preact@10.26.5": {
       "integrity": "sha512-hRPvp1C2ooDzOHqfnhdpHgoIFDbYFAXLhoid3+jSItuPPD/J0r/UsiWKv/8ZO/oEhjRaP0M5niuRYsWqmY2GEA==",
       "dependencies": [
         "@preact/signals-core",
-        "preact@10.24.3"
+        "preact@10.26.5"
       ]
     },
-    "@preact/signals@1.3.0_preact@10.24.3": {
-      "integrity": "sha512-EOMeg42SlLS72dhoq6Vjq08havnLseWmPQ8A0YsgIAqMgWgx7V1a39+Pxo6i7SY5NwJtH4849JogFq3M67AzWg==",
+    "@preact/signals@1.3.2_preact@10.22.1": {
+      "integrity": "sha512-naxcJgUJ6BTOROJ7C3QML7KvwKwCXQJYTc5L/b0eEsdYgPB6SxwoQ1vDGcS0Q7GVjAenVq/tXrybVdFShHYZWg==",
       "dependencies": [
         "@preact/signals-core",
-        "preact@10.24.3"
+        "preact@10.22.1"
       ]
     },
     "@svgdotjs/svg.draggable.js@3.0.6_@svgdotjs+svg.js@3.2.4": {
@@ -468,30 +420,30 @@
     "@trysound/sax@0.2.0": {
       "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA=="
     },
-    "@types/node-fetch@2.6.11": {
-      "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==",
+    "@types/node-fetch@2.6.12": {
+      "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==",
       "dependencies": [
-        "@types/node@22.5.4",
+        "@types/node@22.12.0",
         "form-data"
       ]
     },
-    "@types/node@18.19.64": {
-      "integrity": "sha512-955mDqvO2vFf/oL7V3WiUtiz+BugyX8uVbaT2H8oj3+8dRyH2FLiNdowe7eNqRM7IOIZvzDH76EoAT+gwm6aIQ==",
+    "@types/node@18.19.87": {
+      "integrity": "sha512-OIAAu6ypnVZHmsHCeJ+7CCSub38QNBS9uceMQeg7K5Ur0Jr+wG9wEOEvvMbhp09pxD5czIUy/jND7s7Tb6Nw7A==",
       "dependencies": [
         "undici-types@5.26.5"
       ]
     },
-    "@types/node@22.5.4": {
-      "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
+    "@types/node@22.12.0": {
+      "integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==",
       "dependencies": [
-        "undici-types@6.19.8"
+        "undici-types@6.20.0"
       ]
     },
-    "@viz-js/viz@3.11.0": {
-      "integrity": "sha512-3zoKLQUqShIhTPvBAIIgJUf5wO9aY0q+Ftzw1u26KkJX1OJjT7Z5VUqgML2GIzXJYFgjqS6a2VREMwrgChuubA=="
+    "@viz-js/viz@3.12.0": {
+      "integrity": "sha512-Xca1HNJJYvoRzR3oKFX8eIl4fynNMupsp7s8bkyOcORuX+t416bxKFhplenWu5TliIgpW++5+0Yag/DzIxWYUQ=="
     },
-    "@vue/compiler-core@3.5.12": {
-      "integrity": "sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==",
+    "@vue/compiler-core@3.5.13": {
+      "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==",
       "dependencies": [
         "@babel/parser",
         "@vue/shared",
@@ -500,15 +452,15 @@
         "source-map-js"
       ]
     },
-    "@vue/compiler-dom@3.5.12": {
-      "integrity": "sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg==",
+    "@vue/compiler-dom@3.5.13": {
+      "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==",
       "dependencies": [
         "@vue/compiler-core",
         "@vue/shared"
       ]
     },
-    "@vue/compiler-sfc@3.5.12": {
-      "integrity": "sha512-2k973OGo2JuAa5+ZlekuQJtitI5CgLMOwgl94BzMCsKZCX/xiqzJYzapl4opFogKHqwJk34vfsaKpfEhd1k5nw==",
+    "@vue/compiler-sfc@3.5.13": {
+      "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==",
       "dependencies": [
         "@babel/parser",
         "@vue/compiler-core",
@@ -517,32 +469,32 @@
         "@vue/shared",
         "estree-walker",
         "magic-string",
-        "postcss@8.4.47",
+        "postcss@8.4.49",
         "source-map-js"
       ]
     },
-    "@vue/compiler-ssr@3.5.12": {
-      "integrity": "sha512-eLwc7v6bfGBSM7wZOGPmRavSWzNFF6+PdRhE+VFJhNCgHiF8AM7ccoqcv5kBXA2eWUfigD7byekvf/JsOfKvPA==",
+    "@vue/compiler-ssr@3.5.13": {
+      "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==",
       "dependencies": [
         "@vue/compiler-dom",
         "@vue/shared"
       ]
     },
-    "@vue/reactivity@3.5.12": {
-      "integrity": "sha512-UzaN3Da7xnJXdz4Okb/BGbAaomRHc3RdoWqTzlvd9+WBR5m3J39J1fGcHes7U3za0ruYn/iYy/a1euhMEHvTAg==",
+    "@vue/reactivity@3.5.13": {
+      "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==",
       "dependencies": [
         "@vue/shared"
       ]
     },
-    "@vue/runtime-core@3.5.12": {
-      "integrity": "sha512-hrMUYV6tpocr3TL3Ad8DqxOdpDe4zuQY4HPY3X/VRh+L2myQO8MFXPAMarIOSGNu0bFAjh1yBkMPXZBqCk62Uw==",
+    "@vue/runtime-core@3.5.13": {
+      "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==",
       "dependencies": [
         "@vue/reactivity",
         "@vue/shared"
       ]
     },
-    "@vue/runtime-dom@3.5.12": {
-      "integrity": "sha512-q8VFxR9A2MRfBr6/55Q3umyoN7ya836FzRXajPB6/Vvuv0zOPL+qltd9rIMzG/DbRLAIlREmnLsplEF/kotXKA==",
+    "@vue/runtime-dom@3.5.13": {
+      "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==",
       "dependencies": [
         "@vue/reactivity",
         "@vue/runtime-core",
@@ -550,16 +502,16 @@
         "csstype"
       ]
     },
-    "@vue/server-renderer@3.5.12_vue@3.5.12": {
-      "integrity": "sha512-I3QoeDDeEPZm8yR28JtY+rk880Oqmj43hreIBVTicisFTx/Dl7JpG72g/X7YF8hnQD3IFhkky5i2bPonwrTVPg==",
+    "@vue/server-renderer@3.5.13_vue@3.5.13": {
+      "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==",
       "dependencies": [
         "@vue/compiler-ssr",
         "@vue/shared",
         "vue"
       ]
     },
-    "@vue/shared@3.5.12": {
-      "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg=="
+    "@vue/shared@3.5.13": {
+      "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ=="
     },
     "@yr/monotone-cubic-spline@1.0.3": {
       "integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA=="
@@ -570,8 +522,8 @@
         "event-target-shim"
       ]
     },
-    "agentkeepalive@4.5.0": {
-      "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==",
+    "agentkeepalive@4.6.0": {
+      "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==",
       "dependencies": [
         "humanize-ms"
       ]
@@ -601,8 +553,8 @@
         "picomatch"
       ]
     },
-    "apexcharts@4.5.0_@svgdotjs+svg.js@3.2.4_@svgdotjs+svg.select.js@4.0.2__@svgdotjs+svg.js@3.2.4": {
-      "integrity": "sha512-E7ZkrVqPNBUWy/Rmg8DEIqHNBmElzICE/oxOX5Ekvs2ICQUOK/VkEkMH09JGJu+O/EA0NL31hxlmF+wrwrSLaQ==",
+    "apexcharts@4.7.0_@svgdotjs+svg.js@3.2.4_@svgdotjs+svg.select.js@4.0.2__@svgdotjs+svg.js@3.2.4": {
+      "integrity": "sha512-iZSrrBGvVlL+nt2B1NpqfDuBZ9jX61X9I2+XV0hlYXHtTwhwLTHDKGXjNXAgFBDLuvSYCB/rq2nPWVPRv2DrGA==",
       "dependencies": [
         "@svgdotjs/svg.draggable.js",
         "@svgdotjs/svg.filter.js",
@@ -630,14 +582,6 @@
         "postcss@8.4.35"
       ]
     },
-    "axios@1.8.4": {
-      "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
-      "dependencies": [
-        "follow-redirects",
-        "form-data",
-        "proxy-from-env"
-      ]
-    },
     "balanced-match@1.0.2": {
       "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
     },
@@ -659,8 +603,8 @@
         "fill-range"
       ]
     },
-    "browserslist@4.24.2": {
-      "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==",
+    "browserslist@4.24.4": {
+      "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
       "dependencies": [
         "caniuse-lite",
         "electron-to-chromium",
@@ -668,6 +612,13 @@
         "update-browserslist-db"
       ]
     },
+    "call-bind-apply-helpers@1.0.2": {
+      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+      "dependencies": [
+        "es-errors",
+        "function-bind"
+      ]
+    },
     "camelcase-css@2.0.1": {
       "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="
     },
@@ -680,8 +631,8 @@
         "lodash.uniq"
       ]
     },
-    "caniuse-lite@1.0.30001678": {
-      "integrity": "sha512-RR+4U/05gNtps58PEBDZcPWTgEO2MBeoPZ96aQcjmfkBWRIDfN451fW2qyDA9/+HohLLIL5GqiMwA+IB1pWarw=="
+    "caniuse-lite@1.0.30001715": {
+      "integrity": "sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw=="
     },
     "chokidar@3.6.0": {
       "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
@@ -723,8 +674,8 @@
     "commander@8.3.0": {
       "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="
     },
-    "cross-spawn@7.0.5": {
-      "integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==",
+    "cross-spawn@7.0.6": {
+      "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
       "dependencies": [
         "path-key",
         "shebang-command",
@@ -813,7 +764,7 @@
       "integrity": "sha512-MRq4CIj8pnyZpcI2qs6wswoYoDD1t0aL28n+41c1Ukcpm56m1h6mCexIHBGjfZfnTqtGSSCP4/fB1ovxgjBOiw==",
       "dependencies": [
         "cssnano-preset-default",
-        "lilconfig@3.1.2",
+        "lilconfig",
         "postcss@8.4.35"
       ]
     },
@@ -855,19 +806,27 @@
         "domelementtype"
       ]
     },
-    "domutils@3.1.0": {
-      "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
+    "domutils@3.2.2": {
+      "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
       "dependencies": [
         "dom-serializer",
         "domelementtype",
         "domhandler"
       ]
     },
+    "dunder-proto@1.0.1": {
+      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+      "dependencies": [
+        "call-bind-apply-helpers",
+        "es-errors",
+        "gopd"
+      ]
+    },
     "eastasianwidth@0.2.0": {
       "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
     },
-    "electron-to-chromium@1.5.53": {
-      "integrity": "sha512-7F6qFMWzBArEFK4PLE+c+nWzhS1kIoNkQvGnNDogofxQAym+roQ0GUIdw6C/4YdJ6JKGp19c2a/DLcfKTi4wRQ=="
+    "electron-to-chromium@1.5.142": {
+      "integrity": "sha512-Ah2HgkTu/9RhTDNThBtzu2Wirdy4DC9b0sMT1pUhbkZQ5U/iwmE+PHZX1MpjD5IkJCc2wSghgGG/B04szAx07w=="
     },
     "emoji-regex@8.0.0": {
       "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
@@ -878,6 +837,27 @@
     "entities@4.5.0": {
       "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="
     },
+    "es-define-property@1.0.1": {
+      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
+    },
+    "es-errors@1.3.0": {
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
+    },
+    "es-object-atoms@1.1.1": {
+      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+      "dependencies": [
+        "es-errors"
+      ]
+    },
+    "es-set-tostringtag@2.1.0": {
+      "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+      "dependencies": [
+        "es-errors",
+        "get-intrinsic",
+        "has-tostringtag",
+        "hasown"
+      ]
+    },
     "esbuild-wasm@0.23.1": {
       "integrity": "sha512-L3vn7ctvBrtScRfoB0zG1eOCiV4xYvpLYWfe6PDZuV+iDFDm4Mt3xeLIDllG8cDHQ8clUouK3XekulE+cxgkgw=="
     },
@@ -922,8 +902,8 @@
     "event-target-shim@5.0.1": {
       "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
     },
-    "fast-glob@3.3.2": {
-      "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+    "fast-glob@3.3.3": {
+      "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
       "dependencies": [
         "@nodelib/fs.stat",
         "@nodelib/fs.walk",
@@ -932,8 +912,8 @@
         "micromatch"
       ]
     },
-    "fastq@1.17.1": {
-      "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
+    "fastq@1.19.1": {
+      "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
       "dependencies": [
         "reusify"
       ]
@@ -944,11 +924,8 @@
         "to-regex-range"
       ]
     },
-    "follow-redirects@1.15.9": {
-      "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="
-    },
-    "foreground-child@3.3.0": {
-      "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
+    "foreground-child@3.3.1": {
+      "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
       "dependencies": [
         "cross-spawn",
         "signal-exit"
@@ -957,11 +934,12 @@
     "form-data-encoder@1.7.2": {
       "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="
     },
-    "form-data@4.0.1": {
-      "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
+    "form-data@4.0.2": {
+      "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
       "dependencies": [
         "asynckit",
         "combined-stream",
+        "es-set-tostringtag",
         "mime-types"
       ]
     },
@@ -981,6 +959,28 @@
     "function-bind@1.1.2": {
       "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
     },
+    "get-intrinsic@1.3.0": {
+      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+      "dependencies": [
+        "call-bind-apply-helpers",
+        "es-define-property",
+        "es-errors",
+        "es-object-atoms",
+        "function-bind",
+        "get-proto",
+        "gopd",
+        "has-symbols",
+        "hasown",
+        "math-intrinsics"
+      ]
+    },
+    "get-proto@1.0.1": {
+      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "dependencies": [
+        "dunder-proto",
+        "es-object-atoms"
+      ]
+    },
     "github-slugger@2.0.0": {
       "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="
     },
@@ -1007,6 +1007,18 @@
         "path-scurry"
       ]
     },
+    "gopd@1.2.0": {
+      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
+    },
+    "has-symbols@1.1.0": {
+      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="
+    },
+    "has-tostringtag@1.0.2": {
+      "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+      "dependencies": [
+        "has-symbols"
+      ]
+    },
     "hasown@2.0.2": {
       "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
       "dependencies": [
@@ -1037,8 +1049,8 @@
         "binary-extensions"
       ]
     },
-    "is-core-module@2.15.1": {
-      "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
+    "is-core-module@2.16.1": {
+      "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
       "dependencies": [
         "hasown"
       ]
@@ -1071,8 +1083,8 @@
         "@pkgjs/parseargs"
       ]
     },
-    "jiti@1.21.6": {
-      "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w=="
+    "jiti@1.21.7": {
+      "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="
     },
     "js-tokens@4.0.0": {
       "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
@@ -1083,11 +1095,8 @@
         "commander@8.3.0"
       ]
     },
-    "lilconfig@2.1.0": {
-      "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ=="
-    },
-    "lilconfig@3.1.2": {
-      "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow=="
+    "lilconfig@3.1.3": {
+      "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="
     },
     "lines-and-columns@1.2.4": {
       "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
@@ -1110,8 +1119,8 @@
     "lru-cache@10.4.3": {
       "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
     },
-    "magic-string@0.30.12": {
-      "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==",
+    "magic-string@0.30.17": {
+      "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
       "dependencies": [
         "@jridgewell/sourcemap-codec"
       ]
@@ -1145,6 +1154,9 @@
     "marked@12.0.2": {
       "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q=="
     },
+    "math-intrinsics@1.1.0": {
+      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="
+    },
     "mdn-data@2.0.28": {
       "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="
     },
@@ -1190,8 +1202,8 @@
         "thenify-all"
       ]
     },
-    "nanoid@3.3.7": {
-      "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g=="
+    "nanoid@3.3.11": {
+      "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="
     },
     "node-domexception@1.0.0": {
       "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="
@@ -1202,8 +1214,8 @@
         "whatwg-url"
       ]
     },
-    "node-releases@2.0.18": {
-      "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g=="
+    "node-releases@2.0.19": {
+      "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="
     },
     "normalize-path@3.0.0": {
       "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
@@ -1223,11 +1235,11 @@
     "object-hash@3.0.0": {
       "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="
     },
-    "openai@4.71.1": {
-      "integrity": "sha512-C6JNMaQ1eijM0lrjiRUL3MgThVP5RdwNAghpbJFdW0t11LzmyqON8Eh8MuUuEZ+CeD6bgYl2Fkn2BoptVxv9Ug==",
+    "openai@4.96.0": {
+      "integrity": "sha512-dKoW56i02Prv2XQolJ9Rl9Svqubqkzg3QpwEOBuSVZLk05Shelu7s+ErRTwFc1Bs3JZ2qBqBfVpXQiJhwOGG8A==",
       "dependencies": [
         "@types/node-fetch",
-        "@types/node@18.19.64",
+        "@types/node@18.19.87",
         "abort-controller",
         "agentkeepalive",
         "form-data-encoder",
@@ -1263,8 +1275,8 @@
     "pify@2.3.0": {
       "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="
     },
-    "pirates@4.0.6": {
-      "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg=="
+    "pirates@4.0.7": {
+      "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="
     },
     "postcss-calc@9.0.1_postcss@8.4.35": {
       "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==",
@@ -1316,27 +1328,27 @@
         "postcss@8.4.35"
       ]
     },
-    "postcss-import@15.1.0_postcss@8.4.47": {
+    "postcss-import@15.1.0_postcss@8.4.49": {
       "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
       "dependencies": [
         "postcss-value-parser",
-        "postcss@8.4.47",
+        "postcss@8.4.49",
         "read-cache",
         "resolve"
       ]
     },
-    "postcss-js@4.0.1_postcss@8.4.47": {
+    "postcss-js@4.0.1_postcss@8.4.49": {
       "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
       "dependencies": [
         "camelcase-css",
-        "postcss@8.4.47"
+        "postcss@8.4.49"
       ]
     },
-    "postcss-load-config@4.0.2_postcss@8.4.47": {
+    "postcss-load-config@4.0.2_postcss@8.4.49": {
       "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
       "dependencies": [
-        "lilconfig@3.1.2",
-        "postcss@8.4.47",
+        "lilconfig",
+        "postcss@8.4.49",
         "yaml"
       ]
     },
@@ -1390,11 +1402,11 @@
         "postcss@8.4.35"
       ]
     },
-    "postcss-nested@6.2.0_postcss@8.4.47": {
+    "postcss-nested@6.2.0_postcss@8.4.49": {
       "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
       "dependencies": [
         "postcss-selector-parser",
-        "postcss@8.4.47"
+        "postcss@8.4.49"
       ]
     },
     "postcss-normalize-charset@6.0.2_postcss@8.4.35": {
@@ -1516,47 +1528,38 @@
         "source-map-js"
       ]
     },
-    "postcss@8.4.47": {
-      "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
+    "postcss@8.4.49": {
+      "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
       "dependencies": [
         "nanoid",
         "picocolors",
         "source-map-js"
       ]
     },
-    "postmark@4.0.5": {
-      "integrity": "sha512-nerZdd3TwOH4CgGboZnlUM/q7oZk0EqpZgJL+Y3Nup8kHeaukxouQ6JcFF3EJEijc4QbuNv1TefGhboAKtf/SQ==",
-      "dependencies": [
-        "axios"
-      ]
-    },
-    "preact-render-to-string@6.3.1_preact@10.24.3": {
+    "preact-render-to-string@6.3.1_preact@10.26.5": {
       "integrity": "sha512-NQ28WrjLtWY6lKDlTxnFpKHZdpjfF+oE6V4tZ0rTrunHrtZp6Dm0oFrcJalt/5PNeqJz4j1DuZDS0Y6rCBoqDA==",
       "dependencies": [
-        "preact@10.24.3",
+        "preact@10.26.5",
         "pretty-format"
       ]
     },
-    "preact-render-to-string@6.5.11_preact@10.24.3": {
-      "integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==",
+    "preact-render-to-string@6.5.13_preact@10.26.5": {
+      "integrity": "sha512-iGPd+hKPMFKsfpR2vL4kJ6ZPcFIoWZEcBf0Dpm3zOpdVvj77aY8RlLiQji5OMrngEyaxGogeakTb54uS2FvA6w==",
       "dependencies": [
-        "preact@10.24.3"
+        "preact@10.26.5"
       ]
     },
     "preact@10.22.1": {
       "integrity": "sha512-jRYbDDgMpIb5LHq3hkI0bbl+l/TQ9UnkdQ0ww+lp+4MMOdqaUYdFc5qeyP+IV8FAd/2Em7drVPeKdQxsiWCf/A=="
     },
-    "preact@10.24.3": {
-      "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA=="
+    "preact@10.26.5": {
+      "integrity": "sha512-fmpDkgfGU6JYux9teDWLhj9mKN55tyepwYbxHgQuIxbWQzgFg5vk7Mrrtfx7xRxq798ynkY4DDDxZr235Kk+4w=="
     },
     "pretty-format@3.8.0": {
       "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="
     },
-    "prismjs@1.29.0": {
-      "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q=="
-    },
-    "proxy-from-env@1.1.0": {
-      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+    "prismjs@1.30.0": {
+      "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="
     },
     "queue-microtask@1.2.3": {
       "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
@@ -1579,16 +1582,16 @@
         "picomatch"
       ]
     },
-    "resolve@1.22.8": {
-      "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
+    "resolve@1.22.10": {
+      "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
       "dependencies": [
         "is-core-module",
         "path-parse",
         "supports-preserve-symlinks-flag"
       ]
     },
-    "reusify@1.0.4": {
-      "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="
+    "reusify@1.1.0": {
+      "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="
     },
     "run-parallel@1.2.0": {
       "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
@@ -1604,7 +1607,7 @@
         "htmlparser2",
         "is-plain-object",
         "parse-srcset",
-        "postcss@8.4.47"
+        "postcss@8.4.49"
       ]
     },
     "shebang-command@2.0.0": {
@@ -1688,8 +1691,8 @@
         "picocolors"
       ]
     },
-    "tailwindcss@3.4.14_postcss@8.4.47": {
-      "integrity": "sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==",
+    "tailwindcss@3.4.17_postcss@8.4.49": {
+      "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
       "dependencies": [
         "@alloc/quick-lru",
         "arg",
@@ -1700,7 +1703,7 @@
         "glob-parent@6.0.2",
         "is-glob",
         "jiti",
-        "lilconfig@2.1.0",
+        "lilconfig",
         "micromatch",
         "normalize-path",
         "object-hash",
@@ -1710,7 +1713,7 @@
         "postcss-load-config",
         "postcss-nested",
         "postcss-selector-parser",
-        "postcss@8.4.47",
+        "postcss@8.4.49",
         "resolve",
         "sucrase"
       ]
@@ -1742,11 +1745,11 @@
     "undici-types@5.26.5": {
       "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
     },
-    "undici-types@6.19.8": {
-      "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
+    "undici-types@6.20.0": {
+      "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="
     },
-    "update-browserslist-db@1.1.1_browserslist@4.24.2": {
-      "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==",
+    "update-browserslist-db@1.1.3_browserslist@4.24.4": {
+      "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
       "dependencies": [
         "browserslist",
         "escalade",
@@ -1756,8 +1759,8 @@
     "util-deprecate@1.0.2": {
       "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
     },
-    "vue@3.5.12": {
-      "integrity": "sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==",
+    "vue@3.5.13": {
+      "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==",
       "dependencies": [
         "@vue/compiler-dom",
         "@vue/compiler-sfc",
@@ -1801,19 +1804,11 @@
         "strip-ansi@7.1.0"
       ]
     },
-    "yaml@2.6.0": {
-      "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ=="
+    "yaml@2.7.1": {
+      "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ=="
     }
   },
   "remote": {
-    "https://deno.land/x/deno_doc@0.100.0/deno_doc_wasm.generated.js": "a8011ac4b2d7b704b36d4a2b32d021cba5ea2a796d60ea74870d5f88573fb639",
-    "https://deno.land/x/deno_doc@0.100.0/mod.ts": "3a1069e65428ddadabcf717c40b23805f309514edb3a984090eee037a91c17a9",
-    "https://deno.land/x/deno_graph@0.64.1/deno_graph_wasm.generated.js": "f34428e40b5c7b91ce7a05734d6782a83848cbb7f9f6510a4226d7c8b8ca2d8d",
-    "https://deno.land/x/deno_graph@0.64.1/loader.ts": "512a406cb4c449b45110f2b878cd09929a883a4af193060232d2d9fc76a9d4dd",
-    "https://deno.land/x/deno_graph@0.64.1/media_type.ts": "7c1c5c6654e3cf84b8daa53c0d1ffc1b7864849406f559b961eccff859b0a417",
-    "https://deno.land/x/deno_graph@0.64.1/mod.ts": "c1d12418cfb7c2f913b18a612806c79742ae4917dff70a8326abf009692dbcaf",
-    "https://deno.land/x/deno_graph@0.64.1/types.ts": "bde84cb2919068c07e6cf4d8bf3054e8da908f2f221623d5302380df29b96320",
-    "https://deno.land/x/dir@1.5.1/data_local_dir/mod.ts": "91eb1c4bfadfbeda30171007bac6d85aadacd43224a5ed721bbe56bc64e9eb66",
     "https://deno.land/x/imagescript@1.3.0/ImageScript.js": "cf90773c966031edd781ed176c598f7ed495e7694cd9b86c986d2d97f783cca0",
     "https://deno.land/x/imagescript@1.3.0/mod.ts": "18a6cb83c55e690c873505f6fe867364c678afb64934fe7aef593a6b92f79995",
     "https://deno.land/x/imagescript@1.3.0/png/src/crc.mjs": "5cf50de181d61dd00e66a240d811018ba5070afa8bba302f393604404604de84",
@@ -1918,15 +1913,6 @@
     "https://deno.land/x/jose@v4.4.0/util/base64url.ts": "d2567042684de8bf1e4f1ad67be40ab343ee192ddf1cf12ff1d940d6c1fa2bcb",
     "https://deno.land/x/jose@v4.4.0/util/decode_protected_header.ts": "00c3e86ec4969829568531ac4c52f79f35193759dcd60782e253b4f5e6ac250e",
     "https://deno.land/x/jose@v4.4.0/util/errors.ts": "ca0282ae339b178a26f9e85457f6050993382360ee3bf36dff2379b2ef40b317",
-    "https://deno.land/x/tabler_icons_tsx@0.0.5/tsx/arrow-right.tsx": "266dbbf138a7937be892bb9ebf70c6c920112cba6c44526570a0cdb7810cc31e",
-    "https://deno.land/x/tabler_icons_tsx@0.0.5/tsx/dots.tsx": "8d09ee116356646818a2d34227eff9f98b3d7821f916d2e2835579d9dcef3369",
-    "https://deno.land/x/tabler_icons_tsx@0.0.5/tsx/folder.tsx": "18e246f37110d4337161dd609350f101d7cc7e2b2f67e552dce6581f441b66f4",
-    "https://deno.land/x/tabler_icons_tsx@0.0.5/tsx/package.tsx": "80574824884571488e5cdcf77db64d83770fd757510d21912d2bccf0c5616485",
-    "https://deno.land/x/tabler_icons_tsx@0.0.5/tsx/trash-x.tsx": "2911592c5cb57ca37b67f23073ff74fb98b718c05b5e49cc71e73bbdd3e09987",
-    "https://deno.land/x/wasmbuild@0.15.1/cache.ts": "9d01b5cb24e7f2a942bbd8d14b093751fa690a6cde8e21709ddc97667e6669ed",
-    "https://deno.land/x/wasmbuild@0.15.1/loader.ts": "8c2fc10e21678e42f84c5135d8ab6ab7dc92424c3f05d2354896a29ccfd02a63",
-    "https://deno.land/x/wasmbuild@0.15.4/cache.ts": "9d01b5cb24e7f2a942bbd8d14b093751fa690a6cde8e21709ddc97667e6669ed",
-    "https://deno.land/x/wasmbuild@0.15.4/loader.ts": "8c2fc10e21678e42f84c5135d8ab6ab7dc92424c3f05d2354896a29ccfd02a63",
     "https://googleapis.deno.dev/_/base@v1/auth/authclient.ts": "02fefb7ab7ef45f4d013e3c26368b9910fa628a94379d48fe157bf0ae175afad",
     "https://googleapis.deno.dev/_/base@v1/auth/jwt.ts": "60a0ce915278fce4626e856c745ea81452cb47c7877715046b02773472cecf15",
     "https://googleapis.deno.dev/_/base@v1/auth/mod.ts": "2564c92422e18c3e714ad829af89ac3c28ae3021c7230fcb6ca3a46edf7efe25",
@@ -1939,6 +1925,7 @@
       "jsr:@fresh/core@^2.0.0-alpha.25",
       "jsr:@fresh/plugin-tailwind@^0.0.1-alpha.7",
       "jsr:@preact-icons/tb@^1.0.12",
+      "jsr:@std/datetime@~0.225.4",
       "jsr:@std/fmt@^1.0.6",
       "jsr:@std/front-matter@1",
       "jsr:@std/http@1",
diff --git a/frontend/islands/new.tsx b/frontend/islands/new.tsx
index 668ced91..bb1b63c2 100644
--- a/frontend/islands/new.tsx
+++ b/frontend/islands/new.tsx
@@ -463,7 +463,9 @@ export function CreatePackage({ scope, name, pkg, fromCli }: {
               

{pkg.value.description || No description}

- Created {timeAgo(pkg.value.createdAt)}.bc4 (feat(twas): refracto to use intl api) + Created{" "} + {timeAgo(pkg.value.createdAt)}.bc4 (feat(twas): refracto to use + intl api)

{fromCli && (

diff --git a/frontend/routes/admin/tickets.tsx b/frontend/routes/admin/tickets.tsx index 09a88c52..3bc4a132 100644 --- a/frontend/routes/admin/tickets.tsx +++ b/frontend/routes/admin/tickets.tsx @@ -99,7 +99,7 @@ export default define.page(function Tickets({ > {timeAgo(ticket.createdAt)} - + view diff --git a/frontend/utils/timeAgo.ts b/frontend/utils/timeAgo.ts index f7fe5c6a..735a3690 100644 --- a/frontend/utils/timeAgo.ts +++ b/frontend/utils/timeAgo.ts @@ -1,37 +1,22 @@ // Copyright 2024 the JSR authors. All rights reserved. MIT license. -export function timeAgo(date: Date | string): string { - const now = new Date(); - const past = new Date(date); - const diff = Math.abs(now.getTime() - past.getTime()); +import { difference } from "@std/datetime/difference"; +import type { Unit } from "@std/datetime/difference"; - const duration = { - years: Math.floor(diff / (1000 * 60 * 60 * 24 * 365)), - months: Math.floor( - (diff % (1000 * 60 * 60 * 24 * 365)) / (1000 * 60 * 60 * 24 * 30), - ), - days: Math.floor( - (diff % (1000 * 60 * 60 * 24 * 30)) / (1000 * 60 * 60 * 24), - ), - hours: Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)), - minutes: Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)), - seconds: Math.floor((diff % (1000 * 60)) / 1000), - }; +const units = [ + "years", + "months", + "weeks", + "days", + "hours", + "minutes", + "seconds", +] as Unit[]; - // Force english because JSR is an English-only project +export function timeAgo(date: string): string { + const duration = difference(new Date(date), new Date(), { units }); + if (duration.seconds === 0) return "0 seconds ago"; + const largestUnit = units.find((unit) => duration[unit]! > 0) || "seconds"; // @ts-ignore - TS doesn't know about this API yet - const formatter = new Intl.DurationFormat("en", { style: "long" }); - - if (duration.years >= 1) { - return formatter.format({ years: duration.years }) + " ago"; - } else if (duration.months >= 1) { - return formatter.format({ months: duration.months }) + " ago"; - } else if (duration.days >= 1) { - return formatter.format({ days: duration.days }) + " ago"; - } else if (duration.hours >= 1) { - return formatter.format({ hours: duration.hours }) + " ago"; - } else if (duration.minutes >= 1) { - return formatter.format({ minutes: duration.minutes }) + " ago"; - } else { - return formatter.format({ seconds: duration.seconds }) + " ago"; - } + return new Intl.DurationFormat("en", { style: "long" }) + .format({ [largestUnit]: duration[largestUnit] }) + " ago"; } From b7378e792068c835c0dda762d2976cc0ce8aefab Mon Sep 17 00:00:00 2001 From: Augustin Mauroy <97875033+AugustinMauroy@users.noreply.github.com> Date: Sat, 26 Apr 2025 11:57:27 +0200 Subject: [PATCH 3/3] fix+clean --- frontend/deno.lock | 11 ++++++++++- .../routes/account/(_components)/AccountLayout.tsx | 2 +- frontend/routes/account/tokens/index.tsx | 4 ++-- frontend/routes/package/versions.tsx | 2 +- frontend/routes/status.tsx | 2 +- frontend/utils/timeAgo.ts | 2 +- 6 files changed, 16 insertions(+), 7 deletions(-) diff --git a/frontend/deno.lock b/frontend/deno.lock index eab41035..e3759600 100644 --- a/frontend/deno.lock +++ b/frontend/deno.lock @@ -3,6 +3,7 @@ "specifiers": { "jsr:@deno/gfm@0.11": "0.11.0", "jsr:@denosaurs/emoji@~0.3.1": "0.3.1", + "jsr:@fresh/core@^2.0.0-alpha.1": "2.0.0-alpha.29", "jsr:@fresh/core@^2.0.0-alpha.25": "2.0.0-alpha.29", "jsr:@fresh/plugin-tailwind@^0.0.1-alpha.7": "0.0.1-alpha.7", "jsr:@luca/esbuild-deno-loader@0.11": "0.11.1", @@ -21,6 +22,7 @@ "jsr:@std/fs@1": "1.0.17", "jsr:@std/html@1": "1.0.3", "jsr:@std/http@1": "1.0.15", + "jsr:@std/json@^1.0.2": "1.0.2", "jsr:@std/jsonc@1": "1.0.2", "jsr:@std/media-types@1": "1.1.0", "jsr:@std/path@0.221": "0.221.0", @@ -107,6 +109,7 @@ "@fresh/plugin-tailwind@0.0.1-alpha.7": { "integrity": "b940991bdb76f0995dc58b25183f1001d72c4020e049d384ad3fb751556aa2a9", "dependencies": [ + "jsr:@fresh/core@^2.0.0-alpha.1", "jsr:@std/path@0.221", "npm:autoprefixer", "npm:cssnano", @@ -171,8 +174,14 @@ "@std/http@1.0.15": { "integrity": "435a4934b4e196e82a8233f724da525f7b7112f3566502f28815e94764c19159" }, + "@std/json@1.0.2": { + "integrity": "d9e5497801c15fb679f55a2c01c7794ad7a5dfda4dd1bebab5e409cb5e0d34d4" + }, "@std/jsonc@1.0.2": { - "integrity": "909605dae3af22bd75b1cbda8d64a32cf1fd2cf6efa3f9e224aba6d22c0f44c7" + "integrity": "909605dae3af22bd75b1cbda8d64a32cf1fd2cf6efa3f9e224aba6d22c0f44c7", + "dependencies": [ + "jsr:@std/json" + ] }, "@std/media-types@1.1.0": { "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4" diff --git a/frontend/routes/account/(_components)/AccountLayout.tsx b/frontend/routes/account/(_components)/AccountLayout.tsx index 4e1611bb..c2d89e54 100644 --- a/frontend/routes/account/(_components)/AccountLayout.tsx +++ b/frontend/routes/account/(_components)/AccountLayout.tsx @@ -25,7 +25,7 @@ export function AccountLayout({ user, active, children }: AccountLayoutProps) { {user.name}

- Created account {timeAgo(new Date(user.createdAt))} + Created account {timeAgo(user.createdAt)}

diff --git a/frontend/routes/account/tokens/index.tsx b/frontend/routes/account/tokens/index.tsx index a52ce09e..08ac96c1 100644 --- a/frontend/routes/account/tokens/index.tsx +++ b/frontend/routes/account/tokens/index.tsx @@ -119,7 +119,7 @@ function PersonalTokenRow({ token }: { token: Token }) { )}

- Created {timeAgo(new Date(token.createdAt))} + Created {timeAgo(token.createdAt)}

@@ -178,7 +178,7 @@ function SessionRow({ token }: { token: Token }) {

- Created {timeAgo(new Date(token.createdAt))} + Created {timeAgo(token.createdAt)}

diff --git a/frontend/routes/package/versions.tsx b/frontend/routes/package/versions.tsx index 7b40f362..ad1d225a 100644 --- a/frontend/routes/package/versions.tsx +++ b/frontend/routes/package/versions.tsx @@ -250,7 +250,7 @@ function Version({ {" "} )} - {timeAgo(new Date(version.createdAt))} + {timeAgo(version.createdAt)} )} diff --git a/frontend/routes/status.tsx b/frontend/routes/status.tsx index 02e815d4..77bb7734 100644 --- a/frontend/routes/status.tsx +++ b/frontend/routes/status.tsx @@ -50,7 +50,7 @@ export default define.page(function PackageListPage({

Created:{" "} - {timeAgo(new Date(data.publishingTask.createdAt))} + {timeAgo(data.publishingTask.createdAt)}

{data.publishingTask.user && (

diff --git a/frontend/utils/timeAgo.ts b/frontend/utils/timeAgo.ts index 735a3690..6deef96e 100644 --- a/frontend/utils/timeAgo.ts +++ b/frontend/utils/timeAgo.ts @@ -12,7 +12,7 @@ const units = [ "seconds", ] as Unit[]; -export function timeAgo(date: string): string { +export function timeAgo(date: string | Date): string { const duration = difference(new Date(date), new Date(), { units }); if (duration.seconds === 0) return "0 seconds ago"; const largestUnit = units.find((unit) => duration[unit]! > 0) || "seconds";