({
+ data,
+ rowRenderer,
+ rowHeight,
+}: {
+ data: T[]
+ rowRenderer: (row: T, idx: number) => React.ReactNode
+ rowHeight: number
+}) {
+ const itemData = React.useMemo(() => ({ data, rowRenderer }), [data, rowRenderer])
+
+ const Row = ({ index, style }: ListChildComponentProps) => {
+ const { data, rowRenderer } = itemData as any
+ return {rowRenderer(data[index], index)}
+ }
+
+ const height = Math.min(400, data.length * rowHeight)
+
+ return (
+
+ {Row}
+
+ )
+}
diff --git a/src/components/Charts/AreaChart.tsx b/src/components/Charts/AreaChart.tsx
index 3bb8bf5..59ad8f6 100644
--- a/src/components/Charts/AreaChart.tsx
+++ b/src/components/Charts/AreaChart.tsx
@@ -1,7 +1,10 @@
"use client"
+import { useMediaQuery } from "@mui/material"
import { Highchart, HighchartOptions } from "./Highchart"
import { createOptionsForStat } from "@/components/Charts/defaultOptions"
+import { theme } from "../RootLayout/theme" // Import theme for breakpoints
+
import { HighchartAreaData } from "@/types"
@@ -12,12 +15,16 @@ type AreaChartProps = {
}
export const AreaChart = ({ data, titleText, overrideValue }: AreaChartProps) => {
+ const isSmallChart = useMediaQuery(theme.breakpoints.down("sm")) // Use sm breakpoint for "small" variant
+
const options: HighchartOptions = createOptionsForStat(
titleText,
- 150,
- undefined,
+ 150, // Chart height remains fixed for these stat charts
+ undefined, // Chart width is responsive by default
data,
overrideValue,
+ false, // exportServer
+ isSmallChart ? "small" : "normal", // Pass sizeVariant
)
return
diff --git a/src/components/Charts/defaultOptions.ts b/src/components/Charts/defaultOptions.ts
index 762bc5e..99f58f7 100644
--- a/src/components/Charts/defaultOptions.ts
+++ b/src/components/Charts/defaultOptions.ts
@@ -134,15 +134,18 @@ export function createOptionsForStat(
data: HighchartAreaData[],
overrideValue: number | undefined,
exportServer = false,
+ sizeVariant: "normal" | "small" = "normal", // New parameter
): HighchartOptions {
const fontColor = exportServer ? "rgb(255, 255, 255)" : "var(--mui-palette-text-primary)"
const backgroundColor = exportServer ? "#252424" : "transparent"
const fontSizes = {
- title: exportServer ? "36px" : "12px",
- value: exportServer ? "96px" : "32px",
+ title: exportServer ? "36px" : (sizeVariant === "small" ? "11px" : "12px"), // Slightly smaller title for small
+ value: exportServer ? "96px" : (sizeVariant === "small" ? "24px" : "32px"), // Adjusted value font size
}
+ const valueYPosition = exportServer ? 208 : (sizeVariant === "small" ? 98 : 104); // Adjust Y position slightly for smaller font
+
return {
title: {
align: "left",
@@ -190,7 +193,7 @@ export function createOptionsForStat(
{
point: {
x: 0,
- y: exportServer ? 208 : 104,
+ y: valueYPosition, // Use the calculated Y position
xAxis: null,
yAxis: null,
},
diff --git a/src/components/CopyToClipboard.tsx b/src/components/CopyToClipboard.tsx
index 79b76a6..0dac67f 100644
--- a/src/components/CopyToClipboard.tsx
+++ b/src/components/CopyToClipboard.tsx
@@ -1,47 +1,92 @@
"use client"
-import { Box, Tooltip } from "@mui/material"
+import { Tooltip, IconButton } from "@mui/material"
import { Check, Copy } from "@phosphor-icons/react"
import React from "react"
+// Add CSS for hover/touch behavior
+const styles = `
+.copy-container {
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+}
+.copy-btn {
+ opacity: 0;
+ transition: opacity 0.2s;
+ pointer-events: none;
+}
+.copy-container:hover .copy-btn,
+.copy-container:focus-within .copy-btn,
+.copy-container.show-copy .copy-btn {
+ opacity: 1;
+ pointer-events: auto;
+}
+`;
+
+// Inject the style tag once
+if (typeof window !== "undefined" && !document.getElementById("copy-to-clipboard-style")) {
+ const style = document.createElement("style");
+ style.id = "copy-to-clipboard-style";
+ style.innerHTML = styles;
+ document.head.appendChild(style);
+}
+
type CopyToClipboardProps = {
value: string
}
export function CopyToClipboard(props: CopyToClipboardProps) {
const { value } = props
-
const [copied, setCopied] = React.useState(false)
+ const [showCopy, setShowCopy] = React.useState(false)
+ const touchTimeout = React.useRef(null)
if (!value) return null
+ // Show copy button for a short time on touch (mobile)
+ const handleTouchStart = (_e: React.TouchEvent) => {
+ setShowCopy(true)
+ if (touchTimeout.current) clearTimeout(touchTimeout.current)
+ touchTimeout.current = setTimeout(() => setShowCopy(false), 2000)
+ }
+
return (
-
- {
- event.stopPropagation()
- navigator.clipboard.writeText(value)
-
- setCopied(true)
-
- setTimeout(() => {
- setCopied(false)
- }, 1000)
- }}
- >
- {copied ? (
-
- ) : (
-
- )}
-
-
+
+
+
+ {
+ event.stopPropagation()
+ navigator.clipboard.writeText(value)
+ setCopied(true)
+ setTimeout(() => {
+ setCopied(false)
+ }, 1500)
+ }}
+ sx={{
+ color: "var(--mui-palette-text-primary)",
+ "& .MuiSvgIcon-root": {
+ fontSize: 18,
+ }
+ }}
+ aria-label="Copy to clipboard"
+ >
+ {copied ? (
+
+ ) : (
+
+ )}
+
+
+
+
)
}
diff --git a/src/components/ErrorView.tsx b/src/components/ErrorView.tsx
new file mode 100644
index 0000000..d35fdb7
--- /dev/null
+++ b/src/components/ErrorView.tsx
@@ -0,0 +1,23 @@
+import { Alert, AlertTitle, Button, Stack } from "@mui/material"
+import React from "react"
+
+interface Props {
+ message?: string
+ onRetry?: () => void
+}
+
+export default function ErrorView({ message = "Something went wrong.", onRetry }: Props) {
+ return (
+
+
+ Error
+ {message}
+ {onRetry && (
+
+ )}
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx
index 6ed45ec..5087296 100644
--- a/src/components/Footer.tsx
+++ b/src/components/Footer.tsx
@@ -23,8 +23,17 @@ export function Footer() {
borderTopRightRadius: 8,
}}
>
-
-
+
+
-
+ {/* Spacer for mobile */}
+
+
Powered by
@@ -187,4 +198,4 @@ export function Footer() {
)
-}
+}
\ No newline at end of file
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
index f869092..79e89e2 100644
--- a/src/components/Header.tsx
+++ b/src/components/Header.tsx
@@ -12,27 +12,33 @@ import {
Link as MuiLink,
useScrollTrigger,
Box,
+ useMediaQuery,
+ Drawer,
+ List,
+ ListItem,
+ ListItemButton,
+ ListItemText,
+ ListItemIcon,
+ InputAdornment,
+ TextField,
} from "@mui/material"
-import { Moon, Sun } from "@phosphor-icons/react"
-
-import { Link } from "react-router-dom"
+import { Moon, Sun, List as MenuIcon, XCircle, MagnifyingGlass, ArrowLeft } from "@phosphor-icons/react"
+import { Link, useNavigate } from "react-router-dom"
+import { useState, useCallback } from "react" // Imported useCallback
import { Logo } from "./Logo"
import { MainFontFF } from "./RootLayout/fonts"
import SearchBar from "@/app/SearchBar"
import { usePrimaryArnsName } from "@/hooks/usePrimaryArnsName"
import { useArnsLogo } from "@/hooks/useArnsLogo"
+import { theme } from "./RootLayout/theme" // Import theme for breakpoints
const ProfileButton = () => {
const activeAddress = useActiveAddress()
const { data: arnsName, isLoading: isLoadingName } = usePrimaryArnsName(activeAddress || "")
const { data: logoTxId, isLoading: isLoadingLogo } = useArnsLogo(arnsName || "")
- // Debug logging
- console.log("Profile Debug:", { activeAddress, arnsName, logoTxId, isLoadingName, isLoadingLogo })
-
const handleProfileClick = () => {
- // Find and click the hidden ConnectButton to trigger its modal
const connectButton = document.getElementById('hidden-connect-button')
if (connectButton) {
connectButton.click()
@@ -134,8 +140,20 @@ const ProfileButton = () => {
)
}
+const navItems = [
+ { label: "PROCESSES", path: "/processes" },
+ { label: "MODULES", path: "/modules" },
+ { label: "BLOCKS", path: "/blocks" },
+ { label: "ARNS", path: "/arns" },
+]
+
const Header = () => {
const { mode = "dark", setMode } = useColorScheme()
+ const navigate = useNavigate()
+ const [drawerOpen, setDrawerOpen] = useState(false)
+ const [mobileSearchActive, setMobileSearchActive] = useState(false) // State for mobile search visibility
+ const isMobile = useMediaQuery(theme.breakpoints.down("md"))
+ const isLargeScreen = useMediaQuery(theme.breakpoints.up("lg"))
const elevated = useScrollTrigger({
disableHysteresis: true,
@@ -143,153 +161,189 @@ const Header = () => {
target: typeof window !== "undefined" ? window : undefined,
})
- return (
- (event: React.KeyboardEvent | React.MouseEvent) => {
+ if (
+ event.type === "keydown" &&
+ ((event as React.KeyboardEvent).key === "Tab" || (event as React.KeyboardEvent).key === "Shift")
+ ) {
+ return
+ }
+ setDrawerOpen(open)
+ }, [setDrawerOpen]) // Added setDrawerOpen to dependency array, though empty [] might also work if setDrawerOpen is guaranteed stable
+
+ const drawerContent = (
+
-
-
-
-
-
-
- PROCESSES
-
-
- MODULES
-
-
- BLOCKS
-
-
- ARNS
-
-
-
-
- button > div": {
- height: "fit-content",
- padding: 0,
- },
- "&.MuiBox-root button": {
- height: "100%",
- borderRadius: 1,
- border: "1px solid var(--mui-palette-divider)",
- paddingX: 2.5,
- paddingY: 1,
- color: "var(--mui-palette-primary-main)",
- background: "none",
- },
- "&.MuiBox-root button:active": {
- transform: "scale(0.98) !important",
- },
- "& button:hover": {
- transform: "none !important",
- boxShadow: "none !important",
- },
- "& button > *": {
- fontWeight: 500,
- fontFamily: MainFontFF,
- textTransform: "none",
- lineHeight: 1,
- fontSize: "0.8125rem",
- padding: 0,
- },
- "& button svg": {
- marginY: -1,
- },
- }}
- >
-
-
- {
- const nextMode = mode === "dark" ? "light" : "dark"
- setMode(nextMode)
- }}
+
+
+
+
+
+
+ {navItems.map((item) => (
+
+ navigate(item.path)}>
+
+
+
+ ))}
+
+
+ )
+
+ return (
+ <>
+
+
+
+ {isMobile && mobileSearchActive ? (
+
+ setMobileSearchActive(false)} color="inherit" sx={{ mr: 1, color: 'var(--mui-palette-text-primary)' }}>
+
+
+
+
+
+
+ ) : (
+
- {mode === "dark" ? : }
-
-
-
-
-
-
+
+
+ {!isMobile && (
+
+ {navItems.map((item) => (
+
+ {item.label}
+
+ ))}
+
+ )}
+
+ {/* Reduced gap for mobile icons */}
+ {isLargeScreen && }
+ {!isLargeScreen && (
+ <>
+ setMobileSearchActive(true)}
+ sx={{ color: 'var(--mui-palette-text-primary)' }}
+ >
+
+
+
+
+
+ >
+ )}
+ button > div": {
+ height: "fit-content",
+ padding: 0,
+ },
+ "&.MuiBox-root button": {
+ height: "100%",
+ borderRadius: 1,
+ border: "1px solid var(--mui-palette-divider)",
+ paddingX: { xs: 1.5, sm: 2.5 },
+ paddingY: 1,
+ color: "var(--mui-palette-primary-main)",
+ background: "none",
+ },
+ "&.MuiBox-root button:active": {
+ transform: "scale(0.98) !important",
+ },
+ "& button:hover": {
+ transform: "none !important",
+ boxShadow: "none !important",
+ },
+ "& button > *": {
+ fontWeight: 500,
+ fontFamily: MainFontFF,
+ textTransform: "none",
+ lineHeight: 1,
+ fontSize: "0.8125rem",
+ padding: 0,
+ },
+ "& button svg": {
+ marginY: -1,
+ },
+ }}
+ >
+
+
+ {/* Minimal IconButton for testing TS1005 error */}
+ {
+ const nextMode = mode === "dark" ? "light" : "dark";
+ setMode(nextMode);
+ }} // Added back original onClick logic
+ size="large"
+ sx={{ fontSize: "1.125rem" }}
+ >
+ {mode === "dark" ? : }
+
+
+
+ )}
+
+
+
+
+ {drawerContent}
+
+ >
)
}
diff --git a/src/components/IdBlock.tsx b/src/components/IdBlock.tsx
index 6c60351..47868a1 100644
--- a/src/components/IdBlock.tsx
+++ b/src/components/IdBlock.tsx
@@ -25,7 +25,9 @@ export function IdBlock(props: IdBlockProps) {
display="flex"
alignItems="center"
sx={{
- fill: "none",
+ whiteSpace: "nowrap",
+ overflow: "hidden",
+ textOverflow: "ellipsis",
"&:hover": { fill: "var(--mui-palette-text-secondary)" },
}}
>
@@ -55,7 +57,9 @@ export function IdBlock(props: IdBlockProps) {
return (
diff --git a/src/components/InMemoryTable.tsx b/src/components/InMemoryTable.tsx
index dab766a..a86e24a 100644
--- a/src/components/InMemoryTable.tsx
+++ b/src/components/InMemoryTable.tsx
@@ -106,64 +106,66 @@ export function InMemoryTable(props: InMemoryTableProps) {
return (
-
-
-
- {headerCells.map((cell, index) => (
-
- {cell.sortable ? (
- {
- if (sortField !== cell.field) {
- setSortField(cell.field as string)
- } else {
- setSortAscending(!sortAscending)
- }
- }}
- >
- {cell.label}
-
+
+
+
+
+ {headerCells.map((cell, index) => (
+
+ {cell.sortable ? (
+ {
+ if (sortField !== cell.field) {
+ setSortField(cell.field as string)
+ } else {
+ setSortAscending(!sortAscending)
+ }
+ }}
+ >
+ {cell.label}
+
+ ) : (
+ cell.label
+ )}
+
+ ))}
+
+
+
+
+
+ {loading ? (
+
) : (
- cell.label
+
)}
- ))}
-
-
-
-
-
- {loading ? (
-
- ) : (
-
- )}
-
-
- {visibleRows.map(renderRow)}
- {data.length === 0 && !loading && (
-
-
-
- No data available.
-
-
- )}
-
-
+ {visibleRows.map(renderRow)}
+ {data.length === 0 && !loading && (
+
+
+
+ No data available.
+
+
+
+ )}
+
+
+
{!endReached && data.length > 0 && (
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/PageWrapper.tsx b/src/components/PageWrapper.tsx
new file mode 100644
index 0000000..26e9292
--- /dev/null
+++ b/src/components/PageWrapper.tsx
@@ -0,0 +1,16 @@
+import React, { Suspense } from "react"
+import { ErrorBoundary } from "react-error-boundary"
+import PageSkeleton from "./PageSkeleton"
+import ErrorView from "./ErrorView"
+
+interface PageWrapperProps {
+ children: React.ReactNode
+}
+
+export default function PageWrapper({ children }: PageWrapperProps) {
+ return (
+ }>
+ }>{children}
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/RequestHistoryPanel.tsx b/src/components/RequestHistoryPanel.tsx
index 2880f03..bd34543 100644
--- a/src/components/RequestHistoryPanel.tsx
+++ b/src/components/RequestHistoryPanel.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from "react"
+import React, { useState, lazy, Suspense } from "react"
import {
Accordion,
AccordionSummary,
@@ -10,7 +10,6 @@ import {
Typography,
} from "@mui/material"
import Grid2 from "@mui/material/Unstable_Grid2/Grid2"
-import { CodeEditor } from "@/components/CodeEditor"
import { useActiveAddress } from "@arweave-wallet-kit/react"
import { persistentAtom } from "@nanostores/persistent"
import { useStore } from "@nanostores/react"
@@ -42,6 +41,8 @@ interface RequestHistoryPanelProps {
onSelect: (payload: string) => void
}
+const CodeEditor = lazy(() => import("./CodeEditor").then(m=>({default:m.CodeEditor})))
+
export function RequestHistoryPanel({ onSelect }: RequestHistoryPanelProps) {
const address = useActiveAddress()
const history = useStore(dryRunHistoryStore)
@@ -204,12 +205,14 @@ export function RequestHistoryPanel({ onSelect }: RequestHistoryPanelProps) {
Query
-
+ Loading editor...}>
+
+
@@ -217,12 +220,14 @@ export function RequestHistoryPanel({ onSelect }: RequestHistoryPanelProps) {
Result
-
+ Loading editor...}>
+
+
diff --git a/src/components/RootLayout/RootLayoutUI.tsx b/src/components/RootLayout/RootLayoutUI.tsx
index 5d041ae..40b7714 100644
--- a/src/components/RootLayout/RootLayoutUI.tsx
+++ b/src/components/RootLayout/RootLayoutUI.tsx
@@ -13,7 +13,14 @@ import { Footer } from "../Footer"
import Header from "../Header"
import { NavigationEvents } from "../NavigationEvents"
-const queryClient = new QueryClient()
+const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ staleTime: 5 * 60 * 1000, // 5 minutes
+ gcTime: 10 * 60 * 1000,
+ },
+ },
+})
export default function RootLayoutUI({ children }: { children: React.ReactNode }) {
return (
@@ -24,7 +31,13 @@ export default function RootLayoutUI({ children }: { children: React.ReactNode }
-
+
{children}
diff --git a/src/components/RootLayout/theme.ts b/src/components/RootLayout/theme.ts
index 12a71fb..bd43571 100644
--- a/src/components/RootLayout/theme.ts
+++ b/src/components/RootLayout/theme.ts
@@ -17,6 +17,15 @@ const extraColors = {
}
export const theme = extendTheme({
+ breakpoints: {
+ values: {
+ xs: 0, // phones (portrait)
+ sm: 600, // phones (landscape) / small tablets
+ md: 900, // tablets
+ lg: 1200, // laptop
+ xl: 1536, // large desktop
+ },
+ },
colorSchemes: {
dark: {
palette: {
diff --git a/src/components/Subheading.tsx b/src/components/Subheading.tsx
index 5a12520..c3b8c2b 100644
--- a/src/components/Subheading.tsx
+++ b/src/components/Subheading.tsx
@@ -1,7 +1,9 @@
-import { Stack, Typography } from "@mui/material"
+import { Stack, Typography, Box } from "@mui/material"
import React, { ReactNode } from "react"
import { MonoFontFF } from "./RootLayout/fonts"
+import { IdBlock } from "./IdBlock"
+import { CopyToClipboard } from "./CopyToClipboard"
type SubheadingProps = {
type: string
@@ -39,9 +41,29 @@ export function Subheading(props: SubheadingProps) {
/
-
- {value}
-
+
+ {typeof value === 'string' ? (
+ <>
+ {value}
+
+ >
+ ) : (
+ value
+ )}
+
)
diff --git a/src/components/TagChip.tsx b/src/components/TagChip.tsx
index 8192a7e..6aee182 100644
--- a/src/components/TagChip.tsx
+++ b/src/components/TagChip.tsx
@@ -1,14 +1,16 @@
import { IconButton, Typography, TypographyProps } from "@mui/material"
+import { Check, Copy } from "@phosphor-icons/react"
import React from "react"
+import { MonoFontFF } from "./RootLayout/fonts"
import { getColorFromText } from "@/utils/color-utils"
-import { MonoFontFF } from "./RootLayout/fonts"
-import { Check, Copy } from "@phosphor-icons/react"
import { isArweaveId } from "@/utils/utils"
-export function TagChip(props: TypographyProps & { name: string; value: string }) {
- const { name, value } = props
+export function TagChip(
+ props: TypographyProps & { name: string; value: string; copyOnlyValue?: boolean },
+) {
+ const { name, value, copyOnlyValue = false } = props
const [copied, setCopied] = React.useState(false)
const valuesIsArweaveAddress = isArweaveId(value)
@@ -40,19 +42,21 @@ export function TagChip(props: TypographyProps & { name: string; value: string }
{
e.stopPropagation()
- navigator.clipboard.writeText(value)
+ navigator.clipboard.writeText(copyOnlyValue ? value : `${name}:${value}`)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
}}
sx={{
- width: 0,
- overflow: "hidden",
+ width: 24,
+ minWidth: 24,
+ height: 24,
+ opacity: 0.5,
+ ml: 1,
p: 0,
- transition: "width 0.3s, transform 0.3s",
+ transition: "opacity 0.3s, transform 0.3s",
color: "black",
- ".MuiTypography-root:hover &": {
- width: "auto",
- ml: 1,
+ "&:hover": {
+ opacity: 1,
transform: "scale(1.2)",
},
}}
diff --git a/src/services/messages-api.ts b/src/services/messages-api.ts
index 5e0b656..0120d5c 100644
--- a/src/services/messages-api.ts
+++ b/src/services/messages-api.ts
@@ -251,7 +251,7 @@ export async function getSpawnedProcesses(
export async function getMessageById(id: string): Promise {
if (!isArweaveId(id)) {
- throw new Error("Invalid Arweave ID")
+ return null
}
const { data, error } = await goldsky
.query(
@@ -423,7 +423,6 @@ export async function getResultingMessages(
msgRefs?: string[],
useOldRefSymbol = false,
): Promise<[number | undefined, AoMessage[]]> {
- console.log("📜 LOG > msgRefs:", msgRefs)
try {
const result = await goldsky
.query(resultingMessagesQuery(!cursor, useOldRefSymbol), {
@@ -929,14 +928,23 @@ export interface FetchMessageGraphArgs {
depth?: number
}
-export const fetchMessageGraph = async ({
- msgId,
- actions,
- startFromPushedFor = false,
- ignoreRepeatingMessages = false,
- depth = 0,
-}: FetchMessageGraphArgs): Promise => {
+export const fetchMessageGraph = async (
+ {
+ msgId,
+ actions,
+ startFromPushedFor = false,
+ ignoreRepeatingMessages = false,
+ depth = 0,
+ }: FetchMessageGraphArgs,
+ visited: Set = new Set(),
+): Promise => {
try {
+ if (visited.has(msgId)) {
+ return null
+ }
+
+ visited.add(msgId)
+
let originalMsg = await getMessageById(msgId)
if (!originalMsg) {
@@ -978,6 +986,11 @@ export const fetchMessageGraph = async ({
for (const result of head.result?.Messages ?? []) {
const refTag = result.Tags.find((t: any) => ["Ref_", "Reference"].includes(t.name))
+ // Require a valid target and at least one reference tag value
+ if (!isArweaveId(String(result.Target)) || !refTag?.value) {
+ continue
+ }
+
const shouldUseOldRefSymbol = refTag.name === "Ref_"
const nodes = await getResultingMessagesNodes({
@@ -994,7 +1007,7 @@ export const fetchMessageGraph = async ({
useOldRefSymbol: shouldUseOldRefSymbol,
})
- let nodesIds = nodes.map((n) => n.id)
+ let nodesIds = nodes.filter((n) => n && isArweaveId(String(n.id))).map((n) => n.id)
if (ignoreRepeatingMessages) {
nodesIds = [...new Set(nodesIds)]
@@ -1003,12 +1016,16 @@ export const fetchMessageGraph = async ({
let leafs = []
for (const nodeId of nodesIds) {
- const leaf = await fetchMessageGraph({
- msgId: nodeId,
- actions,
- ignoreRepeatingMessages,
- depth: depth + 1,
- })
+ if (!isArweaveId(nodeId)) continue
+ const leaf = await fetchMessageGraph(
+ {
+ msgId: nodeId,
+ actions,
+ ignoreRepeatingMessages,
+ depth: depth + 1,
+ },
+ visited,
+ )
leafs.push(leaf)
}
@@ -1019,8 +1036,7 @@ export const fetchMessageGraph = async ({
}
return head
- } catch (err) {
- console.error("Failed to fetch message graph:", err)
+ } catch (error) {
return null
}
}
diff --git a/vercel.json b/vercel.json
index 0f32683..fb0d041 100644
--- a/vercel.json
+++ b/vercel.json
@@ -1,3 +1,11 @@
{
- "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
+ "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }],
+ "headers": [
+ {
+ "source": "/index.html",
+ "headers": [
+ { "key": "Cache-Control", "value": "no-cache" }
+ ]
+ }
+ ]
}
diff --git a/vite.config.ts b/vite.config.ts
index ee0b315..d026137 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,10 +1,20 @@
import react from "@vitejs/plugin-react"
import { defineConfig } from "vite"
+import { visualizer } from "rollup-plugin-visualizer"
// https://vitejs.dev/config/
-export default defineConfig({
+export default defineConfig(({ mode }) => ({
base: "./",
- plugins: [react()],
+ plugins: [
+ react(),
+ mode === "analyse" &&
+ visualizer({
+ filename: "bundle-report.html",
+ open: false,
+ gzipSize: true,
+ brotliSize: true,
+ }),
+ ],
resolve: {
alias: {
"@": "/src",
@@ -13,4 +23,20 @@ export default defineConfig({
define: {
global: "globalThis",
},
-})
+ build: {
+ rollupOptions: {
+ output: {
+ manualChunks: {
+ highcharts: [
+ "highcharts",
+ "highcharts/highstock",
+ "highcharts/modules/treemap",
+ "highcharts/modules/exporting",
+ ],
+ phosphor: ["@phosphor-icons/react"],
+ },
+ },
+ },
+ chunkSizeWarningLimit: 1500,
+ },
+}))