diff --git a/apps/web/app/vnext/page.test.tsx b/apps/web/app/vnext/page.test.tsx
index 6b90dd6d..e4f481c0 100644
--- a/apps/web/app/vnext/page.test.tsx
+++ b/apps/web/app/vnext/page.test.tsx
@@ -235,6 +235,72 @@ describe("VNextPage", () => {
expect(screen.getByText("Follow up with Sam about launch owner")).toBeInTheDocument();
}, 60000); // heavy multi-action page test; allow bounded CI contention headroom
+ it("repopulates source, memory, and connector drafts when their selections change", async () => {
+ await renderVNextPage();
+
+ fireEvent.change(screen.getByLabelText("Selected source title"), {
+ target: { value: "Unsaved source draft" },
+ });
+ fireEvent.click(screen.getByRole("button", { name: /Vendor legal note/ }));
+ expect(screen.getByLabelText("Selected source title")).toHaveValue("Vendor legal note");
+ expect(screen.getByLabelText("Source domain")).toHaveValue("legal");
+ expect(screen.getByLabelText("Source sensitivity")).toHaveValue("internal");
+
+ fireEvent.click(screen.getByRole("button", { name: /Launch review note/ }));
+ expect(screen.getByLabelText("Selected source title")).toHaveValue("Launch review note");
+ expect(screen.getByLabelText("Source domain")).toHaveValue("project");
+ expect(screen.getByLabelText("Source sensitivity")).toHaveValue("private");
+
+ fireEvent.change(screen.getByLabelText("Edited memory title"), {
+ target: { value: "Unsaved memory draft" },
+ });
+ fireEvent.change(screen.getByLabelText("Open-loop title"), {
+ target: { value: "Unsaved open-loop draft" },
+ });
+ fireEvent.click(
+ screen.getByRole("button", { name: /Vendor legal review is waiting for Priya/ }),
+ );
+ expect(screen.getByLabelText("Edited memory title")).toHaveValue(
+ "Vendor legal review is waiting for Priya.",
+ );
+ expect(screen.getByLabelText("Edited memory text")).toHaveValue(
+ "Vendor legal review is waiting for Priya.",
+ );
+ expect(screen.getByLabelText("Domain label")).toHaveValue("legal");
+ expect(screen.getByLabelText("Sensitivity label")).toHaveValue("internal");
+ expect(screen.getByLabelText("Assigned project")).toHaveValue("project-fixture-1");
+ expect(screen.getByLabelText("Open-loop title")).toHaveValue(
+ "Vendor legal review is waiting for Priya.",
+ );
+
+ fireEvent.change(screen.getByLabelText("Connector"), {
+ target: { value: "browser_clipper" },
+ });
+ expect(screen.getByLabelText("Enabled")).toHaveValue("enabled");
+ expect(
+ screen.getByLabelText("Default domain", { selector: "#vnext-connector-domain" }),
+ ).toHaveValue("professional");
+ fireEvent.change(
+ screen.getByLabelText("Default domain", { selector: "#vnext-connector-domain" }),
+ { target: { value: "legal" } },
+ );
+ fireEvent.change(screen.getByLabelText("Connector"), {
+ target: { value: "local_folder" },
+ });
+ expect(
+ screen.getByLabelText("Default domain", { selector: "#vnext-connector-domain" }),
+ ).toHaveValue("project");
+ fireEvent.change(screen.getByLabelText("Connector"), {
+ target: { value: "browser_clipper" },
+ });
+ expect(
+ screen.getByLabelText("Default domain", { selector: "#vnext-connector-domain" }),
+ ).toHaveValue("professional");
+ expect(screen.getByLabelText("Trusted-client capture secret ref")).toHaveValue(
+ "browser.capture_token.default",
+ );
+ }, 30000);
+
it("refreshes Ask Alice output and generates reviewable artifacts", async () => {
await renderVNextPage();
diff --git a/apps/web/components/approval-detail.test.tsx b/apps/web/components/approval-detail.test.tsx
index dfbd5099..9b72d7ee 100644
--- a/apps/web/components/approval-detail.test.tsx
+++ b/apps/web/components/approval-detail.test.tsx
@@ -251,4 +251,72 @@ describe("ApprovalDetail", () => {
expect(screen.getByRole("button", { name: "Reject" })).toBeDisabled();
expect(screen.getByText("Approve and reject controls are disabled in fixture mode.")).toBeInTheDocument();
});
+
+ it("adopts refreshed detail objects without over-resetting write-back form state", () => {
+ const { rerender } = render(
+ ,
+ );
+
+ fireEvent.change(screen.getByLabelText("Memory key"), {
+ target: { value: "user.preference.supplement.magnesium" },
+ });
+ fireEvent.change(screen.getByLabelText("Memory value (JSON)"), {
+ target: { value: "not-json" },
+ });
+ fireEvent.click(screen.getByRole("button", { name: "Submit memory write-back" }));
+ expect(screen.getByText("Memory value must be valid JSON.")).toBeInTheDocument();
+
+ const refreshedApproval = {
+ ...approval,
+ tool: { ...approval.tool, name: "Updated Merchant Proxy" },
+ };
+ const refreshedExecution = {
+ ...execution,
+ id: "execution-2",
+ trace_id: "trace-execution-2",
+ request_event_id: "event-request-2",
+ result_event_id: "event-result-2",
+ };
+ rerender(
+ ,
+ );
+
+ expect(screen.getByRole("heading", { name: "Updated Merchant Proxy" })).toBeInTheDocument();
+ expect(screen.getAllByText("event-result-2").length).toBeGreaterThan(0);
+ expect(screen.getAllByText("event-request-2").length).toBeGreaterThan(0);
+ expect(screen.getByText("Memory value must be valid JSON.")).toBeInTheDocument();
+ expect(screen.getByLabelText("Memory key")).toHaveValue("user.preference.supplement.magnesium");
+ expect(screen.getByLabelText("Memory value (JSON)")).toHaveValue("not-json");
+
+ rerender(
+ ,
+ );
+
+ expect(
+ screen.getByText("Execution evidence is required before memory write-back can be submitted."),
+ ).toBeInTheDocument();
+ expect(screen.getByLabelText("Memory key")).toHaveValue("user.preference.supplement.magnesium");
+ expect(screen.getByLabelText("Memory value (JSON)")).toHaveValue("not-json");
+ });
});
diff --git a/apps/web/components/approval-detail.tsx b/apps/web/components/approval-detail.tsx
index 1c5879cd..b71083fa 100644
--- a/apps/web/components/approval-detail.tsx
+++ b/apps/web/components/approval-detail.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useEffect, useState } from "react";
+import { useState } from "react";
import type { ApprovalExecutionResponse, ApprovalItem, ApiSource, ToolExecutionItem } from "../lib/api";
import { EmptyState } from "./empty-state";
@@ -55,12 +55,20 @@ export function ApprovalDetail({
const [approval, setApproval] = useState(initialApproval);
const [execution, setExecution] = useState(initialExecution);
const [executionPreview, setExecutionPreview] = useState(null);
-
- useEffect(() => {
+ const [previousInitialState, setPreviousInitialState] = useState(() => ({
+ approval: initialApproval,
+ execution: initialExecution,
+ }));
+
+ if (
+ initialApproval !== previousInitialState.approval ||
+ initialExecution !== previousInitialState.execution
+ ) {
+ setPreviousInitialState({ approval: initialApproval, execution: initialExecution });
setApproval(initialApproval);
setExecution(initialExecution);
setExecutionPreview(null);
- }, [initialApproval, initialExecution]);
+ }
if (!approval) {
return (
diff --git a/apps/web/components/calendar-event-ingest-form.test.tsx b/apps/web/components/calendar-event-ingest-form.test.tsx
index cb2fcc85..cf460ffc 100644
--- a/apps/web/components/calendar-event-ingest-form.test.tsx
+++ b/apps/web/components/calendar-event-ingest-form.test.tsx
@@ -154,4 +154,82 @@ describe("CalendarEventIngestForm", () => {
expect(screen.getByText("Select one discovered event before submitting ingestion.")).toBeInTheDocument();
expect(ingestCalendarEventMock).not.toHaveBeenCalled();
});
+
+ it("preserves valid workspace state and resets only prop-derived status when inputs change", async () => {
+ const secondWorkspace = {
+ ...baseWorkspaces[0],
+ id: "workspace-2",
+ task_id: "task-2",
+ local_path: "/tmp/task-workspaces/task-2",
+ };
+ const replacementWorkspace = {
+ ...baseWorkspaces[0],
+ id: "workspace-3",
+ task_id: "task-3",
+ local_path: "/tmp/task-workspaces/task-3",
+ };
+ ingestCalendarEventMock.mockResolvedValue({
+ account: baseAccount,
+ event: {
+ provider_event_id: "evt-001",
+ artifact_relative_path: "calendar/acct-owner-001/evt-001.txt",
+ media_type: "text/plain",
+ },
+ artifact: {
+ id: "artifact-1",
+ task_id: "task-1",
+ task_workspace_id: "workspace-2",
+ status: "registered",
+ ingestion_status: "ingested",
+ relative_path: "calendar/acct-owner-001/evt-001.txt",
+ media_type_hint: "text/plain",
+ created_at: "2026-03-18T10:10:00Z",
+ updated_at: "2026-03-18T10:11:00Z",
+ },
+ summary: {
+ total_count: 1,
+ total_characters: 256,
+ media_type: "text/plain",
+ chunking_rule: "normalized_utf8_text_fixed_window_1000_chars_v1",
+ order: ["sequence_no_asc", "id_asc"],
+ },
+ });
+
+ const renderForm = (account = baseAccount, taskWorkspaces = [baseWorkspaces[0], secondWorkspace]) => (
+
+ );
+ const { rerender } = render(renderForm());
+
+ fireEvent.change(screen.getByLabelText("Task workspace"), {
+ target: { value: "workspace-2" },
+ });
+ fireEvent.click(screen.getByRole("button", { name: "Ingest selected event" }));
+ await screen.findByText(/Ingestion completed\./i);
+
+ rerender(renderForm({ ...baseAccount }, [{ ...baseWorkspaces[0] }, { ...secondWorkspace }]));
+ expect(screen.getByLabelText("Task workspace")).toHaveValue("workspace-2");
+ expect(screen.getByText("Select one task workspace to ingest the discovered event.")).toBeInTheDocument();
+ expect(screen.getAllByText("calendar/acct-owner-001/evt-001.txt").length).toBeGreaterThan(0);
+
+ rerender(renderForm({ ...baseAccount }, [replacementWorkspace]));
+ expect(screen.getByLabelText("Task workspace")).toHaveValue("workspace-3");
+ fireEvent.click(screen.getByRole("button", { name: "Ingest selected event" }));
+ await waitFor(() => {
+ expect(ingestCalendarEventMock).toHaveBeenLastCalledWith(
+ "https://api.example.com",
+ "calendar-account-1",
+ "evt-001",
+ { user_id: "user-1", task_workspace_id: "workspace-3" },
+ );
+ });
+ });
});
diff --git a/apps/web/components/calendar-event-ingest-form.tsx b/apps/web/components/calendar-event-ingest-form.tsx
index 42d5fcc9..ca4cee54 100644
--- a/apps/web/components/calendar-event-ingest-form.tsx
+++ b/apps/web/components/calendar-event-ingest-form.tsx
@@ -1,7 +1,7 @@
"use client";
import type { FormEvent } from "react";
-import { useEffect, useMemo, useState } from "react";
+import { useMemo, useState } from "react";
import { useRouter } from "next/navigation";
@@ -28,6 +28,27 @@ type CalendarEventIngestFormProps = {
userId?: string;
};
+function defaultStatusText(options: {
+ hasAccount: boolean;
+ hasTaskWorkspace: boolean;
+ hasSelectedEvent: boolean;
+ liveModeReady: boolean;
+}) {
+ if (!options.hasAccount) {
+ return "Select a Calendar account to enable single-event ingestion.";
+ }
+ if (!options.hasTaskWorkspace) {
+ return "No task workspace is available for ingestion target selection.";
+ }
+ if (!options.hasSelectedEvent) {
+ return "Select one discovered event before submitting ingestion.";
+ }
+ if (options.liveModeReady) {
+ return "Select one task workspace to ingest the discovered event.";
+ }
+ return "Event ingestion is unavailable until live API configuration, live account detail, and live task workspace list are present.";
+}
+
export function CalendarEventIngestForm({
account,
accountSource,
@@ -61,55 +82,51 @@ export function CalendarEventIngestForm({
[account, accountSource, apiBaseUrl, userId, taskWorkspaceSource, taskWorkspaces.length],
);
- const [statusText, setStatusText] = useState(
- !account
- ? "Select a Calendar account to enable single-event ingestion."
- : taskWorkspaces.length === 0
- ? "No task workspace is available for ingestion target selection."
- : !hasSelectedEvent
- ? "Select one discovered event before submitting ingestion."
- : liveModeReady
- ? "Select one task workspace to ingest the discovered event."
- : "Event ingestion is unavailable until live API configuration, live account detail, and live task workspace list are present.",
+ const [statusText, setStatusText] = useState(() =>
+ defaultStatusText({
+ hasAccount: Boolean(account),
+ hasTaskWorkspace: taskWorkspaces.length > 0,
+ hasSelectedEvent,
+ liveModeReady,
+ }),
);
+ const [previousStatusInputs, setPreviousStatusInputs] = useState(() => ({
+ account,
+ hasSelectedEvent,
+ liveModeReady,
+ taskWorkspaceCount: taskWorkspaces.length,
+ }));
- useEffect(() => {
- const hasWorkspace = taskWorkspaces.some((workspace) => workspace.id === taskWorkspaceId);
- if (!hasWorkspace) {
- setTaskWorkspaceId(taskWorkspaces[0]?.id ?? "");
- }
- }, [taskWorkspaceId, taskWorkspaces]);
-
- useEffect(() => {
- if (!account) {
- setStatusTone("info");
- setStatusText("Select a Calendar account to enable single-event ingestion.");
- return;
- }
-
- if (taskWorkspaces.length === 0) {
- setStatusTone("info");
- setStatusText("No task workspace is available for ingestion target selection.");
- return;
- }
-
- if (!hasSelectedEvent) {
- setStatusTone("info");
- setStatusText("Select one discovered event before submitting ingestion.");
- return;
- }
-
- if (!liveModeReady) {
- setStatusTone("info");
- setStatusText(
- "Event ingestion is unavailable until live API configuration, live account detail, and live task workspace list are present.",
- );
- return;
- }
+ const fallbackTaskWorkspaceId = taskWorkspaces[0]?.id ?? "";
+ if (
+ !taskWorkspaces.some((workspace) => workspace.id === taskWorkspaceId) &&
+ taskWorkspaceId !== fallbackTaskWorkspaceId
+ ) {
+ setTaskWorkspaceId(fallbackTaskWorkspaceId);
+ }
+ if (
+ account !== previousStatusInputs.account ||
+ hasSelectedEvent !== previousStatusInputs.hasSelectedEvent ||
+ liveModeReady !== previousStatusInputs.liveModeReady ||
+ taskWorkspaces.length !== previousStatusInputs.taskWorkspaceCount
+ ) {
+ setPreviousStatusInputs({
+ account,
+ hasSelectedEvent,
+ liveModeReady,
+ taskWorkspaceCount: taskWorkspaces.length,
+ });
setStatusTone("info");
- setStatusText("Select one task workspace to ingest the discovered event.");
- }, [account, hasSelectedEvent, liveModeReady, taskWorkspaces.length]);
+ setStatusText(
+ defaultStatusText({
+ hasAccount: Boolean(account),
+ hasTaskWorkspace: taskWorkspaces.length > 0,
+ hasSelectedEvent,
+ liveModeReady,
+ }),
+ );
+ }
async function handleSubmit(event: FormEvent) {
event.preventDefault();
diff --git a/apps/web/components/gmail-message-ingest-form.test.tsx b/apps/web/components/gmail-message-ingest-form.test.tsx
index 6f9378d8..67806bd8 100644
--- a/apps/web/components/gmail-message-ingest-form.test.tsx
+++ b/apps/web/components/gmail-message-ingest-form.test.tsx
@@ -134,4 +134,84 @@ describe("GmailMessageIngestForm", () => {
).toBeInTheDocument();
expect(ingestGmailMessageMock).not.toHaveBeenCalled();
});
+
+ it("preserves typed input and result while reconciling workspace and status props", async () => {
+ const secondWorkspace = {
+ ...baseWorkspaces[0],
+ id: "workspace-2",
+ task_id: "task-2",
+ local_path: "/tmp/task-workspaces/task-2",
+ };
+ const replacementWorkspace = {
+ ...baseWorkspaces[0],
+ id: "workspace-3",
+ task_id: "task-3",
+ local_path: "/tmp/task-workspaces/task-3",
+ };
+ ingestGmailMessageMock.mockResolvedValue({
+ account: baseAccount,
+ message: {
+ provider_message_id: "msg-001",
+ artifact_relative_path: "gmail/acct-owner-001/msg-001.eml",
+ media_type: "message/rfc822",
+ },
+ artifact: {
+ id: "artifact-1",
+ task_id: "task-1",
+ task_workspace_id: "workspace-2",
+ status: "registered",
+ ingestion_status: "ingested",
+ relative_path: "gmail/acct-owner-001/msg-001.eml",
+ media_type_hint: "message/rfc822",
+ created_at: "2026-03-18T10:10:00Z",
+ updated_at: "2026-03-18T10:11:00Z",
+ },
+ summary: {
+ total_count: 1,
+ total_characters: 128,
+ media_type: "message/rfc822",
+ chunking_rule: "normalized_utf8_text_fixed_window_1000_chars_v1",
+ order: ["sequence_no_asc", "id_asc"],
+ },
+ });
+
+ const renderForm = (account = baseAccount, taskWorkspaces = [baseWorkspaces[0], secondWorkspace]) => (
+
+ );
+ const { rerender } = render(renderForm());
+
+ fireEvent.change(screen.getByLabelText("Provider message ID"), {
+ target: { value: "msg-001" },
+ });
+ fireEvent.change(screen.getByLabelText("Task workspace"), {
+ target: { value: "workspace-2" },
+ });
+ fireEvent.click(screen.getByRole("button", { name: "Ingest selected message" }));
+ await screen.findByText(/Ingestion completed\./i);
+
+ rerender(renderForm({ ...baseAccount }, [{ ...baseWorkspaces[0] }, { ...secondWorkspace }]));
+ expect(screen.getByLabelText("Provider message ID")).toHaveValue("msg-001");
+ expect(screen.getByLabelText("Task workspace")).toHaveValue("workspace-2");
+ expect(screen.getByText("Enter one provider message ID and select one task workspace.")).toBeInTheDocument();
+ expect(screen.getAllByText("gmail/acct-owner-001/msg-001.eml").length).toBeGreaterThan(0);
+
+ rerender(renderForm({ ...baseAccount }, [replacementWorkspace]));
+ expect(screen.getByLabelText("Task workspace")).toHaveValue("workspace-3");
+ fireEvent.click(screen.getByRole("button", { name: "Ingest selected message" }));
+ await waitFor(() => {
+ expect(ingestGmailMessageMock).toHaveBeenLastCalledWith(
+ "https://api.example.com",
+ "gmail-account-1",
+ "msg-001",
+ { user_id: "user-1", task_workspace_id: "workspace-3" },
+ );
+ });
+ });
});
diff --git a/apps/web/components/gmail-message-ingest-form.tsx b/apps/web/components/gmail-message-ingest-form.tsx
index cb5670ec..d9b280d0 100644
--- a/apps/web/components/gmail-message-ingest-form.tsx
+++ b/apps/web/components/gmail-message-ingest-form.tsx
@@ -1,7 +1,7 @@
"use client";
import type { FormEvent } from "react";
-import { useEffect, useMemo, useState } from "react";
+import { useMemo, useState } from "react";
import { useRouter } from "next/navigation";
@@ -26,6 +26,23 @@ type GmailMessageIngestFormProps = {
userId?: string;
};
+function defaultStatusText(options: {
+ hasAccount: boolean;
+ hasTaskWorkspace: boolean;
+ liveModeReady: boolean;
+}) {
+ if (!options.hasAccount) {
+ return "Select a Gmail account to enable single-message ingestion.";
+ }
+ if (!options.hasTaskWorkspace) {
+ return "No task workspace is available for ingestion target selection.";
+ }
+ if (options.liveModeReady) {
+ return "Enter one provider message ID and select one task workspace.";
+ }
+ return "Message ingestion is unavailable until live API configuration, live account detail, and live task workspace list are present.";
+}
+
export function GmailMessageIngestForm({
account,
accountSource,
@@ -57,47 +74,46 @@ export function GmailMessageIngestForm({
[account, accountSource, apiBaseUrl, userId, taskWorkspaceSource, taskWorkspaces.length],
);
- const [statusText, setStatusText] = useState(
- !account
- ? "Select a Gmail account to enable single-message ingestion."
- : taskWorkspaces.length === 0
- ? "No task workspace is available for ingestion target selection."
- : liveModeReady
- ? "Enter one provider message ID and select one task workspace."
- : "Message ingestion is unavailable until live API configuration, live account detail, and live task workspace list are present.",
+ const [statusText, setStatusText] = useState(() =>
+ defaultStatusText({
+ hasAccount: Boolean(account),
+ hasTaskWorkspace: taskWorkspaces.length > 0,
+ liveModeReady,
+ }),
);
+ const [previousStatusInputs, setPreviousStatusInputs] = useState(() => ({
+ account,
+ liveModeReady,
+ taskWorkspaceCount: taskWorkspaces.length,
+ }));
- useEffect(() => {
- const hasWorkspace = taskWorkspaces.some((workspace) => workspace.id === taskWorkspaceId);
- if (!hasWorkspace) {
- setTaskWorkspaceId(taskWorkspaces[0]?.id ?? "");
- }
- }, [taskWorkspaceId, taskWorkspaces]);
-
- useEffect(() => {
- if (!account) {
- setStatusTone("info");
- setStatusText("Select a Gmail account to enable single-message ingestion.");
- return;
- }
-
- if (taskWorkspaces.length === 0) {
- setStatusTone("info");
- setStatusText("No task workspace is available for ingestion target selection.");
- return;
- }
-
- if (!liveModeReady) {
- setStatusTone("info");
- setStatusText(
- "Message ingestion is unavailable until live API configuration, live account detail, and live task workspace list are present.",
- );
- return;
- }
+ const fallbackTaskWorkspaceId = taskWorkspaces[0]?.id ?? "";
+ if (
+ !taskWorkspaces.some((workspace) => workspace.id === taskWorkspaceId) &&
+ taskWorkspaceId !== fallbackTaskWorkspaceId
+ ) {
+ setTaskWorkspaceId(fallbackTaskWorkspaceId);
+ }
+ if (
+ account !== previousStatusInputs.account ||
+ liveModeReady !== previousStatusInputs.liveModeReady ||
+ taskWorkspaces.length !== previousStatusInputs.taskWorkspaceCount
+ ) {
+ setPreviousStatusInputs({
+ account,
+ liveModeReady,
+ taskWorkspaceCount: taskWorkspaces.length,
+ });
setStatusTone("info");
- setStatusText("Enter one provider message ID and select one task workspace.");
- }, [account, liveModeReady, taskWorkspaces.length]);
+ setStatusText(
+ defaultStatusText({
+ hasAccount: Boolean(account),
+ hasTaskWorkspace: taskWorkspaces.length > 0,
+ liveModeReady,
+ }),
+ );
+ }
async function handleSubmit(event: FormEvent) {
event.preventDefault();
diff --git a/apps/web/components/vnext-brain-workspace.tsx b/apps/web/components/vnext-brain-workspace.tsx
index 8a73ffb7..992e39b3 100644
--- a/apps/web/components/vnext-brain-workspace.tsx
+++ b/apps/web/components/vnext-brain-workspace.tsx
@@ -131,6 +131,11 @@ export function VNextBrainWorkspace({
]);
const [operatorAgentApiKey, setOperatorAgentApiKey] = useState("");
const [operatorAgentApiKeyActive, setOperatorAgentApiKeyActive] = useState(false);
+ const [previousLiveRefreshInputs, setPreviousLiveRefreshInputs] = useState(() => ({
+ apiBaseUrl,
+ liveModeReady,
+ userId,
+ }));
const [captureTitle, setCaptureTitle] = useState("Launch note");
const [captureText, setCaptureText] = useState(
@@ -138,7 +143,8 @@ export function VNextBrainWorkspace({
);
const [defaultDomain, setDefaultDomain] = useState("project");
const [defaultSensitivity, setDefaultSensitivity] = useState("private");
- const [selectedSourceId, setSelectedSourceId] = useState("");
+ const initialSelectedSource = workspace.sources[0] ?? null;
+ const [selectedSourceId, setSelectedSourceId] = useState(initialSelectedSource?.id ?? "");
const selectedSource = useMemo(
() => workspace.sources.find((source) => source.id === selectedSourceId) ?? workspace.sources[0] ?? null,
[selectedSourceId, workspace.sources],
@@ -150,23 +156,56 @@ export function VNextBrainWorkspace({
: null,
[selectedSource, workspace.traceability.items],
);
- const [sourceTitleDraft, setSourceTitleDraft] = useState("");
- const [sourceDomainDraft, setSourceDomainDraft] = useState("project");
- const [sourceSensitivityDraft, setSourceSensitivityDraft] = useState("private");
- const [sourceProjectDraft, setSourceProjectDraft] = useState("");
+ const initialSelectedSourceMetadata = asRecord(initialSelectedSource?.metadata_json);
+ const [sourceTitleDraft, setSourceTitleDraft] = useState(
+ initialSelectedSource
+ ? textValue(initialSelectedSource.title) || initialSelectedSource.source_type
+ : "",
+ );
+ const [sourceDomainDraft, setSourceDomainDraft] = useState(
+ initialSelectedSource ? asDomain(initialSelectedSource.domain) : "project",
+ );
+ const [sourceSensitivityDraft, setSourceSensitivityDraft] = useState(
+ initialSelectedSource ? asSensitivity(initialSelectedSource.sensitivity) : "private",
+ );
+ const [sourceProjectDraft, setSourceProjectDraft] = useState(
+ textValue(initialSelectedSourceMetadata.project_id) || workspace.projects[0]?.id || "",
+ );
const [sourceReviewNote, setSourceReviewNote] = useState("Reviewed from /vnext operator console.");
+ const [previousSourceDraftInputs, setPreviousSourceDraftInputs] = useState(() => ({
+ selectedSource,
+ projects: workspace.projects,
+ }));
- const [selectedReviewId, setSelectedReviewId] = useState("");
+ const initialSelectedReview = workspace.reviewItems[0] ?? null;
+ const [selectedReviewId, setSelectedReviewId] = useState(initialSelectedReview?.id ?? "");
const selectedReview = useMemo(
() => workspace.reviewItems.find((item) => item.id === selectedReviewId) ?? workspace.reviewItems[0] ?? null,
[selectedReviewId, workspace.reviewItems],
);
- const [draftTitle, setDraftTitle] = useState("");
- const [draftText, setDraftText] = useState("");
- const [draftDomain, setDraftDomain] = useState("project");
- const [draftSensitivity, setDraftSensitivity] = useState("private");
- const [selectedProjectId, setSelectedProjectId] = useState("");
+ const initialSelectedReviewMetadata = asRecord(initialSelectedReview?.metadata_json);
+ const [draftTitle, setDraftTitle] = useState(
+ initialSelectedReview
+ ? textValue(initialSelectedReview.title) || memoryText(initialSelectedReview)
+ : "",
+ );
+ const [draftText, setDraftText] = useState(
+ initialSelectedReview ? memoryText(initialSelectedReview) : "",
+ );
+ const [draftDomain, setDraftDomain] = useState(
+ initialSelectedReview ? asDomain(initialSelectedReview.domain) : "project",
+ );
+ const [draftSensitivity, setDraftSensitivity] = useState(
+ initialSelectedReview ? asSensitivity(initialSelectedReview.sensitivity) : "private",
+ );
+ const [selectedProjectId, setSelectedProjectId] = useState(
+ textValue(initialSelectedReviewMetadata.project_id) || workspace.projects[0]?.id || "",
+ );
const [inlineConfirmationDrafts, setInlineConfirmationDrafts] = useState>({});
+ const [previousReviewDraftInputs, setPreviousReviewDraftInputs] = useState(() => ({
+ selectedReview,
+ projects: workspace.projects,
+ }));
const [question, setQuestion] = useState("What should I focus on before the product review?");
const [answer, setAnswer] = useState({
@@ -181,17 +220,25 @@ export function VNextBrainWorkspace({
sensitivity: "private",
});
- const [openLoopTitle, setOpenLoopTitle] = useState("Confirm launch checklist owner");
+ const [openLoopTitle, setOpenLoopTitle] = useState(
+ initialSelectedReview ? memoryText(initialSelectedReview) : "Confirm launch checklist owner",
+ );
const [openLoopDescription, setOpenLoopDescription] = useState("Created from the selected memory candidate.");
const [openLoopDueAt, setOpenLoopDueAt] = useState("");
const [openLoopPriority, setOpenLoopPriority] = useState("normal");
- const [selectedOpenLoopId, setSelectedOpenLoopId] = useState("");
+ const [selectedOpenLoopId, setSelectedOpenLoopId] = useState(workspace.openLoops[0]?.id ?? "");
const [newProjectName, setNewProjectName] = useState("Product launch");
const [newProjectState, setNewProjectState] = useState("Launch ownership is unresolved.");
- const [charterText, setCharterText] = useState("# ALICE.md\n\nKeep generated artifacts reviewable before promotion.");
- const [charterSensitivity, setCharterSensitivity] = useState("private");
+ const [charterText, setCharterText] = useState(
+ workspace.brainCharter?.content_markdown ??
+ "# ALICE.md\n\nKeep generated artifacts reviewable before promotion.",
+ );
+ const [charterSensitivity, setCharterSensitivity] = useState(
+ workspace.brainCharter ? asSensitivity(workspace.brainCharter.sensitivity) : "private",
+ );
+ const [previousBrainCharter, setPreviousBrainCharter] = useState(workspace.brainCharter);
const [schedulerDrafts, setSchedulerDrafts] = useState<
Record
>({});
@@ -208,10 +255,30 @@ export function VNextBrainWorkspace({
const [qualityVerbosity, setQualityVerbosity] = useState("right_sized");
const [qualityComments, setQualityComments] = useState("");
const [selectedConnectorId, setSelectedConnectorId] = useState("telegram");
- const [connectorEnabled, setConnectorEnabled] = useState(true);
- const [connectorDomain, setConnectorDomain] = useState("personal");
- const [connectorSensitivity, setConnectorSensitivity] = useState("private");
- const [connectorSecretRef, setConnectorSecretRef] = useState("");
+ const initialSelectedConnector =
+ INITIAL_CONNECTORS.find((connector) => connector.id === "telegram") ?? INITIAL_CONNECTORS[0];
+ const initialSelectedConnectorHealth =
+ workspace.connectorHealth.items.find(
+ (item) => item.connector_name === initialSelectedConnector.id,
+ ) ?? null;
+ const [connectorEnabled, setConnectorEnabled] = useState(
+ Boolean(initialSelectedConnectorHealth?.enabled ?? false),
+ );
+ const [connectorDomain, setConnectorDomain] = useState(
+ asDomain(initialSelectedConnectorHealth?.default_domain ?? initialSelectedConnector.defaultDomain),
+ );
+ const [connectorSensitivity, setConnectorSensitivity] = useState(
+ asSensitivity(
+ initialSelectedConnectorHealth?.default_sensitivity ?? initialSelectedConnector.defaultSensitivity,
+ ),
+ );
+ const [connectorSecretRef, setConnectorSecretRef] = useState(
+ initialSelectedConnector.id === "browser_clipper" ? "browser.capture_token.default" : "",
+ );
+ const [previousConnectorDraftInputs, setPreviousConnectorDraftInputs] = useState(() => ({
+ connectorHealth: workspace.connectorHealth,
+ selectedConnectorId,
+ }));
const [telegramAllowedChats, setTelegramAllowedChats] = useState("999001");
const [localFolderPath, setLocalFolderPath] = useState("~/Notes");
const [localFolderExtensions, setLocalFolderExtensions] = useState(".md,.txt");
@@ -220,52 +287,32 @@ export function VNextBrainWorkspace({
const [browserClipSelection, setBrowserClipSelection] = useState("Fact: Browser clipper test content remains untrusted.");
const [browserClipperPreparedOrigin, setBrowserClipperPreparedOrigin] = useState("");
const [browserClipperExpiresAt, setBrowserClipperExpiresAt] = useState("");
-
- const dailyArtifact = latestArtifact(workspace.artifacts, "daily_brief");
- const weeklyArtifact = latestArtifact(workspace.artifacts, "weekly_synthesis");
- const projectUpdateArtifacts = workspace.artifacts.filter((artifact) => artifact.artifact_type === "project_update");
-
- const refreshWorkspace = useCallback(
- async (successMessage?: string) => {
- if (!liveModeReady || !apiBaseUrl || !userId) {
- return;
- }
+ const [previousBrowserClipperInputs, setPreviousBrowserClipperInputs] = useState(() => ({
+ apiBaseUrl,
+ browserClipUrl,
+ connectorDomain,
+ connectorSensitivity,
+ userId,
+ }));
+
+ if (
+ apiBaseUrl !== previousLiveRefreshInputs.apiBaseUrl ||
+ liveModeReady !== previousLiveRefreshInputs.liveModeReady ||
+ userId !== previousLiveRefreshInputs.userId
+ ) {
+ setPreviousLiveRefreshInputs({ apiBaseUrl, liveModeReady, userId });
+ if (liveModeReady) {
setIsRefreshing(true);
setStatusTone("info");
setStatusText("Refreshing live vNext workspace...");
- try {
- const payload = await getVNextWorkspace(apiBaseUrl, userId);
- const nextWorkspace = workspaceFromPayload(payload);
- setWorkspace(nextWorkspace);
- setDataSource("live");
- setStatusTone("success");
- setStatusText(successMessage ?? "Live vNext workspace loaded.");
- setActionLog((previous) => pushBoundedLog(successMessage ?? "Live workspace refreshed.", previous));
- } catch (error) {
- setStatusTone("danger");
- setStatusText(`Unable to load live workspace: ${error instanceof Error ? error.message : "Request failed"}`);
- setDataSource("live");
- } finally {
- setIsRefreshing(false);
- }
- },
- [apiBaseUrl, liveModeReady, userId],
- );
-
- useEffect(
- () => () => {
- clearVNextOperatorAgentApiKey();
- },
- [],
- );
-
- useEffect(() => {
- if (liveModeReady) {
- void refreshWorkspace("Live vNext workspace loaded.");
}
- }, [liveModeReady, refreshWorkspace]);
+ }
- useEffect(() => {
+ if (
+ selectedSource !== previousSourceDraftInputs.selectedSource ||
+ workspace.projects !== previousSourceDraftInputs.projects
+ ) {
+ setPreviousSourceDraftInputs({ selectedSource, projects: workspace.projects });
if (selectedSource) {
const metadata = asRecord(selectedSource.metadata_json);
setSelectedSourceId(selectedSource.id);
@@ -278,9 +325,13 @@ export function VNextBrainWorkspace({
setSourceTitleDraft("");
setSourceProjectDraft(workspace.projects[0]?.id ?? "");
}
- }, [selectedSource, workspace.projects]);
+ }
- useEffect(() => {
+ if (
+ selectedReview !== previousReviewDraftInputs.selectedReview ||
+ workspace.projects !== previousReviewDraftInputs.projects
+ ) {
+ setPreviousReviewDraftInputs({ selectedReview, projects: workspace.projects });
if (selectedReview) {
setSelectedReviewId(selectedReview.id);
setDraftTitle(textValue(selectedReview.title) || memoryText(selectedReview));
@@ -296,38 +347,122 @@ export function VNextBrainWorkspace({
setDraftText("");
setSelectedProjectId(workspace.projects[0]?.id ?? "");
}
- }, [selectedReview, workspace.projects]);
+ }
- useEffect(() => {
- if (workspace.openLoops.length > 0 && !workspace.openLoops.some((loop) => loop.id === selectedOpenLoopId)) {
- setSelectedOpenLoopId(workspace.openLoops[0].id);
- }
- }, [selectedOpenLoopId, workspace.openLoops]);
+ if (
+ workspace.openLoops.length > 0 &&
+ !workspace.openLoops.some((loop) => loop.id === selectedOpenLoopId)
+ ) {
+ setSelectedOpenLoopId(workspace.openLoops[0].id);
+ }
- useEffect(() => {
+ if (workspace.brainCharter !== previousBrainCharter) {
+ setPreviousBrainCharter(workspace.brainCharter);
if (workspace.brainCharter) {
setCharterText(workspace.brainCharter.content_markdown);
setCharterSensitivity(asSensitivity(workspace.brainCharter.sensitivity));
}
- }, [workspace.brainCharter]);
+ }
- useEffect(() => {
- const connector = INITIAL_CONNECTORS.find((item) => item.id === selectedConnectorId) ?? INITIAL_CONNECTORS[0];
- const health = workspace.connectorHealth.items.find((item) => item.connector_name === connector.id) ?? null;
+ if (
+ selectedConnectorId !== previousConnectorDraftInputs.selectedConnectorId ||
+ workspace.connectorHealth !== previousConnectorDraftInputs.connectorHealth
+ ) {
+ setPreviousConnectorDraftInputs({
+ connectorHealth: workspace.connectorHealth,
+ selectedConnectorId,
+ });
+ const connector =
+ INITIAL_CONNECTORS.find((item) => item.id === selectedConnectorId) ?? INITIAL_CONNECTORS[0];
+ const health =
+ workspace.connectorHealth.items.find((item) => item.connector_name === connector.id) ?? null;
setConnectorEnabled(Boolean(health?.enabled ?? false));
setConnectorDomain(asDomain(health?.default_domain ?? connector.defaultDomain));
- setConnectorSensitivity(asSensitivity(health?.default_sensitivity ?? connector.defaultSensitivity));
- if (connector.id === "browser_clipper") {
- setConnectorSecretRef("browser.capture_token.default");
- } else {
- setConnectorSecretRef("");
- }
- }, [selectedConnectorId, workspace.connectorHealth]);
+ setConnectorSensitivity(
+ asSensitivity(health?.default_sensitivity ?? connector.defaultSensitivity),
+ );
+ setConnectorSecretRef(
+ connector.id === "browser_clipper" ? "browser.capture_token.default" : "",
+ );
+ }
- useEffect(() => {
+ if (
+ apiBaseUrl !== previousBrowserClipperInputs.apiBaseUrl ||
+ browserClipUrl !== previousBrowserClipperInputs.browserClipUrl ||
+ connectorDomain !== previousBrowserClipperInputs.connectorDomain ||
+ connectorSensitivity !== previousBrowserClipperInputs.connectorSensitivity ||
+ userId !== previousBrowserClipperInputs.userId
+ ) {
+ setPreviousBrowserClipperInputs({
+ apiBaseUrl,
+ browserClipUrl,
+ connectorDomain,
+ connectorSensitivity,
+ userId,
+ });
setBrowserClipperPreparedOrigin("");
setBrowserClipperExpiresAt("");
- }, [apiBaseUrl, browserClipUrl, connectorDomain, connectorSensitivity, userId]);
+ }
+
+ const dailyArtifact = latestArtifact(workspace.artifacts, "daily_brief");
+ const weeklyArtifact = latestArtifact(workspace.artifacts, "weekly_synthesis");
+ const projectUpdateArtifacts = workspace.artifacts.filter((artifact) => artifact.artifact_type === "project_update");
+
+ const loadWorkspace = useCallback(
+ (successMessage?: string) => {
+ if (!liveModeReady || !apiBaseUrl || !userId) {
+ return Promise.resolve();
+ }
+ return getVNextWorkspace(apiBaseUrl, userId)
+ .then((payload) => {
+ const nextWorkspace = workspaceFromPayload(payload);
+ setWorkspace(nextWorkspace);
+ setDataSource("live");
+ setStatusTone("success");
+ setStatusText(successMessage ?? "Live vNext workspace loaded.");
+ setActionLog((previous) =>
+ pushBoundedLog(successMessage ?? "Live workspace refreshed.", previous),
+ );
+ })
+ .catch((error: unknown) => {
+ setStatusTone("danger");
+ setStatusText(
+ `Unable to load live workspace: ${error instanceof Error ? error.message : "Request failed"}`,
+ );
+ setDataSource("live");
+ })
+ .finally(() => {
+ setIsRefreshing(false);
+ });
+ },
+ [apiBaseUrl, liveModeReady, userId],
+ );
+
+ const refreshWorkspace = useCallback(
+ async (successMessage?: string) => {
+ if (!liveModeReady || !apiBaseUrl || !userId) {
+ return;
+ }
+ setIsRefreshing(true);
+ setStatusTone("info");
+ setStatusText("Refreshing live vNext workspace...");
+ await loadWorkspace(successMessage);
+ },
+ [apiBaseUrl, liveModeReady, loadWorkspace, userId],
+ );
+
+ useEffect(
+ () => () => {
+ clearVNextOperatorAgentApiKey();
+ },
+ [],
+ );
+
+ useEffect(() => {
+ if (liveModeReady) {
+ void loadWorkspace("Live vNext workspace loaded.");
+ }
+ }, [liveModeReady, loadWorkspace]);
function handleOperatorAgentApiKeyChange(value: string) {
clearVNextOperatorAgentApiKey();
diff --git a/apps/web/components/vnext-operator-auth.test.tsx b/apps/web/components/vnext-operator-auth.test.tsx
index 80720f42..ec16c4d8 100644
--- a/apps/web/components/vnext-operator-auth.test.tsx
+++ b/apps/web/components/vnext-operator-auth.test.tsx
@@ -64,6 +64,49 @@ describe("VNextBrainWorkspace operator authentication", () => {
vi.unstubAllEnvs();
});
+ it.each([
+ {
+ label: "success",
+ response: new Response(JSON.stringify(EMPTY_LIVE_WORKSPACE), {
+ status: 200,
+ headers: { "Content-Type": "application/json" },
+ }),
+ expectedStatus: "Live vNext workspace loaded.",
+ },
+ {
+ label: "failure",
+ response: new Response(JSON.stringify({ detail: "Workspace offline" }), {
+ status: 503,
+ headers: { "Content-Type": "application/json" },
+ }),
+ expectedStatus: "Unable to load live workspace: Workspace offline",
+ },
+ ])("starts one immediate live request and reaches the $label state", async ({ response, expectedStatus }) => {
+ let resolveWorkspaceRequest: ((value: Response) => void) | undefined;
+ fetchMock.mockImplementation(
+ () =>
+ new Promise((resolve) => {
+ resolveWorkspaceRequest = resolve;
+ }),
+ );
+
+ render(
+ ,
+ );
+
+ expect(fetchMock).toHaveBeenCalledTimes(1);
+ expect(screen.getByText("Loading live vNext workspace from the trusted API.")).toBeInTheDocument();
+ expect(screen.queryByText("Refreshing live vNext workspace...")).not.toBeInTheDocument();
+
+ resolveWorkspaceRequest?.(response);
+ expect((await screen.findAllByText(expectedStatus)).length).toBeGreaterThan(0);
+ expect(fetchMock).toHaveBeenCalledTimes(1);
+ });
+
it("loads the remote same-origin page with the entered key without forwarding it to an evil origin", async () => {
const agentApiKey = "alice_sk_remote_operator_secret";
vi.stubEnv("NEXT_PUBLIC_ALICEBOT_API_BASE_URL", "https://alice.example.com");
@@ -230,6 +273,46 @@ describe("VNextBrainWorkspace operator authentication", () => {
expect(window.sessionStorage.length).toBe(0);
expect(window.location.href).not.toContain(capability);
expect(document.documentElement.outerHTML).not.toContain(capability);
- expect(screen.getByText(/Prepared for https:\/\/example.com/)).toBeInTheDocument();
+ const preparedPattern = /Prepared for https:\/\/example.com/;
+ expect(screen.getByText(preparedPattern)).toBeInTheDocument();
+
+ fireEvent.change(screen.getByLabelText("Selected text"), {
+ target: { value: "Fact: Editing clip content does not invalidate the bound capability." },
+ });
+ expect(screen.getByText(preparedPattern)).toBeInTheDocument();
+
+ fireEvent.change(screen.getByLabelText("Page URL"), {
+ target: { value: "https://example.com/changed" },
+ });
+ expect(screen.queryByText(preparedPattern)).not.toBeInTheDocument();
+
+ fireEvent.change(screen.getByLabelText("Page URL"), {
+ target: { value: "https://example.com/article" },
+ });
+ fireEvent.click(screen.getByRole("button", { name: "Issue and copy one-time bookmarklet" }));
+ await waitFor(() => expect(writeText).toHaveBeenCalledTimes(2));
+ expect(screen.getByText(preparedPattern)).toBeInTheDocument();
+
+ fireEvent.change(
+ screen.getByLabelText("Default domain", { selector: "#vnext-connector-domain" }),
+ { target: { value: "personal" } },
+ );
+ expect(screen.queryByText(preparedPattern)).not.toBeInTheDocument();
+
+ fireEvent.change(
+ screen.getByLabelText("Default domain", { selector: "#vnext-connector-domain" }),
+ { target: { value: "professional" } },
+ );
+ fireEvent.click(screen.getByRole("button", { name: "Issue and copy one-time bookmarklet" }));
+ await waitFor(() => expect(writeText).toHaveBeenCalledTimes(3));
+ expect(screen.getByText(preparedPattern)).toBeInTheDocument();
+
+ fireEvent.change(
+ screen.getByLabelText("Default sensitivity", {
+ selector: "#vnext-connector-sensitivity",
+ }),
+ { target: { value: "confidential" } },
+ );
+ expect(screen.queryByText(preparedPattern)).not.toBeInTheDocument();
});
});
diff --git a/apps/web/components/workflow-memory-writeback-form.tsx b/apps/web/components/workflow-memory-writeback-form.tsx
index 1ccc6f7b..ea63d081 100644
--- a/apps/web/components/workflow-memory-writeback-form.tsx
+++ b/apps/web/components/workflow-memory-writeback-form.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useEffect, useMemo, useState, type FormEvent } from "react";
+import { useMemo, useState, type FormEvent } from "react";
import { useRouter } from "next/navigation";
@@ -93,8 +93,25 @@ export function WorkflowMemoryWritebackForm({
hasPreview: Boolean(preview),
}),
);
-
- useEffect(() => {
+ const [previousStatusInputs, setPreviousStatusInputs] = useState(() => ({
+ evidenceEventCount: evidenceEventIds.length,
+ liveModeReady,
+ preview,
+ source,
+ }));
+
+ if (
+ evidenceEventIds.length !== previousStatusInputs.evidenceEventCount ||
+ liveModeReady !== previousStatusInputs.liveModeReady ||
+ preview !== previousStatusInputs.preview ||
+ source !== previousStatusInputs.source
+ ) {
+ setPreviousStatusInputs({
+ evidenceEventCount: evidenceEventIds.length,
+ liveModeReady,
+ preview,
+ source,
+ });
setStatusTone("info");
setStatusText(
defaultStatusText({
@@ -104,7 +121,7 @@ export function WorkflowMemoryWritebackForm({
hasPreview: Boolean(preview),
}),
);
- }, [evidenceEventIds.length, liveModeReady, preview, source]);
+ }
async function handleSubmit(event: FormEvent) {
event.preventDefault();
diff --git a/apps/web/eslint.config.mjs b/apps/web/eslint.config.mjs
index ec5cc8a5..e9c1cef7 100644
--- a/apps/web/eslint.config.mjs
+++ b/apps/web/eslint.config.mjs
@@ -1,21 +1,10 @@
-import js from "@eslint/js";
-import { FlatCompat } from "@eslint/eslintrc";
-import { dirname } from "node:path";
-import { fileURLToPath } from "node:url";
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = dirname(__filename);
-
-const compat = new FlatCompat({
- baseDirectory: __dirname,
- recommendedConfig: js.configs.recommended,
-});
+import coreWebVitals from "eslint-config-next/core-web-vitals";
const eslintConfig = [
{
ignores: [".next/**", "node_modules/**", "next-env.d.ts"],
},
- ...compat.extends("next/core-web-vitals"),
+ ...coreWebVitals,
];
export default eslintConfig;
diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts
index 830fb594..9edff1c7 100644
--- a/apps/web/next-env.d.ts
+++ b/apps/web/next-env.d.ts
@@ -1,6 +1,6 @@
///
///
-///
+import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/apps/web/package.json b/apps/web/package.json
index c8f0ca32..9ede2dd4 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -26,7 +26,7 @@
"test:mvp:validation-matrix": "vitest run test/periphery-cut.test.ts lib/legacy-surfaces.server.test.ts components/shell-basics.test.tsx app/approvals/page.test.tsx app/tasks/page.test.tsx app/artifacts/page.test.tsx app/gmail/page.test.tsx app/calendar/page.test.tsx app/memories/page.test.tsx app/entities/page.test.tsx components/trace-list.test.tsx lib/api.test.ts"
},
"dependencies": {
- "next": "15.5.21",
+ "next": "16.2.12",
"react": "19.2.7",
"react-dom": "19.2.7"
},
@@ -40,10 +40,10 @@
"@types/react-dom": "19.2.3",
"@vitest/coverage-v8": "3.2.6",
"eslint": "9.39.5",
- "eslint-config-next": "15.5.21",
+ "eslint-config-next": "16.2.12",
"jsdom": "26.1.0",
"semver": "7.8.0",
- "typescript": "5.8.2",
+ "typescript": "6.0.3",
"vitest": "3.2.6"
},
"pnpm": {
diff --git a/apps/web/pnpm-lock.yaml b/apps/web/pnpm-lock.yaml
index 7a5dda13..b7648da7 100644
--- a/apps/web/pnpm-lock.yaml
+++ b/apps/web/pnpm-lock.yaml
@@ -17,8 +17,8 @@ importers:
.:
dependencies:
next:
- specifier: 15.5.21
- version: 15.5.21(@playwright/test@1.55.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ specifier: 16.2.12
+ version: 16.2.12(@babel/core@7.29.7)(@playwright/test@1.55.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
react:
specifier: 19.2.7
version: 19.2.7
@@ -54,8 +54,8 @@ importers:
specifier: 9.39.5
version: 9.39.5
eslint-config-next:
- specifier: 15.5.21
- version: 15.5.21(eslint@9.39.5)(typescript@5.8.2)
+ specifier: 16.2.12
+ version: 16.2.12(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@6.0.3))(eslint@9.39.5)(typescript@6.0.3)
jsdom:
specifier: 26.1.0
version: 26.1.0
@@ -63,8 +63,8 @@ importers:
specifier: 7.8.0
version: 7.8.0
typescript:
- specifier: 5.8.2
- version: 5.8.2
+ specifier: 6.0.3
+ version: 6.0.3
vitest:
specifier: 3.2.6
version: 3.2.6(@types/node@26.1.2)(jsdom@26.1.0)
@@ -90,6 +90,36 @@ packages:
resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
engines: {node: '>=6.9.0'}
+ '@babel/compat-data@7.29.7':
+ resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/core@7.29.7':
+ resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/generator@7.29.7':
+ resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-compilation-targets@7.29.7':
+ resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-globals@7.29.7':
+ resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-imports@7.29.7':
+ resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-transforms@7.29.7':
+ resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
'@babel/helper-string-parser@7.29.7':
resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==}
engines: {node: '>=6.9.0'}
@@ -98,6 +128,14 @@ packages:
resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-validator-option@7.29.7':
+ resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helpers@7.29.7':
+ resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==}
+ engines: {node: '>=6.9.0'}
+
'@babel/parser@7.29.7':
resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==}
engines: {node: '>=6.0.0'}
@@ -107,6 +145,14 @@ packages:
resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==}
engines: {node: '>=6.9.0'}
+ '@babel/template@7.29.7':
+ resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/traverse@7.29.7':
+ resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==}
+ engines: {node: '>=6.9.0'}
+
'@babel/types@7.29.7':
resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==}
engines: {node: '>=6.9.0'}
@@ -523,6 +569,9 @@ packages:
'@jridgewell/gen-mapping@0.3.13':
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+ '@jridgewell/remapping@2.3.5':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
'@jridgewell/resolve-uri@3.1.2':
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
@@ -536,56 +585,56 @@ packages:
'@napi-rs/wasm-runtime@0.2.12':
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
- '@next/env@15.5.21':
- resolution: {integrity: sha512-hjJI/GfrjWHgNguRIBzItjRRu0m3Nrz17GhxsjuHfjIvg9hyg3239REd2dpI+bpMTFuVrVprHzEQ19m++cDtbw==}
+ '@next/env@16.2.12':
+ resolution: {integrity: sha512-d0Z5Bc13Fa4nR8pFAKx2jay2yhJM16vlfHbTzYnUQAxlNb6B6lmn4hjt69lYNt4kRtyYP6gEM49lPRHNbIyneg==}
- '@next/eslint-plugin-next@15.5.21':
- resolution: {integrity: sha512-5MoR9vO3dE1lU3LixogB54xrn7OhMGempTqk1q3WlvbKrT6cNt5mV2mMkX5sghAk0vZF6Nz1HxoTb9YmZv8wRg==}
+ '@next/eslint-plugin-next@16.2.12':
+ resolution: {integrity: sha512-uF2z/qAK2q7B5/6CpnFcBRX6jOq5iCO+Uqh1UkJhXljX1JwLarLYhhoJadO6dPb6moTprOKewMXheBcbIoSbug==}
- '@next/swc-darwin-arm64@15.5.21':
- resolution: {integrity: sha512-ZfjqPEdi6TRC/fWx7UDbwb1fbVgyh2uD5tVTRKIDZDlYM+UNuE/LafDG2fwuAoZilADpABh46OY/F5qf9JjqLQ==}
+ '@next/swc-darwin-arm64@16.2.12':
+ resolution: {integrity: sha512-0W1R0teHWJrqKX0FH20IzzIWAOuGtBxPGuObrxy1lE8hQvCFj49KE8a3WUg0D7sq6rn6zkM4c7YGUnhudBS6oA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
- '@next/swc-darwin-x64@15.5.21':
- resolution: {integrity: sha512-TlCf1NpxgQLzTrexuev75xwmNCJMd1/qkJpTVP1GRRcih93hlIBn1P72hkh8T0gnRFr6BmWksQtbyG3jT6jnww==}
+ '@next/swc-darwin-x64@16.2.12':
+ resolution: {integrity: sha512-Hy5Ls099+aFUmOLmIgPfLqNi6iCwhL3uQCssz5rWk+5Nkc6TUKCE83DY5BbNylfm3+mfwcSFnLRfrZDJhVxdtw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
- '@next/swc-linux-arm64-gnu@15.5.21':
- resolution: {integrity: sha512-LXRsq1p+HvHSi7ygwNcSEEcK0zuo5jS75ZlqFHtOH+LF7qntXAJVJxah+1Pi/GyBm7EpkwU7m4EgbvIKrMqm9A==}
+ '@next/swc-linux-arm64-gnu@16.2.12':
+ resolution: {integrity: sha512-+YqU2h1cQkHsGfvjAsrSmst8UIFBibBGm5x3Xgel8NLMiDQtNOM4sM2GOEMvG5YiOBNeN/Ykk8cQC2S0Xrqljg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
- '@next/swc-linux-arm64-musl@15.5.21':
- resolution: {integrity: sha512-hyGixhFxpDKjqoev6l4KlcRBlt9AXWrGhDZwmwg49sMJM5tnKQPSi+SEj9+e5n+l/bthRGZUdh59GKIs6lQPRw==}
+ '@next/swc-linux-arm64-musl@16.2.12':
+ resolution: {integrity: sha512-0qjhiYBaKAqF63LA1ZWAAnKTzFUguAaZiRa5etMLGGPj/B6uEVjtIZldIzFEp3wHlB0koK6aTzqPtSdplTCjoA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
- '@next/swc-linux-x64-gnu@15.5.21':
- resolution: {integrity: sha512-qfE+YfOba6S2+13e8qn1/UozDVNZ2clBlrs8UtDoax4s8ediu6sq93z66OEHUYlb69Tffh5JTNkgtsAKiSuugg==}
+ '@next/swc-linux-x64-gnu@16.2.12':
+ resolution: {integrity: sha512-7A3q26W+h7gnA15uqBToNuDqBEFZZcqh0mW2mn4AJh/G5pdg2RVE3n4slzLEliASZFG3NmsbEzng/x2Sh09mBg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
- '@next/swc-linux-x64-musl@15.5.21':
- resolution: {integrity: sha512-BXLGG+EvIwp/Rrgl6HY8sqvD6BOUOIRz8/naDbeLNX7mlA5H2XRcL6MW/0IGnJISfj5BA9gNhFyJj5yOoiIDJQ==}
+ '@next/swc-linux-x64-musl@16.2.12':
+ resolution: {integrity: sha512-qSjL/uppm+cbh21s72Ss8gkiOhQ4dExWHNGOWy6eZV7STj5WsKehgxT61beSsOj+YYQuTplL376lOCdMQU5T8w==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
- '@next/swc-win32-arm64-msvc@15.5.21':
- resolution: {integrity: sha512-tNGNOlT0Wn7E4IMsSnufjXN/l2L2/AGdLLpa2vzS89SYCBuihgLn3ngLsIrvndAnWo9nAkus+4gZHTI/Ijx9HA==}
+ '@next/swc-win32-arm64-msvc@16.2.12':
+ resolution: {integrity: sha512-X6hzsOUJac/e7AWSbn9gQ9nzHld1xWP5iyjHpYWvud8pufB679O1xg4JDyKr8Xd69Jvd+kM2Der6uftiZCmjYA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
- '@next/swc-win32-x64-msvc@15.5.21':
- resolution: {integrity: sha512-DmIdWmC9p4rdNIiQqo8ap0+Cnj6kKtTZnuSCxoYydSc8sgpDgAg9wFhxplunak9imLV0pTvc5WVCOHwm5eHLtQ==}
+ '@next/swc-win32-x64-msvc@16.2.12':
+ resolution: {integrity: sha512-F6fakeHuFTLOPt0bslQJdf+xtT+WIP9DVn/m4y1w1mRnVPyh3D/cNvzlRkxM444xfm+IvvYNSOrKiA2CDJ0Uxw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
@@ -743,9 +792,6 @@ packages:
'@rtsao/scc@1.1.0':
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
- '@rushstack/eslint-patch@1.16.1':
- resolution: {integrity: sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==}
-
'@swc/helpers@0.5.15':
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
@@ -1118,6 +1164,11 @@ packages:
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
engines: {node: 18 || 20 || >=22}
+ baseline-browser-mapping@2.11.5:
+ resolution: {integrity: sha512-xJo6a6YZnwZfnyGmQKWMbVOcii7XRibjOskRh+WJ9UHQoX16xrQrcIgAMQOzfvs8XiLMx6ih/fsLPF73iY2D1A==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
brace-expansion@1.1.16:
resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==}
@@ -1132,6 +1183,11 @@ packages:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
+ browserslist@4.28.7:
+ resolution: {integrity: sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
cac@6.7.14:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
@@ -1184,6 +1240,9 @@ packages:
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
@@ -1277,6 +1336,9 @@ packages:
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
+ electron-to-chromium@1.5.397:
+ resolution: {integrity: sha512-khGTy9U9x02KEtsKM8vx5A62BsRmcOsIgDpWr1ImE32Ax8GxHGPHZf+Eu9H8zOOyHJnB0jTbseyTHbq2XCT8yw==}
+
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -1327,14 +1389,18 @@ packages:
engines: {node: '>=18'}
hasBin: true
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
- eslint-config-next@15.5.21:
- resolution: {integrity: sha512-W8m8iFZis5SLqrHVXFv3IrRUpUu95VCHcXRMrMWtxpjV7JHfRB847qL2HJS5wIdLER+k17LWGnihBDRkFBRK8A==}
+ eslint-config-next@16.2.12:
+ resolution: {integrity: sha512-iaaf4vvKo5h2LBdGt0JuRv7t0Ysqr9FMCiFxbptDg8LqOE//mIKR80DdpOnSVM7qjLH3jT8P0aFiwXxBEGZRXw==}
peerDependencies:
- eslint: ^7.23.0 || ^8.0.0 || ^9.0.0
+ eslint: '>=9.0.0'
typescript: '>=3.3.1'
peerDependenciesMeta:
typescript:
@@ -1393,11 +1459,11 @@ packages:
peerDependencies:
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9
- eslint-plugin-react-hooks@5.2.0:
- resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==}
- engines: {node: '>=10'}
+ eslint-plugin-react-hooks@7.1.1:
+ resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==}
+ engines: {node: '>=18'}
peerDependencies:
- eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
+ eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0
eslint-plugin-react@7.37.5:
resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==}
@@ -1534,6 +1600,10 @@ packages:
resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==}
engines: {node: '>= 0.4'}
+ gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+
get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
@@ -1566,6 +1636,10 @@ packages:
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
engines: {node: '>=18'}
+ globals@16.4.0:
+ resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==}
+ engines: {node: '>=18'}
+
globalthis@1.0.4:
resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
engines: {node: '>= 0.4'}
@@ -1601,6 +1675,12 @@ packages:
resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==}
engines: {node: '>= 0.4'}
+ hermes-estree@0.25.1:
+ resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==}
+
+ hermes-parser@0.25.1:
+ resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==}
+
html-encoding-sniffer@4.0.0:
resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
engines: {node: '>=18'}
@@ -1805,6 +1885,11 @@ packages:
canvas:
optional: true
+ jsesc@3.1.0:
+ resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+ engines: {node: '>=6'}
+ hasBin: true
+
json-buffer@3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
@@ -1818,6 +1903,11 @@ packages:
resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
hasBin: true
+ json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+
jsx-ast-utils@3.3.5:
resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
engines: {node: '>=4.0'}
@@ -1856,6 +1946,9 @@ packages:
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+ lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
lz-string@1.5.0:
resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
hasBin: true
@@ -1920,9 +2013,9 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
- next@15.5.21:
- resolution: {integrity: sha512-/TsdBtkWLhkl+NVL3Uqws2UphNd6IPzOtzSk1fHaf+0P7GQKLZDUytyhns/Ykbzdy9+YRjwG7ONvrHaaTDdFqQ==}
- engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
+ next@16.2.12:
+ resolution: {integrity: sha512-iD59eYQWmbFcEbX7v/acG5DRym9iw1DdaPoD0WTA920naWsE25wShzJW4+UvAs8MK9EC2kBfIH6vtto1H1PHGw==}
+ engines: {node: '>=20.9.0'}
hasBin: true
peerDependencies:
'@opentelemetry/api': ^1.1.0
@@ -1945,6 +2038,10 @@ packages:
resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==}
engines: {node: '>= 0.4'}
+ node-releases@2.0.51:
+ resolution: {integrity: sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==}
+ engines: {node: '>=18'}
+
nwsapi@2.2.23:
resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==}
@@ -2384,8 +2481,15 @@ packages:
resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
engines: {node: '>= 0.4'}
- typescript@5.8.2:
- resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==}
+ typescript-eslint@8.65.0:
+ resolution: {integrity: sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.1.0'
+
+ typescript@6.0.3:
+ resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==}
engines: {node: '>=14.17'}
hasBin: true
@@ -2399,6 +2503,12 @@ packages:
unrs-resolver@1.11.1:
resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==}
+ update-browserslist-db@1.2.3:
+ resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
@@ -2553,10 +2663,22 @@ packages:
xmlchars@2.2.0:
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
+ yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
+ zod-validation-error@4.0.2:
+ resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ zod: ^3.25.0 || ^4.0.0
+
+ zod@4.4.3:
+ resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==}
+
snapshots:
'@adobe/css-tools@4.4.4': {}
@@ -2585,16 +2707,97 @@ snapshots:
js-tokens: 4.0.0
picocolors: 1.1.1
+ '@babel/compat-data@7.29.7': {}
+
+ '@babel/core@7.29.7':
+ dependencies:
+ '@babel/code-frame': 7.29.7
+ '@babel/generator': 7.29.7
+ '@babel/helper-compilation-targets': 7.29.7
+ '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7)
+ '@babel/helpers': 7.29.7
+ '@babel/parser': 7.29.7
+ '@babel/template': 7.29.7
+ '@babel/traverse': 7.29.7
+ '@babel/types': 7.29.7
+ '@jridgewell/remapping': 2.3.5
+ convert-source-map: 2.0.0
+ debug: 4.4.3
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/generator@7.29.7':
+ dependencies:
+ '@babel/parser': 7.29.7
+ '@babel/types': 7.29.7
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+ jsesc: 3.1.0
+
+ '@babel/helper-compilation-targets@7.29.7':
+ dependencies:
+ '@babel/compat-data': 7.29.7
+ '@babel/helper-validator-option': 7.29.7
+ browserslist: 4.28.7
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ '@babel/helper-globals@7.29.7': {}
+
+ '@babel/helper-module-imports@7.29.7':
+ dependencies:
+ '@babel/traverse': 7.29.7
+ '@babel/types': 7.29.7
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)':
+ dependencies:
+ '@babel/core': 7.29.7
+ '@babel/helper-module-imports': 7.29.7
+ '@babel/helper-validator-identifier': 7.29.7
+ '@babel/traverse': 7.29.7
+ transitivePeerDependencies:
+ - supports-color
+
'@babel/helper-string-parser@7.29.7': {}
'@babel/helper-validator-identifier@7.29.7': {}
+ '@babel/helper-validator-option@7.29.7': {}
+
+ '@babel/helpers@7.29.7':
+ dependencies:
+ '@babel/template': 7.29.7
+ '@babel/types': 7.29.7
+
'@babel/parser@7.29.7':
dependencies:
'@babel/types': 7.29.7
'@babel/runtime@7.29.2': {}
+ '@babel/template@7.29.7':
+ dependencies:
+ '@babel/code-frame': 7.29.7
+ '@babel/parser': 7.29.7
+ '@babel/types': 7.29.7
+
+ '@babel/traverse@7.29.7':
+ dependencies:
+ '@babel/code-frame': 7.29.7
+ '@babel/generator': 7.29.7
+ '@babel/helper-globals': 7.29.7
+ '@babel/parser': 7.29.7
+ '@babel/template': 7.29.7
+ '@babel/types': 7.29.7
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
'@babel/types@7.29.7':
dependencies:
'@babel/helper-string-parser': 7.29.7
@@ -2901,6 +3104,11 @@ snapshots:
'@jridgewell/sourcemap-codec': 1.5.5
'@jridgewell/trace-mapping': 0.3.31
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
'@jridgewell/resolve-uri@3.1.2': {}
'@jridgewell/sourcemap-codec@1.5.5': {}
@@ -2917,34 +3125,34 @@ snapshots:
'@tybys/wasm-util': 0.10.2
optional: true
- '@next/env@15.5.21': {}
+ '@next/env@16.2.12': {}
- '@next/eslint-plugin-next@15.5.21':
+ '@next/eslint-plugin-next@16.2.12':
dependencies:
fast-glob: 3.3.1
- '@next/swc-darwin-arm64@15.5.21':
+ '@next/swc-darwin-arm64@16.2.12':
optional: true
- '@next/swc-darwin-x64@15.5.21':
+ '@next/swc-darwin-x64@16.2.12':
optional: true
- '@next/swc-linux-arm64-gnu@15.5.21':
+ '@next/swc-linux-arm64-gnu@16.2.12':
optional: true
- '@next/swc-linux-arm64-musl@15.5.21':
+ '@next/swc-linux-arm64-musl@16.2.12':
optional: true
- '@next/swc-linux-x64-gnu@15.5.21':
+ '@next/swc-linux-x64-gnu@16.2.12':
optional: true
- '@next/swc-linux-x64-musl@15.5.21':
+ '@next/swc-linux-x64-musl@16.2.12':
optional: true
- '@next/swc-win32-arm64-msvc@15.5.21':
+ '@next/swc-win32-arm64-msvc@16.2.12':
optional: true
- '@next/swc-win32-x64-msvc@15.5.21':
+ '@next/swc-win32-x64-msvc@16.2.12':
optional: true
'@nodelib/fs.scandir@2.1.5':
@@ -3045,8 +3253,6 @@ snapshots:
'@rtsao/scc@1.1.0': {}
- '@rushstack/eslint-patch@1.16.1': {}
-
'@swc/helpers@0.5.15':
dependencies:
tslib: 2.8.1
@@ -3116,40 +3322,40 @@ snapshots:
dependencies:
csstype: 3.2.3
- '@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@5.8.2))(eslint@9.39.5)(typescript@5.8.2)':
+ '@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@6.0.3))(eslint@9.39.5)(typescript@6.0.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
- '@typescript-eslint/parser': 8.65.0(eslint@9.39.5)(typescript@5.8.2)
+ '@typescript-eslint/parser': 8.65.0(eslint@9.39.5)(typescript@6.0.3)
'@typescript-eslint/scope-manager': 8.65.0
- '@typescript-eslint/type-utils': 8.65.0(eslint@9.39.5)(typescript@5.8.2)
- '@typescript-eslint/utils': 8.65.0(eslint@9.39.5)(typescript@5.8.2)
+ '@typescript-eslint/type-utils': 8.65.0(eslint@9.39.5)(typescript@6.0.3)
+ '@typescript-eslint/utils': 8.65.0(eslint@9.39.5)(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.65.0
eslint: 9.39.5
ignore: 7.0.5
natural-compare: 1.4.0
- ts-api-utils: 2.5.0(typescript@5.8.2)
- typescript: 5.8.2
+ ts-api-utils: 2.5.0(typescript@6.0.3)
+ typescript: 6.0.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@5.8.2)':
+ '@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@6.0.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.65.0
'@typescript-eslint/types': 8.65.0
- '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.8.2)
+ '@typescript-eslint/typescript-estree': 8.65.0(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.65.0
debug: 4.4.3
eslint: 9.39.5
- typescript: 5.8.2
+ typescript: 6.0.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/project-service@8.65.0(typescript@5.8.2)':
+ '@typescript-eslint/project-service@8.65.0(typescript@6.0.3)':
dependencies:
- '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.8.2)
+ '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@6.0.3)
'@typescript-eslint/types': 8.65.0
debug: 4.4.3
- typescript: 5.8.2
+ typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -3158,47 +3364,47 @@ snapshots:
'@typescript-eslint/types': 8.65.0
'@typescript-eslint/visitor-keys': 8.65.0
- '@typescript-eslint/tsconfig-utils@8.65.0(typescript@5.8.2)':
+ '@typescript-eslint/tsconfig-utils@8.65.0(typescript@6.0.3)':
dependencies:
- typescript: 5.8.2
+ typescript: 6.0.3
- '@typescript-eslint/type-utils@8.65.0(eslint@9.39.5)(typescript@5.8.2)':
+ '@typescript-eslint/type-utils@8.65.0(eslint@9.39.5)(typescript@6.0.3)':
dependencies:
'@typescript-eslint/types': 8.65.0
- '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.8.2)
- '@typescript-eslint/utils': 8.65.0(eslint@9.39.5)(typescript@5.8.2)
+ '@typescript-eslint/typescript-estree': 8.65.0(typescript@6.0.3)
+ '@typescript-eslint/utils': 8.65.0(eslint@9.39.5)(typescript@6.0.3)
debug: 4.4.3
eslint: 9.39.5
- ts-api-utils: 2.5.0(typescript@5.8.2)
- typescript: 5.8.2
+ ts-api-utils: 2.5.0(typescript@6.0.3)
+ typescript: 6.0.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/types@8.65.0': {}
- '@typescript-eslint/typescript-estree@8.65.0(typescript@5.8.2)':
+ '@typescript-eslint/typescript-estree@8.65.0(typescript@6.0.3)':
dependencies:
- '@typescript-eslint/project-service': 8.65.0(typescript@5.8.2)
- '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@5.8.2)
+ '@typescript-eslint/project-service': 8.65.0(typescript@6.0.3)
+ '@typescript-eslint/tsconfig-utils': 8.65.0(typescript@6.0.3)
'@typescript-eslint/types': 8.65.0
'@typescript-eslint/visitor-keys': 8.65.0
debug: 4.4.3
minimatch: 10.2.5
semver: 7.8.0
tinyglobby: 0.2.16
- ts-api-utils: 2.5.0(typescript@5.8.2)
- typescript: 5.8.2
+ ts-api-utils: 2.5.0(typescript@6.0.3)
+ typescript: 6.0.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@8.65.0(eslint@9.39.5)(typescript@5.8.2)':
+ '@typescript-eslint/utils@8.65.0(eslint@9.39.5)(typescript@6.0.3)':
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5)
'@typescript-eslint/scope-manager': 8.65.0
'@typescript-eslint/types': 8.65.0
- '@typescript-eslint/typescript-estree': 8.65.0(typescript@5.8.2)
+ '@typescript-eslint/typescript-estree': 8.65.0(typescript@6.0.3)
eslint: 9.39.5
- typescript: 5.8.2
+ typescript: 6.0.3
transitivePeerDependencies:
- supports-color
@@ -3459,6 +3665,8 @@ snapshots:
balanced-match@4.0.4: {}
+ baseline-browser-mapping@2.11.5: {}
+
brace-expansion@1.1.16:
dependencies:
balanced-match: 1.0.2
@@ -3476,6 +3684,14 @@ snapshots:
dependencies:
fill-range: 7.1.1
+ browserslist@4.28.7:
+ dependencies:
+ baseline-browser-mapping: 2.11.5
+ caniuse-lite: 1.0.30001806
+ electron-to-chromium: 1.5.397
+ node-releases: 2.0.51
+ update-browserslist-db: 1.2.3(browserslist@4.28.7)
+
cac@6.7.14: {}
call-bind-apply-helpers@1.0.2:
@@ -3529,6 +3745,8 @@ snapshots:
concat-map@0.0.1: {}
+ convert-source-map@2.0.0: {}
+
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
@@ -3616,6 +3834,8 @@ snapshots:
eastasianwidth@0.2.0: {}
+ electron-to-chromium@1.5.397: {}
+
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
@@ -3754,24 +3974,26 @@ snapshots:
'@esbuild/win32-ia32': 0.25.12
'@esbuild/win32-x64': 0.25.12
+ escalade@3.2.0: {}
+
escape-string-regexp@4.0.0: {}
- eslint-config-next@15.5.21(eslint@9.39.5)(typescript@5.8.2):
+ eslint-config-next@16.2.12(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@6.0.3))(eslint@9.39.5)(typescript@6.0.3):
dependencies:
- '@next/eslint-plugin-next': 15.5.21
- '@rushstack/eslint-patch': 1.16.1
- '@typescript-eslint/eslint-plugin': 8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@5.8.2))(eslint@9.39.5)(typescript@5.8.2)
- '@typescript-eslint/parser': 8.65.0(eslint@9.39.5)(typescript@5.8.2)
+ '@next/eslint-plugin-next': 16.2.12
eslint: 9.39.5
eslint-import-resolver-node: 0.3.10
- eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5)
- eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@5.8.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5)
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@6.0.3))(eslint@9.39.5))(eslint@9.39.5)
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@6.0.3))(eslint@9.39.5))(eslint@9.39.5))(eslint@9.39.5)
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.5)
eslint-plugin-react: 7.37.5(eslint@9.39.5)
- eslint-plugin-react-hooks: 5.2.0(eslint@9.39.5)
+ eslint-plugin-react-hooks: 7.1.1(eslint@9.39.5)
+ globals: 16.4.0
+ typescript-eslint: 8.65.0(eslint@9.39.5)(typescript@6.0.3)
optionalDependencies:
- typescript: 5.8.2
+ typescript: 6.0.3
transitivePeerDependencies:
+ - '@typescript-eslint/parser'
- eslint-import-resolver-webpack
- eslint-plugin-import-x
- supports-color
@@ -3784,7 +4006,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5):
+ eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@6.0.3))(eslint@9.39.5))(eslint@9.39.5):
dependencies:
'@nolyfill/is-core-module': 1.0.39
debug: 4.4.3
@@ -3795,22 +4017,22 @@ snapshots:
tinyglobby: 0.2.16
unrs-resolver: 1.11.1
optionalDependencies:
- eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@5.8.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5)
+ eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@6.0.3))(eslint@9.39.5))(eslint@9.39.5))(eslint@9.39.5)
transitivePeerDependencies:
- supports-color
- eslint-module-utils@2.12.1(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@5.8.2))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5):
+ eslint-module-utils@2.12.1(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@6.0.3))(eslint@9.39.5))(eslint@9.39.5))(eslint@9.39.5):
dependencies:
debug: 3.2.7
optionalDependencies:
- '@typescript-eslint/parser': 8.65.0(eslint@9.39.5)(typescript@5.8.2)
+ '@typescript-eslint/parser': 8.65.0(eslint@9.39.5)(typescript@6.0.3)
eslint: 9.39.5
eslint-import-resolver-node: 0.3.10
- eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5)
+ eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@6.0.3))(eslint@9.39.5))(eslint@9.39.5)
transitivePeerDependencies:
- supports-color
- eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@5.8.2))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5):
+ eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@6.0.3))(eslint@9.39.5))(eslint@9.39.5))(eslint@9.39.5):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.9
@@ -3821,7 +4043,7 @@ snapshots:
doctrine: 2.1.0
eslint: 9.39.5
eslint-import-resolver-node: 0.3.10
- eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@5.8.2))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5)
+ eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@6.0.3))(eslint@9.39.5))(eslint@9.39.5))(eslint@9.39.5)
hasown: 2.0.3
is-core-module: 2.16.2
is-glob: 4.0.3
@@ -3833,7 +4055,7 @@ snapshots:
string.prototype.trimend: 1.0.9
tsconfig-paths: 3.15.0
optionalDependencies:
- '@typescript-eslint/parser': 8.65.0(eslint@9.39.5)(typescript@5.8.2)
+ '@typescript-eslint/parser': 8.65.0(eslint@9.39.5)(typescript@6.0.3)
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
@@ -3858,9 +4080,16 @@ snapshots:
safe-regex-test: 1.1.0
string.prototype.includes: 2.0.1
- eslint-plugin-react-hooks@5.2.0(eslint@9.39.5):
+ eslint-plugin-react-hooks@7.1.1(eslint@9.39.5):
dependencies:
+ '@babel/core': 7.29.7
+ '@babel/parser': 7.29.7
eslint: 9.39.5
+ hermes-parser: 0.25.1
+ zod: 4.4.3
+ zod-validation-error: 4.0.2(zod@4.4.3)
+ transitivePeerDependencies:
+ - supports-color
eslint-plugin-react@7.37.5(eslint@9.39.5):
dependencies:
@@ -4030,6 +4259,8 @@ snapshots:
generator-function@2.0.1: {}
+ gensync@1.0.0-beta.2: {}
+
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -4077,6 +4308,8 @@ snapshots:
globals@14.0.0: {}
+ globals@16.4.0: {}
+
globalthis@1.0.4:
dependencies:
define-properties: 1.2.1
@@ -4106,6 +4339,12 @@ snapshots:
dependencies:
function-bind: 1.1.2
+ hermes-estree@0.25.1: {}
+
+ hermes-parser@0.25.1:
+ dependencies:
+ hermes-estree: 0.25.1
+
html-encoding-sniffer@4.0.0:
dependencies:
whatwg-encoding: 3.1.1
@@ -4342,6 +4581,8 @@ snapshots:
- supports-color
- utf-8-validate
+ jsesc@3.1.0: {}
+
json-buffer@3.0.1: {}
json-schema-traverse@0.4.1: {}
@@ -4352,6 +4593,8 @@ snapshots:
dependencies:
minimist: 1.2.8
+ json5@2.2.3: {}
+
jsx-ast-utils@3.3.5:
dependencies:
array-includes: 3.1.9
@@ -4390,6 +4633,10 @@ snapshots:
lru-cache@10.4.3: {}
+ lru-cache@5.1.1:
+ dependencies:
+ yallist: 3.1.1
+
lz-string@1.5.0: {}
magic-string@0.30.21:
@@ -4441,24 +4688,25 @@ snapshots:
natural-compare@1.4.0: {}
- next@15.5.21(@playwright/test@1.55.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
+ next@16.2.12(@babel/core@7.29.7)(@playwright/test@1.55.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
dependencies:
- '@next/env': 15.5.21
+ '@next/env': 16.2.12
'@swc/helpers': 0.5.15
+ baseline-browser-mapping: 2.11.5
caniuse-lite: 1.0.30001806
postcss: 8.5.18
react: 19.2.7
react-dom: 19.2.7(react@19.2.7)
- styled-jsx: 5.1.6(react@19.2.7)
+ styled-jsx: 5.1.6(@babel/core@7.29.7)(react@19.2.7)
optionalDependencies:
- '@next/swc-darwin-arm64': 15.5.21
- '@next/swc-darwin-x64': 15.5.21
- '@next/swc-linux-arm64-gnu': 15.5.21
- '@next/swc-linux-arm64-musl': 15.5.21
- '@next/swc-linux-x64-gnu': 15.5.21
- '@next/swc-linux-x64-musl': 15.5.21
- '@next/swc-win32-arm64-msvc': 15.5.21
- '@next/swc-win32-x64-msvc': 15.5.21
+ '@next/swc-darwin-arm64': 16.2.12
+ '@next/swc-darwin-x64': 16.2.12
+ '@next/swc-linux-arm64-gnu': 16.2.12
+ '@next/swc-linux-arm64-musl': 16.2.12
+ '@next/swc-linux-x64-gnu': 16.2.12
+ '@next/swc-linux-x64-musl': 16.2.12
+ '@next/swc-win32-arm64-msvc': 16.2.12
+ '@next/swc-win32-x64-msvc': 16.2.12
'@playwright/test': 1.55.1
sharp: 0.35.0
transitivePeerDependencies:
@@ -4472,6 +4720,8 @@ snapshots:
object.entries: 1.1.9
semver: 6.3.1
+ node-releases@2.0.51: {}
+
nwsapi@2.2.23: {}
object-assign@4.1.1: {}
@@ -4914,10 +5164,12 @@ snapshots:
dependencies:
js-tokens: 9.0.1
- styled-jsx@5.1.6(react@19.2.7):
+ styled-jsx@5.1.6(@babel/core@7.29.7)(react@19.2.7):
dependencies:
client-only: 0.0.1
react: 19.2.7
+ optionalDependencies:
+ '@babel/core': 7.29.7
supports-color@7.2.0:
dependencies:
@@ -4966,9 +5218,9 @@ snapshots:
dependencies:
punycode: 2.3.1
- ts-api-utils@2.5.0(typescript@5.8.2):
+ ts-api-utils@2.5.0(typescript@6.0.3):
dependencies:
- typescript: 5.8.2
+ typescript: 6.0.3
tsconfig-paths@3.15.0:
dependencies:
@@ -5016,7 +5268,18 @@ snapshots:
possible-typed-array-names: 1.1.0
reflect.getprototypeof: 1.0.10
- typescript@5.8.2: {}
+ typescript-eslint@8.65.0(eslint@9.39.5)(typescript@6.0.3):
+ dependencies:
+ '@typescript-eslint/eslint-plugin': 8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5)(typescript@6.0.3))(eslint@9.39.5)(typescript@6.0.3)
+ '@typescript-eslint/parser': 8.65.0(eslint@9.39.5)(typescript@6.0.3)
+ '@typescript-eslint/typescript-estree': 8.65.0(typescript@6.0.3)
+ '@typescript-eslint/utils': 8.65.0(eslint@9.39.5)(typescript@6.0.3)
+ eslint: 9.39.5
+ typescript: 6.0.3
+ transitivePeerDependencies:
+ - supports-color
+
+ typescript@6.0.3: {}
unbox-primitive@1.1.0:
dependencies:
@@ -5051,6 +5314,12 @@ snapshots:
'@unrs/resolver-binding-win32-ia32-msvc': 1.11.1
'@unrs/resolver-binding-win32-x64-msvc': 1.11.1
+ update-browserslist-db@1.2.3(browserslist@4.28.7):
+ dependencies:
+ browserslist: 4.28.7
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
uri-js@4.4.1:
dependencies:
punycode: 2.3.1
@@ -5217,4 +5486,12 @@ snapshots:
xmlchars@2.2.0: {}
+ yallist@3.1.1: {}
+
yocto-queue@0.1.0: {}
+
+ zod-validation-error@4.0.2(zod@4.4.3):
+ dependencies:
+ zod: 4.4.3
+
+ zod@4.4.3: {}
diff --git a/apps/web/scripts/check-bundle-budget.mjs b/apps/web/scripts/check-bundle-budget.mjs
index 0b0be42f..2f92481b 100644
--- a/apps/web/scripts/check-bundle-budget.mjs
+++ b/apps/web/scripts/check-bundle-budget.mjs
@@ -1,21 +1,37 @@
import { readFile, stat } from "node:fs/promises";
import { gzipSync } from "node:zlib";
-const manifestPath = new URL("../.next/app-build-manifest.json", import.meta.url);
+const legacyManifestPath = new URL("../.next/app-build-manifest.json", import.meta.url);
+const routeStatsPath = new URL("../.next/diagnostics/route-bundle-stats.json", import.meta.url);
const nextRoot = new URL("../.next/", import.meta.url);
-const budgets = {
- "/page": 120_000,
- "/continuity/page": 130_000,
- "/vnext/page": 155_000,
-};
+const budgets = [
+ { legacyRoute: "/page", route: "/", budget: 164_000 },
+ { legacyRoute: "/continuity/page", route: "/continuity", budget: 174_000 },
+ { legacyRoute: "/vnext/page", route: "/vnext", budget: 199_000 },
+];
-const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
+let legacyManifest = null;
+try {
+ legacyManifest = JSON.parse(await readFile(legacyManifestPath, "utf8"));
+} catch (error) {
+ if (!(error instanceof Error) || !Object.hasOwn(error, "code") || error.code !== "ENOENT") {
+ throw error;
+ }
+}
+
+const routeStats = legacyManifest
+ ? null
+ : JSON.parse(await readFile(routeStatsPath, "utf8"));
let failed = false;
-for (const [route, budget] of Object.entries(budgets)) {
- const assets = manifest.pages?.[route];
+for (const { legacyRoute, route, budget } of budgets) {
+ const assets = legacyManifest
+ ? legacyManifest.pages?.[legacyRoute]
+ : routeStats
+ .find((entry) => entry.route === route)
+ ?.firstLoadChunkPaths.map((asset) => asset.replace(/^\.next\//, ""));
if (!Array.isArray(assets)) {
- throw new Error(`Bundle manifest is missing expected app route ${route}`);
+ throw new Error(`Bundle metadata is missing expected app route ${route}`);
}
const javascriptAssets = [...new Set(assets.filter((asset) => asset.endsWith(".js")))];
diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json
index b5692538..cab78cfb 100644
--- a/apps/web/tsconfig.json
+++ b/apps/web/tsconfig.json
@@ -14,7 +14,7 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
- "jsx": "preserve",
+ "jsx": "react-jsx",
"incremental": true,
"esModuleInterop": true,
"plugins": [
@@ -27,7 +27,8 @@
"**/*.ts",
"**/*.tsx",
"next-env.d.ts",
- ".next/types/**/*.ts"
+ ".next/types/**/*.ts",
+ ".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules"