docs: create roadmap (4 phases)
This commit is contained in:
283
CLAUDE.md
Normal file
283
CLAUDE.md
Normal file
@@ -0,0 +1,283 @@
|
||||
<!-- GSD:project-start source:PROJECT.md -->
|
||||
## Project
|
||||
|
||||
**DiunDashboard**
|
||||
|
||||
A web-based dashboard that receives DIUN webhook events and presents a persistent, visual overview of which Docker services have available updates. Built for self-hosters who use DIUN to monitor container images but need something better than dismissable push notifications — a place that nags you until you actually update.
|
||||
|
||||
**Core Value:** Reliable, persistent visibility into which services need updating — data never disappears, and the dashboard is the one place you trust to show the full picture.
|
||||
|
||||
### Constraints
|
||||
|
||||
- **Tech stack**: Go backend + React frontend — established, no migration
|
||||
- **Database**: Must support both SQLite (simple deploys) and PostgreSQL (robust deploys)
|
||||
- **Deployment**: Docker-first, single-container with optional compose
|
||||
- **No CGO**: Pure Go SQLite driver (modernc.org/sqlite) — must maintain this for easy cross-compilation
|
||||
- **Backward compatible**: Existing users with SQLite databases should be able to upgrade without data loss
|
||||
<!-- GSD:project-end -->
|
||||
|
||||
<!-- GSD:stack-start source:codebase/STACK.md -->
|
||||
## Technology Stack
|
||||
|
||||
## Languages
|
||||
- Go 1.26 - Backend HTTP server and all API logic (`cmd/diunwebhook/main.go`, `pkg/diunwebhook/diunwebhook.go`)
|
||||
- TypeScript ~5.7 - Frontend React SPA (`frontend/src/`)
|
||||
- SQL (SQLite dialect) - Inline schema DDL and queries in `pkg/diunwebhook/diunwebhook.go`
|
||||
## Runtime
|
||||
- Go 1.26 (compiled binary, no runtime needed in production)
|
||||
- Bun (frontend build toolchain, uses `oven/bun:1-alpine` Docker image)
|
||||
- Alpine Linux 3.18 (production container base)
|
||||
- Go modules - `go.mod` at project root (module name: `awesomeProject`)
|
||||
- Bun - `frontend/bun.lock` present for frontend dependencies
|
||||
- Bun - `docs/bun.lock` present for documentation site dependencies
|
||||
## Frameworks
|
||||
- `net/http` (Go stdlib) - HTTP server, routing, and handler registration. No third-party router.
|
||||
- React 19 (`^19.0.0`) - Frontend SPA (`frontend/`)
|
||||
- Vite 6 (`^6.0.5`) - Frontend dev server and build tool (`frontend/vite.config.ts`)
|
||||
- Tailwind CSS 3.4 (`^3.4.17`) - Utility-first CSS (`frontend/tailwind.config.ts`)
|
||||
- shadcn/ui - Component library (uses Radix UI primitives, `class-variance-authority`, `clsx`, `tailwind-merge`)
|
||||
- Radix UI (`@radix-ui/react-tooltip` `^1.1.6`) - Accessible tooltip primitives
|
||||
- dnd-kit (`@dnd-kit/core` `^6.3.1`, `@dnd-kit/utilities` `^3.2.2`) - Drag and drop
|
||||
- Lucide React (`^0.469.0`) - Icon library
|
||||
- simple-icons (`^16.9.0`) - Brand/service icons
|
||||
- VitePress (`^1.6.3`) - Static documentation site (`docs/`)
|
||||
- Go stdlib `testing` package with `httptest` for handler tests
|
||||
- No frontend test framework detected
|
||||
- Vite 6 (`^6.0.5`) - Frontend bundler (`frontend/vite.config.ts`)
|
||||
- TypeScript ~5.7 (`^5.7.2`) - Type checking (`tsc -b` runs before `vite build`)
|
||||
- PostCSS 8.4 (`^8.4.49`) with Autoprefixer 10.4 (`^10.4.20`) - CSS processing (`frontend/postcss.config.js`)
|
||||
- `@vitejs/plugin-react` (`^4.3.4`) - React Fast Refresh for Vite
|
||||
## Key Dependencies
|
||||
- `modernc.org/sqlite` v1.46.1 - Pure-Go SQLite driver (no CGO required). Registered as `database/sql` driver named `"sqlite"`.
|
||||
- `modernc.org/libc` v1.67.6 - C runtime emulation for pure-Go SQLite
|
||||
- `modernc.org/memory` v1.11.0 - Memory allocator for pure-Go SQLite
|
||||
- `github.com/dustin/go-humanize` v1.0.1 - Human-readable formatting (indirect dep of modernc.org/sqlite)
|
||||
- `github.com/google/uuid` v1.6.0 - UUID generation (indirect)
|
||||
- `github.com/mattn/go-isatty` v0.0.20 - Terminal detection (indirect)
|
||||
- `golang.org/x/sys` v0.37.0 - System calls (indirect)
|
||||
- `golang.org/x/exp` v0.0.0-20251023 - Experimental packages (indirect)
|
||||
- `react` / `react-dom` `^19.0.0` - UI framework
|
||||
- `@dnd-kit/core` `^6.3.1` - Drag-and-drop for tag assignment
|
||||
- `tailwindcss` `^3.4.17` - Styling
|
||||
- `class-variance-authority` `^0.7.1` - shadcn/ui component variant management
|
||||
- `clsx` `^2.1.1` - Conditional CSS class composition
|
||||
- `tailwind-merge` `^2.6.0` - Tailwind class deduplication
|
||||
## Configuration
|
||||
- `PORT` - HTTP listen port (default: `8080`)
|
||||
- `DB_PATH` - SQLite database file path (default: `./diun.db`)
|
||||
- `WEBHOOK_SECRET` - Token for webhook authentication (optional; when unset, webhook is open)
|
||||
- `go.mod` - Go module definition (module `awesomeProject`)
|
||||
- `frontend/vite.config.ts` - Vite config with `@` path alias to `./src`, dev proxy for `/api` and `/webhook` to `:8080`
|
||||
- `frontend/tailwind.config.ts` - Tailwind with shadcn/ui theme tokens (dark mode via `class` strategy)
|
||||
- `frontend/postcss.config.js` - PostCSS with Tailwind and Autoprefixer plugins
|
||||
- `frontend/tsconfig.json` - Project references to `tsconfig.node.json` and `tsconfig.app.json`
|
||||
- `@` resolves to `frontend/src/` (configured in `frontend/vite.config.ts`)
|
||||
## Database
|
||||
## Platform Requirements
|
||||
- Go 1.26+
|
||||
- Bun (for frontend and docs development)
|
||||
- No CGO required (pure-Go SQLite driver)
|
||||
- Single static binary + `frontend/dist/` static assets
|
||||
- Alpine Linux 3.18 Docker container
|
||||
- Persistent volume at `/data/` for SQLite database
|
||||
- Port 8080 (configurable via `PORT`)
|
||||
- Gitea Actions with custom Docker image `gitea.jeanlucmakiola.de/makiolaj/docker-node-and-go` (contains both Go and Node/Bun toolchains)
|
||||
- `GOTOOLCHAIN=local` env var set in CI
|
||||
<!-- GSD:stack-end -->
|
||||
|
||||
<!-- GSD:conventions-start source:CONVENTIONS.md -->
|
||||
## Conventions
|
||||
|
||||
## Naming Patterns
|
||||
- Package-level source files use the package name: `diunwebhook.go`
|
||||
- Test files follow Go convention: `diunwebhook_test.go`
|
||||
- Test-only export files: `export_test.go`
|
||||
- Entry point: `main.go` inside `cmd/diunwebhook/`
|
||||
- PascalCase for exported functions: `WebhookHandler`, `UpdateEvent`, `InitDB`, `GetUpdates`
|
||||
- Handler functions are named `<Noun>Handler`: `WebhookHandler`, `UpdatesHandler`, `DismissHandler`, `TagsHandler`, `TagByIDHandler`, `TagAssignmentHandler`
|
||||
- Test functions use `Test<FunctionName>_<Scenario>`: `TestWebhookHandler_BadRequest`, `TestDismissHandler_NotFound`
|
||||
- PascalCase structs: `DiunEvent`, `UpdateEntry`, `Tag`
|
||||
- JSON tags use snake_case: `json:"diun_version"`, `json:"hub_link"`, `json:"received_at"`
|
||||
- Package-level unexported variables use short names: `mu`, `db`, `webhookSecret`
|
||||
- Local variables use short idiomatic Go names: `w`, `r`, `err`, `res`, `n`, `e`
|
||||
- Components: PascalCase `.tsx` files: `ServiceCard.tsx`, `AcknowledgeButton.tsx`, `Header.tsx`, `TagSection.tsx`
|
||||
- Hooks: camelCase with `use` prefix: `useUpdates.ts`, `useTags.ts`
|
||||
- Types: camelCase `.ts` files: `diun.ts`
|
||||
- Utilities: camelCase `.ts` files: `utils.ts`, `time.ts`, `serviceIcons.ts`
|
||||
- UI primitives (shadcn): lowercase `.tsx` files: `badge.tsx`, `button.tsx`, `card.tsx`, `tooltip.tsx`
|
||||
- camelCase for regular functions and hooks: `fetchUpdates`, `useUpdates`, `getServiceIcon`
|
||||
- PascalCase for React components: `ServiceCard`, `StatCard`, `AcknowledgeButton`
|
||||
- Helper functions within components use camelCase: `getInitials`, `getTag`, `getShortName`
|
||||
- Event handlers prefixed with `handle`: `handleDragEnd`, `handleNewGroupSubmit`
|
||||
- PascalCase interfaces: `DiunEvent`, `UpdateEntry`, `Tag`, `ServiceCardProps`
|
||||
- Type aliases: PascalCase: `UpdatesMap`
|
||||
- Interface properties use snake_case matching the Go JSON tags: `diun_version`, `hub_link`
|
||||
## Code Style
|
||||
- `gofmt` enforced in CI (formatting check fails the build)
|
||||
- No additional Go linter (golangci-lint) configured
|
||||
- `go vet` runs in CI
|
||||
- Standard Go formatting: tabs for indentation
|
||||
- No ESLint or Prettier configured in the frontend
|
||||
- No formatting enforcement in CI for frontend code
|
||||
- Consistent 2-space indentation observed in all `.tsx` and `.ts` files
|
||||
- Single quotes for strings in TypeScript
|
||||
- No semicolons (observed in all frontend files)
|
||||
- Trailing commas used in multi-line constructs
|
||||
- `strict: true` in `tsconfig.app.json`
|
||||
- `noUnusedLocals: true`
|
||||
- `noUnusedParameters: true`
|
||||
- `noFallthroughCasesInSwitch: true`
|
||||
- `noUncheckedSideEffectImports: true`
|
||||
## Import Organization
|
||||
- The project module is aliased as `diun` in both `main.go` and test files
|
||||
- The blank-import pattern `_ "modernc.org/sqlite"` is used for the SQLite driver in `pkg/diunwebhook/diunwebhook.go`
|
||||
- `@/` maps to `frontend/src/` (configured in `vite.config.ts` and `tsconfig.app.json`)
|
||||
## Error Handling
|
||||
- Handlers use `http.Error(w, message, statusCode)` for all error responses
|
||||
- Error messages are lowercase: `"bad request"`, `"internal error"`, `"not found"`, `"method not allowed"`
|
||||
- Internal errors are logged with `log.Printf` before returning HTTP 500
|
||||
- Decode errors include context: `log.Printf("WebhookHandler: failed to decode request: %v", err)`
|
||||
- Fatal errors in `main.go` use `log.Fatalf`
|
||||
- `errors.Is()` used for sentinel error comparison (e.g., `http.ErrServerClosed`)
|
||||
- String matching used for SQLite constraint errors: `strings.Contains(err.Error(), "UNIQUE")`
|
||||
- API errors throw with HTTP status: `throw new Error(\`HTTP ${res.status}\`)`
|
||||
- Catch blocks use `console.error` for logging
|
||||
- Error state stored in hook state: `setError(e instanceof Error ? e.message : 'Failed to fetch updates')`
|
||||
- Optimistic updates used for tag assignment (update UI first, then call API)
|
||||
## Logging
|
||||
- Startup messages: `log.Printf("Listening on :%s", port)`
|
||||
- Warnings: `log.Println("WARNING: WEBHOOK_SECRET not set ...")`
|
||||
- Request logging on success: `log.Printf("Update received: %s (%s)", event.Image, event.Status)`
|
||||
- Error logging before HTTP error response: `log.Printf("WebhookHandler: failed to store event: %v", err)`
|
||||
- Handler name prefixed to log messages: `"WebhookHandler: ..."`, `"UpdatesHandler: ..."`
|
||||
## Comments
|
||||
- Comments are sparse in the Go codebase
|
||||
- Handler functions have short doc comments describing the routes they handle:
|
||||
- Inline comments used for non-obvious behavior: `// Migration: add acknowledged_at to existing databases`
|
||||
- No JSDoc/TSDoc in the frontend codebase
|
||||
## Function Design
|
||||
- Each handler is a standalone `func(http.ResponseWriter, *http.Request)`
|
||||
- Method checking done at the top of each handler (not via middleware)
|
||||
- Multi-method handlers use `switch r.Method`
|
||||
- URL path parameters extracted via `strings.TrimPrefix`
|
||||
- Request bodies decoded with `json.NewDecoder(r.Body).Decode(&target)`
|
||||
- Responses written with `json.NewEncoder(w).Encode(data)` or `w.WriteHeader(status)`
|
||||
- Mutex (`mu`) used around write operations to SQLite
|
||||
- Custom hooks return object with state and action functions
|
||||
- `useCallback` wraps all action functions
|
||||
- `useEffect` for side effects (polling, initial fetch)
|
||||
- State updates use functional form: `setUpdates(prev => { ... })`
|
||||
## Module Design
|
||||
- Single package `diunwebhook` exports all types and handler functions
|
||||
- No barrel files; single source file `diunwebhook.go` contains everything
|
||||
- Test helpers exposed via `export_test.go` (only visible to `_test` packages)
|
||||
- Named exports for all components, hooks, and utilities
|
||||
- Default export only for the root `App` component (`export default function App()`)
|
||||
- Type exports use `export interface` or `export type`
|
||||
- `@/components/ui/` contains shadcn primitives (`badge.tsx`, `button.tsx`, etc.)
|
||||
## Git Commit Message Conventions
|
||||
- `feat` - new features
|
||||
- `fix` - bug fixes
|
||||
- `docs` - documentation changes
|
||||
- `chore` - maintenance tasks (deps, config)
|
||||
- `refactor` - code restructuring
|
||||
- `style` - UI/styling changes
|
||||
- `test` - test additions
|
||||
<!-- GSD:conventions-end -->
|
||||
|
||||
<!-- GSD:architecture-start source:ARCHITECTURE.md -->
|
||||
## Architecture
|
||||
|
||||
## Pattern Overview
|
||||
- Single Go binary serves both the JSON API and the static frontend assets
|
||||
- All backend logic lives in one library package (`pkg/diunwebhook/`)
|
||||
- SQLite database for persistence (pure-Go driver, no CGO)
|
||||
- Frontend is a standalone React SPA that communicates via REST polling
|
||||
- No middleware framework -- uses `net/http` standard library directly
|
||||
## Layers
|
||||
- Purpose: Accept HTTP requests, validate input, delegate to storage functions, return JSON responses
|
||||
- Location: `pkg/diunwebhook/diunwebhook.go` (functions: `WebhookHandler`, `UpdatesHandler`, `DismissHandler`, `TagsHandler`, `TagByIDHandler`, `TagAssignmentHandler`)
|
||||
- Contains: Request parsing, method checks, JSON encoding/decoding, HTTP status responses
|
||||
- Depends on: Storage layer (package-level `db` and `mu` variables)
|
||||
- Used by: Route registration in `cmd/diunwebhook/main.go`
|
||||
- Purpose: Persist and query DIUN events, tags, and tag assignments
|
||||
- Location: `pkg/diunwebhook/diunwebhook.go` (functions: `InitDB`, `UpdateEvent`, `GetUpdates`; inline SQL in handlers)
|
||||
- Contains: Schema creation, migrations, CRUD operations via raw SQL
|
||||
- Depends on: `modernc.org/sqlite` driver, `database/sql` stdlib
|
||||
- Used by: HTTP handlers in the same file
|
||||
- Purpose: Initialize database, configure routes, start HTTP server with graceful shutdown
|
||||
- Location: `cmd/diunwebhook/main.go`
|
||||
- Contains: Environment variable reading, mux setup, signal handling, server lifecycle
|
||||
- Depends on: `pkg/diunwebhook` (imported as `diun`)
|
||||
- Used by: Docker container CMD, direct `go run`
|
||||
- Purpose: Display DIUN update events in an interactive dashboard with drag-and-drop grouping
|
||||
- Location: `frontend/src/`
|
||||
- Contains: React components, custom hooks for data fetching, TypeScript type definitions
|
||||
- Depends on: Backend REST API (`/api/*` endpoints)
|
||||
- Used by: Served as static files from `frontend/dist/` by the Go server
|
||||
## Data Flow
|
||||
- **Backend:** No in-memory state beyond the `sync.Mutex`. All data lives in SQLite. The `db` and `mu` variables are package-level globals in `pkg/diunwebhook/diunwebhook.go`.
|
||||
- **Frontend:** React `useState` hooks in two custom hooks:
|
||||
- No global state library (no Redux, Zustand, etc.) -- state is passed via props from `App.tsx`
|
||||
## Key Abstractions
|
||||
- Purpose: Represents a single DIUN webhook payload (image update notification)
|
||||
- Defined in: `pkg/diunwebhook/diunwebhook.go` (Go struct), `frontend/src/types/diun.ts` (TypeScript interface)
|
||||
- Pattern: Direct JSON mapping between Go struct tags and TypeScript interface
|
||||
- Purpose: Wraps a `DiunEvent` with metadata (received timestamp, acknowledged flag, optional tag)
|
||||
- Defined in: `pkg/diunwebhook/diunwebhook.go` (Go), `frontend/src/types/diun.ts` (TypeScript)
|
||||
- Pattern: The API returns `map[string]UpdateEntry` keyed by image name (`UpdatesMap` type in frontend)
|
||||
- Purpose: User-defined grouping label for organizing images
|
||||
- Defined in: `pkg/diunwebhook/diunwebhook.go` (Go), `frontend/src/types/diun.ts` (TypeScript)
|
||||
- Pattern: Simple ID + name, linked to images via `tag_assignments` join table
|
||||
## Entry Points
|
||||
- Location: `cmd/diunwebhook/main.go`
|
||||
- Triggers: `go run ./cmd/diunwebhook/` or Docker container `CMD ["./server"]`
|
||||
- Responsibilities: Read env vars (`DB_PATH`, `PORT`, `WEBHOOK_SECRET`), init DB, register routes, start HTTP server, handle graceful shutdown on SIGINT/SIGTERM
|
||||
- Location: `frontend/src/main.tsx`
|
||||
- Triggers: Browser loads `index.html` from `frontend/dist/` (served by Go file server at `/`)
|
||||
- Responsibilities: Mount React app, force dark mode (`document.documentElement.classList.add('dark')`)
|
||||
- Location: `POST /webhook` -> `WebhookHandler` in `pkg/diunwebhook/diunwebhook.go`
|
||||
- Triggers: External DIUN instance sends webhook on image update detection
|
||||
- Responsibilities: Authenticate (if secret set), validate payload, upsert event into database
|
||||
## Concurrency Model
|
||||
- A single `sync.Mutex` (`mu`) in `pkg/diunwebhook/diunwebhook.go` guards all write operations to the database
|
||||
- `UpdateEvent()`, `DismissHandler`, `TagsHandler` (POST), `TagByIDHandler` (DELETE), and `TagAssignmentHandler` (PUT/DELETE) all acquire `mu.Lock()` before writing
|
||||
- Read operations (`GetUpdates`, `TagsHandler` GET) do NOT acquire the mutex
|
||||
- SQLite connection is configured with `db.SetMaxOpenConns(1)` to prevent concurrent write issues
|
||||
- Standard `net/http` server handles requests concurrently via goroutines
|
||||
- Graceful shutdown with 15-second timeout on SIGINT/SIGTERM
|
||||
## Error Handling
|
||||
- Method validation: Return `405 Method Not Allowed` for wrong HTTP methods
|
||||
- Input validation: Return `400 Bad Request` for missing/malformed fields
|
||||
- Authentication: Return `401 Unauthorized` if webhook secret doesn't match
|
||||
- Not found: Return `404 Not Found` when row doesn't exist (e.g., dismiss nonexistent image)
|
||||
- Conflict: Return `409 Conflict` for unique constraint violations (duplicate tag name)
|
||||
- Internal errors: Return `500 Internal Server Error` for database failures
|
||||
- Fatal startup errors: `log.Fatalf` on `InitDB` failure
|
||||
- `useUpdates`: catches fetch errors, stores error message in state, displays error banner
|
||||
- `useTags`: catches errors, logs to `console.error`, fails silently (no user-visible error)
|
||||
- `assignTag`: uses optimistic update -- updates local state first, fires API call, logs errors to console but does not revert on failure
|
||||
## Cross-Cutting Concerns
|
||||
<!-- GSD:architecture-end -->
|
||||
|
||||
<!-- GSD:workflow-start source:GSD defaults -->
|
||||
## GSD Workflow Enforcement
|
||||
|
||||
Before using Edit, Write, or other file-changing tools, start work through a GSD command so planning artifacts and execution context stay in sync.
|
||||
|
||||
Use these entry points:
|
||||
- `/gsd:quick` for small fixes, doc updates, and ad-hoc tasks
|
||||
- `/gsd:debug` for investigation and bug fixing
|
||||
- `/gsd:execute-phase` for planned phase work
|
||||
|
||||
Do not make direct repo edits outside a GSD workflow unless the user explicitly asks to bypass it.
|
||||
<!-- GSD:workflow-end -->
|
||||
|
||||
|
||||
|
||||
<!-- GSD:profile-start -->
|
||||
## Developer Profile
|
||||
|
||||
> Profile not yet configured. Run `/gsd:profile-user` to generate your developer profile.
|
||||
> This section is managed by `generate-claude-profile` -- do not edit manually.
|
||||
<!-- GSD:profile-end -->
|
||||
Reference in New Issue
Block a user