Skip to content

Add Vite + React Router + shadcn admin SPA - #6

Open
zachbroad wants to merge 56 commits into
mainfrom
claude/vite-react-router-shadcn-c0fac5
Open

Add Vite + React Router + shadcn admin SPA#6
zachbroad wants to merge 56 commits into
mainfrom
claude/vite-react-router-shadcn-c0fac5

Conversation

@zachbroad

Copy link
Copy Markdown
Owner

What

A modern Vite + React Router v7 + shadcn/ui admin SPA for NitroHook (in web/ui/), a full replacement for the server-rendered Go-template UI, consuming the existing /api JSON endpoints. Two-pane master–detail layout (~20% list / ~80% detail). No auth (matches current API).

Includes a small Go backend delta required for parity:

  • Configurable CORS middleware (CORS_ALLOWED_ORIGINS, default http://localhost:5173)
  • 3 new JSON endpoints: source script test-run, forward delivery, forward-all recorded
  • Bind config on the JSON action create/update handlers so slack/smtp/twilio actions work via the API

The Go-template UI is left untouched.

Stack

Vite · React 18 · TypeScript · React Router v7 · shadcn/ui (Base UI + Tailwind v4) · TanStack Query · react-hook-form + zod · CodeMirror 6 · Vitest + React Testing Library.

Features

  • Sources: list + create; detail with Overview (mode toggle, webhook ingest URL, timestamps), Actions, Script, Events tabs
  • Actions: type-aware form for all 5 types (webhook/javascript/slack/smtp/twilio) with a zod discriminated union; active toggle; edit/delete
  • Script: CodeMirror editor + test-run against a recorded delivery
  • Deliveries: list with status filter; detail with JSON payload/headers viewer + attempts timeline; forward recorded deliveries

Verification

  • Backend: go test ./... ✓; new CORS/script-test/forward handler tests
  • Frontend: 29 Vitest tests ✓, tsc -b clean, production build succeeds
  • End-to-end in a real browser against a running backend: created a source + webhook action, sent a webhook, and observed the full pipeline (ingest → fan-out → delivery + retry attempts) render in the SPA; verified slack/smtp action create+update over CORS

Follow-ups (non-blocking)

  • Route errorElement is at the layout level, so a leaf render error replaces the whole shell incl. nav — consider a pathless-parent refactor to keep nav intact
  • JS bundle ~1.17MB (CodeMirror); could code-split the Script route
  • Minor: z.string().url() deprecation, delivery detail shows source_id UUID vs. name, TestScript response shape differs from the design doc

Design + plan: docs/superpowers/specs/2026-07-10-react-admin-spa-design.md, docs/superpowers/plans/2026-07-10-react-admin-spa.md

🤖 Generated with Claude Code

zachbroad and others added 30 commits July 10, 2026 19:36
Two-pane master-detail Vite + React Router + shadcn frontend that is a
full replacement for the Go-template UI, consuming the existing /api
JSON endpoints. Includes backend delta: CORS + 3 new JSON endpoints
(script test, forward delivery, forward-all recorded).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add `make dev` for a one-command local development loop: brings up
Postgres + Redis in Docker (waiting until healthy), applies migrations
via the API binary, then runs the API with an in-process worker under
Air for hot reload. Add `make dev-setup` (installs Air) and `make
dev-down` (stops the infra).

Start CHANGELOG.md (Keep a Changelog + SemVer) with a 0.1.0 entry, and
document changelog + docs-site upkeep expectations in CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ad errors

Mirror worker.FanoutWorker.runTransform's payload/headers unmarshal
semantics in SourceHandler.TestScript instead of falling back to a
{"_raw": ...} payload or silently discarding a headers unmarshal
error, so a passing preview implies the script will actually run in
production. Also stop discarding the actions-lookup error, which
previously let a DB failure silently run the script against zero
actions and still report success.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Set up the standalone React admin SPA under web/ui/: Vite + React 19 + TS,
Tailwind v4 (CSS-first, @tailwindcss/vite), shadcn/ui (base-nova style, 17
base components on @base-ui/react primitives), @/ path alias, and
Vitest + RTL + jsdom with a passing smoke test. Scaffolding only — no app
features yet. Adds Makefile ui-dev/ui-build targets.
Addresses Task 4 review finding: config file was outside every tsconfig
project, producing phantom editor errors. No build/runtime change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire inboundauth verification into the ingest handler so unauthenticated
or badly-signed webhooks are rejected with 401 before any delivery row
is created or published to Redis. Adds a nitrohook_webhook_auth_failures_total
counter (source, reason) for observability.
…empts

Addresses Task 6 review: useForwardDelivery only invalidated the deliveries
list, leaving an open delivery-detail/attempts view stale after a forward
(a forward creates a new attempt and changes status). Also widen
useDeliveries to accept limit for client parity and drop the now-unused
slug param (the deliveries prefix key already covers source-filtered lists).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Re-commits Task 1's persistence layer, which was orphaned from branch
history by an intervening amend+reset; content is identical to the
reviewed commit 20ad733.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds AppShell (20/80 split with Sources|Deliveries section switcher),
generic ListPane, EmptyState, and a createBrowserRouter skeleton
wiring /sources and /deliveries index routes. main.tsx now wraps
RouterProvider in QueryClientProvider and renders the themed shadcn
Toaster.
Adds an auth-card htmx fragment on the source overview page (mirroring
mode-card) with a preset dropdown and secret field, backed by a new
POST /sources/:slug/auth handler that saves config via
store.SetAuthConfig and re-renders the fragment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the /sources route's left pane to real data via useSources(),
and add a Base UI-based CreateSourceDialog (react-hook-form + zod)
that creates a source and navigates to its detail route on success.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses Task 8 review: onSubmit awaited mutateAsync with no catch, so a
failed create produced an unhandled promise rejection alongside the onError
toast. Wrap in try/catch; dialog stays open for retry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses Task 10 review: the smtp and twilio field groups in the action
form rendered no inline validation errors (their required-field zod
messages never displayed), and the payload-mapping/edit-mode-seeding
logic (toPayload, defaultValuesFromAction) had no direct test coverage
despite being the highest-risk code in the form.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses Task 11 review: the editor re-synced from source.script_body on
every change, so a Save-triggered refetch could clobber in-progress edits.
Seed only when the slug changes (ref-guarded). Also make the undefined/null
JsonViewer tests assert real rendered content instead of toBeDefined().

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
zachbroad and others added 6 commits July 10, 2026 21:08
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds RouteError error boundaries to /sources and /deliveries routes,
documents the admin SPA (web/ui/README.md) and links it from the root
README, and folds in accumulated cleanup: extract a single formatDate
helper into lib/utils.ts (removing three duplicated copies) and delete
the dead App.tsx/App.css/App.test.tsx scaffold left over from create-vite.
…ssets

Adds a third binary, cmd/mcp, a stdio Model Context Protocol server exposing
read-only list_sources, list_actions, and list_deliveries tools backed by the
existing store (github.com/modelcontextprotocol/go-sdk).

Also included:
- docker-compose: add Grafana service (promotes prometheus/client_golang to a
  direct dependency)
- web/static: vendor htmx + Monaco editor assets for the script editor UI
- Makefile: run-mcp target and bin/mcp build
- docs: MCP server guide, tour page, local-dev quickstart section; CLAUDE.md
  updated to three binaries; astro nav entries
- go.mod: bump to go 1.25, add go-sdk and supporting deps
- .gitignore: ignore local .superpowers/ tooling state

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ON API

createActionRequest/updateActionRequest never bound the config field, so
Create validated a nil config (400 "config is required") and Update never
passed config into store.ActionUpdateParams (silent no-op). Add Config
json.RawMessage to both request structs and wire it into the validation
temp action, ActionCreateParams, and ActionUpdateParams.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses whole-branch review (Important): the one env var an operator must
set to serve the SPA from a non-default origin was undocumented.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 146 files, which is 96 over the limit of 50.

To get a review, narrow the scope:
• coderabbit review --type committed # exclude uncommitted changes
• coderabbit review --dir # limit to a subdirectory
• coderabbit review --base # compare against a closer base

Upgrade to a paid plan to raise the limit.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a12cc906-3a49-4ff1-8396-d8d502a493af

📥 Commits

Reviewing files that changed from the base of the PR and between c9505ba and 40f9e1b.

⛔ Files ignored due to path filters (9)
  • dashboard-light.png is excluded by !**/*.png
  • go.sum is excluded by !**/*.sum
  • header-light.png is excluded by !**/*.png
  • web/static/htmx.min.js is excluded by !**/*.min.js
  • web/static/vs/base/browser/ui/codicons/codicon/codicon.ttf is excluded by !**/*.ttf
  • web/ui/package-lock.json is excluded by !**/package-lock.json
  • web/ui/public/favicon.svg is excluded by !**/*.svg
  • web/ui/public/icons.svg is excluded by !**/*.svg
  • web/ui/src/assets/vite.svg is excluded by !**/*.svg
📒 Files selected for processing (146)
  • .claude/skills/verify/SKILL.md
  • .env.example
  • .gitignore
  • CHANGELOG.md
  • CLAUDE.md
  • Makefile
  • README.md
  • cmd/api/main.go
  • cmd/mcp/main.go
  • docker-compose.yml
  • docs/astro.config.mjs
  • docs/src/content/docs/api/sources.md
  • docs/src/content/docs/getting-started/quickstart.md
  • docs/src/content/docs/guides/authentication.md
  • docs/src/content/docs/guides/mcp.md
  • docs/src/content/docs/tour.md
  • docs/superpowers/plans/2026-07-10-incoming-webhook-authentication.md
  • docs/superpowers/plans/2026-07-10-react-admin-spa.md
  • docs/superpowers/plans/2026-07-13-frontend-query-error-handling.md
  • docs/superpowers/specs/2026-07-10-react-admin-spa-design.md
  • docs/superpowers/specs/2026-07-13-frontend-query-error-handling-design.md
  • docs/superpowers/specs/2026-07-15-basic-auth-and-secret-generation-design.md
  • go.mod
  • internal/config/config.go
  • internal/handler/action.go
  • internal/handler/delivery.go
  • internal/handler/delivery_forward_test.go
  • internal/handler/handler_integration_test.go
  • internal/handler/source.go
  • internal/handler/source_auth.go
  • internal/handler/source_auth_test.go
  • internal/handler/source_script_test.go
  • internal/handler/webhook.go
  • internal/handler/webhook_auth_integration_test.go
  • internal/inboundauth/config.go
  • internal/inboundauth/presets.go
  • internal/inboundauth/presets_test.go
  • internal/inboundauth/sanitize.go
  • internal/inboundauth/sanitize_test.go
  • internal/inboundauth/verify.go
  • internal/inboundauth/verify_test.go
  • internal/metrics/metrics.go
  • internal/middleware/cors.go
  • internal/middleware/cors_test.go
  • internal/model/model.go
  • internal/store/delivery.go
  • internal/store/source.go
  • internal/store/source_auth_integration_test.go
  • internal/store/store_integration_test.go
  • internal/testutil/testutil.go
  • internal/worker/worker_integration_test.go
  • migrations/000009_add_source_auth.down.sql
  • migrations/000009_add_source_auth.up.sql
  • web/handler.go
  • web/source.go
  • web/static/vs/base/worker/workerMain.js
  • web/static/vs/basic-languages/javascript/javascript.js
  • web/static/vs/basic-languages/typescript/typescript.js
  • web/static/vs/editor/editor.main.css
  • web/static/vs/editor/editor.main.js
  • web/static/vs/language/typescript/tsMode.js
  • web/static/vs/language/typescript/tsWorker.js
  • web/static/vs/loader.js
  • web/templates/source-overview.html
  • web/ui/.env.example
  • web/ui/.gitignore
  • web/ui/.oxlintrc.json
  • web/ui/README.md
  • web/ui/components.json
  • web/ui/index.html
  • web/ui/package.json
  • web/ui/src/components/action-type-badge.tsx
  • web/ui/src/components/app-shell.tsx
  • web/ui/src/components/delivery-detail-body.tsx
  • web/ui/src/components/error-state.test.tsx
  • web/ui/src/components/error-state.tsx
  • web/ui/src/components/header.tsx
  • web/ui/src/components/json-viewer.test.tsx
  • web/ui/src/components/json-viewer.tsx
  • web/ui/src/components/list-pane.tsx
  • web/ui/src/components/script-editor.tsx
  • web/ui/src/components/status-badge.test.tsx
  • web/ui/src/components/status-badge.tsx
  • web/ui/src/components/theme-toggle.tsx
  • web/ui/src/components/ui/badge.tsx
  • web/ui/src/components/ui/button.tsx
  • web/ui/src/components/ui/card.tsx
  • web/ui/src/components/ui/checkbox.tsx
  • web/ui/src/components/ui/dialog.tsx
  • web/ui/src/components/ui/dropdown-menu.tsx
  • web/ui/src/components/ui/input.tsx
  • web/ui/src/components/ui/label.tsx
  • web/ui/src/components/ui/scroll-area.tsx
  • web/ui/src/components/ui/select.tsx
  • web/ui/src/components/ui/separator.tsx
  • web/ui/src/components/ui/skeleton.tsx
  • web/ui/src/components/ui/sonner.tsx
  • web/ui/src/components/ui/switch.tsx
  • web/ui/src/components/ui/table.tsx
  • web/ui/src/components/ui/tabs.tsx
  • web/ui/src/components/ui/textarea.tsx
  • web/ui/src/components/ui/tooltip.tsx
  • web/ui/src/index.css
  • web/ui/src/lib/api.test.ts
  • web/ui/src/lib/api.ts
  • web/ui/src/lib/queries.ts
  • web/ui/src/lib/types.ts
  • web/ui/src/lib/utils.test.ts
  • web/ui/src/lib/utils.ts
  • web/ui/src/main.tsx
  • web/ui/src/router.tsx
  • web/ui/src/routes/action-form.test.tsx
  • web/ui/src/routes/action-form.tsx
  • web/ui/src/routes/action-payload.test.ts
  • web/ui/src/routes/create-source-dialog.test.tsx
  • web/ui/src/routes/create-source-dialog.tsx
  • web/ui/src/routes/dashboard.test.tsx
  • web/ui/src/routes/dashboard.tsx
  • web/ui/src/routes/deliveries-layout.test.tsx
  • web/ui/src/routes/deliveries-layout.tsx
  • web/ui/src/routes/delivery-detail.test.tsx
  • web/ui/src/routes/delivery-detail.tsx
  • web/ui/src/routes/empty-state.tsx
  • web/ui/src/routes/error-boundary.tsx
  • web/ui/src/routes/source-actions.test.tsx
  • web/ui/src/routes/source-actions.tsx
  • web/ui/src/routes/source-auth-form.test.tsx
  • web/ui/src/routes/source-auth-form.tsx
  • web/ui/src/routes/source-detail.test.tsx
  • web/ui/src/routes/source-detail.tsx
  • web/ui/src/routes/source-events.test.tsx
  • web/ui/src/routes/source-events.tsx
  • web/ui/src/routes/source-overview.test.tsx
  • web/ui/src/routes/source-overview.tsx
  • web/ui/src/routes/source-script.test.tsx
  • web/ui/src/routes/source-script.tsx
  • web/ui/src/routes/sources-layout.test.tsx
  • web/ui/src/routes/sources-layout.tsx
  • web/ui/src/test/render.tsx
  • web/ui/src/test/setup.ts
  • web/ui/src/vite-env.d.ts
  • web/ui/tsconfig.app.json
  • web/ui/tsconfig.json
  • web/ui/tsconfig.node.json
  • web/ui/vite.config.ts
  • web/ui/vitest.config.ts

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Review on demand using usage pricing
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/vite-react-router-shadcn-c0fac5

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

zachbroad and others added 16 commits July 12, 2026 21:15
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-handling tasks

Commits in-flight UI work (dashboard, header, theme toggle, nuqs status
filter, delivery detail body) so per-task commits stay scoped. All 39
frontend tests green at this checkpoint.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
source-events.tsx's multi-select changes were committed alongside the
error-handling task (a7f7618); this commits the rest of that in-flight
feature (checkbox component, queries mutation, tests) so the branch
builds standalone. Typecheck clean; 57/57 frontend tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lly fails

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
zachbroad and others added 4 commits July 15, 2026 19:35
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The feat/webhook-auth branch shipped verification with an htmx-only
config UI. This ports it to the SPA stack:

- GET /api/auth/presets and PUT /api/sources/{slug}/auth, mirroring the
  htmx handler's validation (unknown preset, missing secret/public key,
  fail-closed on blank credentials).
- Authentication card on the React source Overview tab: enable switch,
  provider preset select, secret/public-key fields that follow the
  preset, inline validation, and a has_secret hint since secrets are
  never echoed back.
- Source API responses now carry a sanitized auth_config (enabled,
  scheme, preset, public_key, has_secret) instead of the raw JSON,
  which previously leaked plaintext signing secrets to every client;
  MCP list_sources drops auth_config entirely.
- CORS middleware now allows PUT — its absence failed the browser
  preflight for the new endpoint (caught by live verification).
- Includes the Config -> SourceConfiguration rename in inboundauth and
  a fix for the setupRouter call-site mismatch that broke the
  integration build.
- Docs: API reference for the new endpoints; auth guide updated for
  the SPA flow. Changelog entries under Unreleased.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The SourceConfiguration rename swept up FailureReason, which maps
verification errors to metric labels and has nothing to do with the
config type.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add JSON tree view to payload in delivery detail page React Frontend

1 participant