Repository navigation
refactor: skeleton layout, OpenAPI-first API, and Chain Console web overhaul - #85
Merged
Merged
Conversation
Restructure the project around the ethpandaops skeleton layout, replace the hand-rolled HTTP API with an OpenAPI-first ogen-generated server, and modernize the web UI stack. Go restructure: - Move the application root from pkg/forky to the repo root (package forky: forkchoice.go, http.go, config.go); subpackages move from pkg/forky/* to internal/* (store, source, service, db, api, ethereum, human, types, version, yaml). - Replace main.go + cmd/root.go with a cmd/forky/main.go cobra entrypoint that builds the logger, loads config, and runs the server. - Serve on a http.ServeMux: /api/ -> generated server, /openapi.yaml -> embedded spec, / -> embedded SPA fallback. - Bump Go to 1.26.2 and refresh dependencies. OpenAPI-first HTTP API: - api/openapi.yaml is now the source of truth for the HTTP API. - api/rest holds the ogen-generated server/client (go generate); internal/api implements the generated rest.Handler interface and maps domain types onto the spec schemas. - Delete the hand-rolled httprouter routing/response layer (pkg/forky/api/http); drop julienschmidt/httprouter, pkg/errors and lib/pq in favour of ogen with otel instrumentation. - Add .redocly.yaml for spec linting; make generate regenerates the Go server and the typed web client together. Web UI overhaul: - Switch npm -> pnpm (pnpm-lock.yaml, packageManager pin, CI updates). - Replace react-router-dom/wouter with TanStack Router using file-based routes (web/src/routes) and a generated route tree. - Replace hand-written API fetchers with a @hey-api/openapi-ts generated typed client (web/src/api) integrated with TanStack Query and zod. - Move Tailwind to v4 CSS-first config: tailwind.config.js removed, semantic color tokens defined in web/src/index.css and enforced by custom ESLint rules (no-hardcoded-colors, no-primitive-color-scales). - Embed built assets via web/embed.go behind the webui build tag (with a stub for tag-less builds), replacing web/static.go. - Rework major views (Events, Header, Selection, summaries) onto the semantic token theme; bump React 19.2, Vite 8, TypeScript 6, ESLint 10 and Vitest 4. Tooling and docs: - New help-driven Makefile (build, build-web, generate, lint, audit, vuln, modernize); .golangci.yml rewritten for golangci-lint v2. - CI: Go 1.26.x, golangci-lint v2.12.2, pnpm-based web jobs. - Add AGENTS.md, CLAUDE.md, web/CLAUDE.md, CONTRIBUTING.md, .editorconfig, .tool-versions and .dockerignore. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Continue the web overhaul by replacing the catch-all splat route with typed file-based TanStack routes, adding a Storybook setup, and splitting the Vitest suite into unit and Storybook browser tests. Routing: - Drop the single splat route (`routes/$.tsx`, `routes/-RouteView.tsx`) in favor of concrete routes: `byo`, `events`, `node.$nodeId`, `snapshot.$frameId`, plus their non-nested `_.events` overlay variants. - Add `utils/routes.ts` (`parseAppRoute`, `nodePath`, `snapshotPath`, `eventsPathFor`, and `*EventsPath` helpers) as the single boundary that turns plain app path strings into typed route targets; cover it with `utils/__tests__/routes.test.ts`. - Rework `Link.tsx` and `useAppNavigation.ts` to dispatch parsed routes to typed `RouterLink`/`navigate` targets, falling back to a plain anchor / `window.location.assign` for unknown paths. - Thread events overlay state through props: `App` takes `eventsOpen` / `eventsCloseTo`, `Events` consumes them instead of sniffing the pathname, and each `_.events` route opens the overlay with the right close target. - Wire a `notFoundComponent` on the root route and simplify Header links. Storybook: - Add Storybook 10 (`.storybook/` config, preview, story routes, MSW setup) and stories for App, Fallbacks, and the component/parts library. - Add `src/stories/` fixtures and helpers shared across stories. - Register `eslint-plugin-storybook` and `tsconfig.storybook.json`. Testing: - Split Vitest: `vitest.config.unit.ts` (jsdom unit tests) and `vitest.config.ts` (Storybook tests in a Playwright/Chromium browser); add `test:unit*`, `test:storybook*`, and `test:ui` scripts. - Extract `ErrorBoundaryFallback` so the fallback can be story-tested. Tooling: - Bump msw to 2.14.6, add Storybook/Vitest browser/Playwright and supporting dev deps; ignore `storybook-static` and `.tanstack`.
Reskin the operator UI to the dark-first "Chain Console" design language, collapse the node routes onto a single splat route, and render one persistent <App> from the root route. Theming (src/index.css) - Rewrite the two-tier token system around a cool-neutral "ink" ramp (hue 270), an electric-cyan accent and six status families that share OKLCH lightness/chroma per role, so every status holds equal contrast against the surface ladder in both themes. - Add semantic accent tokens (--color-accent, --color-accent-hover) and glass-chrome / stage-grid / dot-grid utilities; drop the old Tailwind-palette primitives and the unused inset-shadow/pulse helpers. - Adopt Inter Variable + JetBrains Mono Variable via @fontsource (added to package.json); restyle Header, Control, Share, Events, AggregatedNode, WeightedNode, Graph, Timeline and the rest to match (glass header/dock, network-name badge, icon buttons). Routing - Replace /node/$nodeId and /node_/$nodeId/events with one splat route (node.$.tsx) so multi-segment node ids with literal slashes (ethpandaops/mainnet/node-001) deep-link cleanly. Add parseNodeSplat and per-segment encodeNodeId in utils/routes.ts; a trailing /events segment always means the overlay. - Remove the -AppRoute.tsx wrapper. The root route now renders the single, persistent <App> for every view and derives its props from the location via parseAppRoute; leaf route files are matchers only. One App (and the focus provider) stays mounted across navigation, so switching views no longer resets player time or play/pause. - Regenerate routeTree.gen.ts; expand routes tests for multi-segment and encoded-slash node ids. Branding & metadata - Add SVG wordmark marks (forky-mark.svg, forky-mark-small.svg) and use them in the header; re-optimize the PNG favicons/logos (much smaller). - Redesign favicon.svg; set theme-color/tile color to #090a0e; fix the placeholder name in site.webmanifest; remove humans.txt. - Drop a stale @ts-expect-error in index.tsx; refresh web/CLAUDE.md routing docs. typecheck, lint, 125 unit tests and 81 storybook tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ports PR #84 onto the Chain Console web overhaul: behind-node partitioning (Stage), struck-through lagging sources and truncation rendering (Graph), pinned checkpoint columns and gap markers (useGraph), and the TruncationMarker component restyled to the semantic token system. Master's styling is intentionally not taken; the BYO header link fix was already present on this branch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Long-tail scenario exercising pinned finalized/justified checkpoints with gap markers, an aggregated view with a stuck source struck through in the Sources panel, and the TruncationMarker primitive. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…the tail The TruncationMarker in the primitives story overlapped the progress circles; it now sits on its own row at the graph's real radius, and the marker typography is sized so comma'd counts stay inside the disc. The truncated long-tail graph stories auto-pan (via real mouse events, so pan/zoom state stays consistent) from the head to the pinned checkpoints and gap markers on load. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ills Graph primitives stories now render every node at the graph's actual radius (150) so the fixed node typography keeps its real proportions, with layouts re-spaced and boundary labels moved clear of the discs. TruncationMarker and ConcatNode backgrounds are fully opaque — no graph node lets edges or rails bleed through. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
samcm
approved these changes
Jun 10, 2026
barnabasbusa
approved these changes
Jun 10, 2026
mattevans
approved these changes
Jun 10, 2026
pnpm/action-setup found no version (the packageManager pin lives in web/package.json, not the repo root) — pin pnpm 10.30.1 explicitly. golangci-lint-action v3 passes --out-format, removed in golangci-lint v2; bump to v9.2.0 and drop only-new-issues (full repo is clean, and this PR's diff exceeds the API's 20k-line limit). Install Playwright Chromium before web tests for the Vitest browser-mode Storybook suite, and move core actions to current SHA-pinned releases ahead of the Node 20 runner deprecation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jun 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Ground-up modernization of forky: the Go service moves to the ethpandaops skeleton layout with an OpenAPI-first, ogen-generated HTTP API, and the web UI gets a full overhaul — new build/test stack, typed routing, a Storybook-first component library, and a complete "Chain Console" visual redesign. Master's stale-node filtering and graph tail truncation (#84) are merged in and re-expressed in the new design system.
Go: skeleton layout + OpenAPI-first API
pkg/forkyto the repo root; subpackages becomeinternal/*(store, source, service, db, api, ethereum, human, types, version, yaml). Newcmd/forky/main.gocobra entrypoint.api/openapi.yamlis now the source of truth for the HTTP API on both sides: the Go server (api/rest, generated by ogen, implemented byinternal/api) and the typed web client (web/src/api, generated by @hey-api/openapi-ts).make generateregenerates both together; wire format matches by construction.http.ServeMux:/api/→ generated server,/openapi.yaml→ embedded spec,/→ embedded SPA fallback. Hand-rolled httprouter layer deleted; Go bumped to 1.26.Web: stack + architecture
vitest.config.unit.ts, 125 tests) and Storybook interaction tests (84 stories/assertions)./,/byo,/events,/node/*,/snapshot/:idand their/eventsoverlay variants. Node ids contain literal slashes (ethpandaops/mainnet/node-001), so/nodeis a splat route — pretty multi-segment node URLs deep-link correctly.parseAppRouteinutils/routes.tsis the single tested boundary between plain path strings and typed route targets.<App>and derives view props from the location, so navigating between aggregated / node / snapshot views (or opening the events overlay) never remounts the app — the focused time and play/pause state survive navigation, with no query params involved.Web: "Chain Console" redesign
Dark-first mission-control aesthetic; light mode is a faithful inversion.
index.css): two-tier token architecture retained (custom ESLint rules still enforce semantic-tokens-only in app code). New cool near-black "ink" neutral ladder, alpha hairline borders, and the six fork-choice status colors normalized in OKLCH (shared lightness/chroma bands) so every status holds equal contrast in both themes. One electric-cyan accent owns focus rings, links, the playhead and LIVE state.Merged from master: stale nodes + tail truncation (#84)
Logic merged intact, visuals re-expressed in the design system:
MAX_GRAPH_SLOTS = 128): long graphs render only the most recent window; finalized/justified checkpoints that fall outside are pinned into compact columns with their own slot boundaries, bridged by dashed "N slots hidden" gap markers and a rail.TruncationMarkercomponent (opaque, dashed neutral ring — no graph node has a transparent fill).Testing
pnpm lint,pnpm typecheck,pnpm test(125 unit + 84 Storybook browser tests),pnpm buildall green;tsc -bproduction build verified.?t=), and player-state persistence across navigation.Screenshots
All live mainnet (
localhost:5173) unless noted.Aggregated view — dark

Aggregated view — light

Single node view

Events drawer (live reorg events)

Mobile (390px)

About drawer

Tail truncation: pinned finalized/justified checkpoints + gap markers (Storybook scenario — healthy mainnet doesn't truncate)

Stale node excluded from aggregation, flagged in Sources (Storybook scenario)

🤖 Generated with Claude Code