Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
bc7e258
fix(dave): react warn about display name definition missing
brunomenezes Dec 17, 2025
0322833
fix(dave): Add default value to avoid unstable undefined error and di…
brunomenezes Dec 17, 2025
4d9f4a4
fix(dave): protect from invalid values when parsing specific params.
brunomenezes Dec 17, 2025
7b72221
feat(dave): Add env variables and notification system.
brunomenezes Dec 17, 2025
5720772
chore(dave): Add feature to log and export current node-api queries.
brunomenezes Dec 17, 2025
137e545
feat(dave): Add setup for running the app in mock mode.
brunomenezes Dec 17, 2025
c788dc0
fix(dave): Add data-provider to support page stories.
brunomenezes Dec 18, 2025
2d24a11
refactor(dave): Change hierarchy hook to use cartesi/wagmi hooks inst…
brunomenezes Dec 18, 2025
67ace5c
fix(dave): Disallow null tournament link and display message instead.
brunomenezes Dec 19, 2025
2c3f03a
feat(dave): display message for empty input list.
brunomenezes Dec 19, 2025
89bc3a9
feat(dave): Add theme toggle.
brunomenezes Dec 19, 2025
11b922a
feat(dave): Add four initial test cases based on node-api.
brunomenezes Dec 19, 2025
071b110
refactor(dave): change time elapse calculation to use milliseconds in…
brunomenezes Jan 6, 2026
a851b20
chore(dave): cleanup lingering property after node-api changes.
brunomenezes Jan 6, 2026
59fb6b6
feat(dave): Add test case 5 and 6.
brunomenezes Jan 9, 2026
5a5a3dc
feat(dave): Add UI for eliminated matches on Tournament view.
brunomenezes Jan 9, 2026
23165ea
feat(dave): Add local data for test case top eliminate-inner-tournament.
brunomenezes Jan 13, 2026
db0b3b1
feat(dave): Add UI behaviour for child-tournament result without a wi…
brunomenezes Jan 13, 2026
44a222b
feat(dave): Add two-commitments use cases.
brunomenezes Jan 13, 2026
6c6db27
feat(dave): Add feedback in the tournament view when there are no com…
brunomenezes Jan 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/dave/.env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
NEXT_PUBLIC_DEBUG_ENABLED=false
NEXT_PUBLIC_MOCK_ENABLED=false
NEXT_PUBLIC_CARTESI_NODE_RPC_URL="http://127.0.0.1:10011/rpc"
3 changes: 3 additions & 0 deletions apps/dave/.env.production
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
NEXT_PUBLIC_DEBUG_ENABLED=false
NEXT_PUBLIC_MOCK_ENABLED=false
NEXT_PUBLIC_CARTESI_NODE_RPC_URL="http://127.0.0.1:10011/rpc"
4 changes: 2 additions & 2 deletions apps/dave/.storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { MantineProvider } from "@mantine/core";
import "@mantine/core/styles.css";
import type { Preview, StoryContext, StoryFn } from "@storybook/nextjs";
import React from "react";
import Layout from "../src/components/layout/Layout";
import DataProvider from '../src/providers/DataProvider';
import theme from "../src/providers/theme";

// @ts-expect-error JSON.stringify will try to call toJSON on bigints. ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#use_within_json
Expand All @@ -15,7 +15,7 @@ const withLayout = (StoryFn: StoryFn, context: StoryContext) => {
const [sectionType] = title.split("/");

if (sectionType.toLowerCase().includes("pages"))
return <Layout>{StoryFn(context.args, context)}</Layout>;
return <DataProvider><Layout>{StoryFn(context.args, context)}</Layout></DataProvider>;

return <>{StoryFn(context.args, context)}</>;
};
Expand Down
22 changes: 22 additions & 0 deletions apps/dave/additional.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
declare namespace NodeJS {
export interface ProcessEnv {
/**
* When enabled display a few header actions to help generate mock data
* from react-query current query cache information.
*/
NEXT_PUBLIC_DEBUG_ENABLED: string;

/**
* When enabled will inject query data directly into the
* react query cache information. Also, sets the client
* to never gc the cache or mark this data as stale (so it will never go to the network)
* The mock is usually a recording of a node-api real responses.
*/
NEXT_PUBLIC_MOCK_ENABLED: string;

/**
* Cartesi rollups node RPC endpoint.
*/
NEXT_PUBLIC_CARTESI_NODE_RPC_URL: string;
}
}
1 change: 1 addition & 0 deletions apps/dave/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@cartesi/wagmi": "2.0.0-alpha.27",
"@mantine/core": "^8.3.10",
"@mantine/hooks": "^8.3.10",
"@mantine/notifications": "^8.3.10",
"@raugfer/jazzicon": "^1.0.6",
"@tabler/icons-react": "^3.35.0",
"@tanstack/react-query": "catalog:",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { notFound } from "next/navigation";
import { getAddress } from "viem";
import { TournamentContainer } from "../../../../../../../containers/TournamentContainer";

Expand All @@ -6,8 +7,16 @@ export default async function Page(
) {
const params = await props.params;
const application = params.application;
const epochIndex = BigInt(params.epochIndex);
const tournamentAddress = getAddress(params.tournamentAddress);
let epochIndex, tournamentAddress;

try {
epochIndex = BigInt(params.epochIndex);
tournamentAddress = getAddress(params.tournamentAddress);
} catch (err: unknown) {
const error = err as Error;
console.error(error.message);
return notFound();
}

return (
<TournamentContainer
Expand Down
13 changes: 7 additions & 6 deletions apps/dave/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";
import { ColorSchemeScript, mantineHtmlProps } from "@mantine/core";
import "@mantine/core/styles.css";
import "@mantine/notifications/styles.css";
import { Analytics } from "@vercel/analytics/react";
import type { FC, ReactNode } from "react";
import { StrictMode } from "react";
Expand All @@ -16,16 +17,16 @@ const RootLayout: FC<RootLayoutProps> = ({ children }) => {
return (
<html lang="en" {...mantineHtmlProps}>
<head>
<ColorSchemeScript defaultColorScheme="auto" />
<ColorSchemeScript />
</head>
<body>
<StrictMode>
<DataProvider>
<StyleProvider>
<StyleProvider>
<DataProvider>
<Layout>{children}</Layout>
</StyleProvider>
<Analytics />
</DataProvider>
<Analytics />
</DataProvider>
</StyleProvider>
</StrictMode>
</body>
</html>
Expand Down
28 changes: 28 additions & 0 deletions apps/dave/src/components/ThemeToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use client";
import { Switch, useMantineColorScheme, VisuallyHidden } from "@mantine/core";
import type { FC } from "react";
import { TbMoonStars, TbSun } from "react-icons/tb";

export const ThemeToggle: FC = () => {
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
return (
<Switch
label={<VisuallyHidden>Theme mode switch</VisuallyHidden>}
checked={colorScheme === "light"}
onChange={() => toggleColorScheme()}
size="md"
onLabel={
<>
<VisuallyHidden>Dark Mode</VisuallyHidden>
<TbMoonStars size="1rem" />
</>
}
offLabel={
<>
<VisuallyHidden>Light Mode</VisuallyHidden>
<TbSun size="1rem" />
</>
}
/>
);
};
2 changes: 1 addition & 1 deletion apps/dave/src/components/application/ApplicationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type Props = { applications: Application[] };

export const ApplicationList: FC<Props> = ({ applications }) => {
return (
<Stack gap={5}>
<Stack gap={5} pb="xl">
{applications.map((app) => (
<ApplicationCard
key={app.applicationAddress}
Expand Down
62 changes: 61 additions & 1 deletion apps/dave/src/components/layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,72 @@ import {
AppShell,
AppShellHeader,
AppShellMain,
Button,
Container,
Group,
useMantineTheme,
} from "@mantine/core";
import { notifications } from "@mantine/notifications";
import { useQueryClient } from "@tanstack/react-query";
import Link from "next/link";
import type { FC, PropsWithChildren } from "react";
import { defaultTo } from "ramda";
import { Activity, type FC, type PropsWithChildren } from "react";
import { useIsSmallDevice } from "../../hooks/useIsSmallDevice";
import CartesiLogo from "../icons/CartesiLogo";
import { ThemeToggle } from "../ThemeToggle";

const debugEnabled = defaultTo(
false,
process.env.NEXT_PUBLIC_DEBUG_ENABLED == "true",
);

const Layout: FC<PropsWithChildren> = ({ children }) => {
const queryClient = useQueryClient();
const theme = useMantineTheme();
const { isSmallDevice, viewport } = useIsSmallDevice();
const { height } = viewport;

const printQueryInfo = () => {
const defaultOpts = queryClient.getDefaultOptions();
const queryCache = queryClient.getQueryCache();
console.log(defaultOpts);
console.log(queryCache);

notifications.show({
title: "Query cache printed",
message: "Check your browser devtools console.",
});
};

const logQueries = () => {
const queries = queryClient.getQueryCache().getAll();
const obj: object[] = [];
// @ts-expect-error saving whatever is the original to reset.
const orig = BigInt.prototype.toJSON;

// @ts-expect-error JSON.stringify will try to call toJSON on bigints. ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#use_within_json
BigInt.prototype.toJSON = function () {
return `$bigint:${this.toString()}`;
};

queries.forEach((query) => {
obj.push({ queryKey: query.queryKey, data: query.state.data });
});

try {
console.log(JSON.stringify(obj, null, 4));
} catch (error) {
console.error(error);
} finally {
// @ts-expect-error just adding the original back.
BigInt.prototype.toJSON = orig;
notifications.show({
title: "Queries as JSON requested!",
message: "Check your browser devtools console.",
});
}
};

return (
<AppShell>
<AppShellHeader style={{ zIndex: theme.other.zIndexLG }}>
Expand All @@ -30,6 +82,14 @@ const Layout: FC<PropsWithChildren> = ({ children }) => {
<Link href="/" aria-label="Home">
<CartesiLogo height={isSmallDevice ? 30 : 40} />
</Link>

<Group>
<ThemeToggle />
<Activity mode={debugEnabled ? "visible" : "hidden"}>
<Button onClick={printQueryInfo}>Show cache</Button>
<Button onClick={logQueries}>Export queries</Button>
</Activity>
</Group>
</Group>
</AppShellHeader>
<AppShellMain>
Expand Down
12 changes: 8 additions & 4 deletions apps/dave/src/components/match/BisectionItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export interface BisectionItemProps extends TimelineItemProps {
total: number;
}

export const BisectionItem: FC<BisectionItemProps> = forwardRef<
const BisectionItem: FC<BisectionItemProps> = forwardRef<
HTMLDivElement,
BisectionItemProps
>((props, ref) => {
Expand All @@ -58,9 +58,9 @@ export const BisectionItem: FC<BisectionItemProps> = forwardRef<
const p = useMemo(() => {
const [start, end] = range;
const [domainStart, domainEnd] = domain;
return (
(start + end - 2 * domainStart) / (2 * (domainEnd - domainStart))
);
const numerator = start + end - 2 * domainStart;
const denominator = Math.max(2 * (domainEnd - domainStart), 1);
return numerator / denominator;
}, [domain, range]);

return (
Expand Down Expand Up @@ -102,3 +102,7 @@ export const BisectionItem: FC<BisectionItemProps> = forwardRef<
</ClaimTimelineItem>
);
});

BisectionItem.displayName = "BisectionItem";

export { BisectionItem };
2 changes: 1 addition & 1 deletion apps/dave/src/components/match/ClaimTimelineItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export interface ClaimTimelineItemProps extends TimelineItemProps {
}

const formatTime = (now: number, timestamp: number) => {
return `${humanizeDuration((now - timestamp) * 1000, { units: ["h", "m", "s"] })} ago`;
return `${humanizeDuration(now - timestamp, { units: ["h", "m", "s"], round: true })} ago`;
};

export const ClaimTimelineItem = forwardRef<
Expand Down
31 changes: 31 additions & 0 deletions apps/dave/src/components/match/ClaimsEliminatedItem.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Timeline } from "@mantine/core";
import type { Meta, StoryObj } from "@storybook/nextjs";
import { ClaimsEliminatedItem } from "./ClaimsEliminatedItem";

const meta = {
title: "Components/Match/ClaimsEliminatedItem",
component: ClaimsEliminatedItem,
tags: ["autodocs"],
decorators: [
(Story) => (
<Timeline bulletSize={24} lineWidth={2}>
<Story />
</Timeline>
),
],
} satisfies Meta<typeof ClaimsEliminatedItem>;

export default meta;
type Story = StoryObj<typeof meta>;

const now = Math.floor(Date.now() / 1000);

/**
* Claims eliminated
*/
export const Default: Story = {
args: {
now,
timestamp: now - 3452,
},
};
42 changes: 42 additions & 0 deletions apps/dave/src/components/match/ClaimsEliminatedItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {
Group,
Paper,
Text,
useComputedColorScheme,
useMantineTheme,
} from "@mantine/core";
import type { FC } from "react";
import { TbSwordOff } from "react-icons/tb";
import { ClaimTimelineItem } from "./ClaimTimelineItem";

type ClaimsEliminatedItemProps = {
/**
* Current timestamp
*/
now: number;

/**
* Timestamp
*/
timestamp: number;
};

export const ClaimsEliminatedItem: FC<ClaimsEliminatedItemProps> = ({
now,
timestamp,
}) => {
const theme = useMantineTheme();
const scheme = useComputedColorScheme();
const bg = scheme === "light" ? theme.colors.gray[0] : undefined;

return (
<ClaimTimelineItem now={now} timestamp={timestamp}>
<Paper withBorder p={16} radius="lg" bg={bg}>
<Group gap="xs">
<TbSwordOff size={24} />
<Text>both claims eliminated</Text>
</Group>
</Paper>
</ClaimTimelineItem>
);
};
13 changes: 3 additions & 10 deletions apps/dave/src/components/match/EliminationTimeoutItem.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {
Group,
Paper,
Text,
useComputedColorScheme,
useMantineTheme,
} from "@mantine/core";
import { type FC } from "react";
import { TbClockCancel, TbSwordOff } from "react-icons/tb";
import { TbClockCancel } from "react-icons/tb";
import type { Claim } from "../types";
import { ClaimTimelineItem } from "./ClaimTimelineItem";
import { ClaimsEliminatedItem } from "./ClaimsEliminatedItem";

interface EliminationTimeoutItemProps {
/**
Expand Down Expand Up @@ -55,14 +55,7 @@ export const EliminationTimeoutItem: FC<EliminationTimeoutItemProps> = (
<Text c="dimmed">no action taken</Text>
</Group>
</ClaimTimelineItem>
<ClaimTimelineItem now={now} timestamp={timestamp}>
<Paper withBorder p={16} radius="lg" bg={bg}>
<Group gap="xs">
<TbSwordOff size={24} />
<Text>both claims eliminated</Text>
</Group>
</Paper>
</ClaimTimelineItem>
<ClaimsEliminatedItem now={now} timestamp={timestamp} />
</>
);
};
Loading
Loading