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"