This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
make build # Frontend (CSS+JS) + server + CLI
make build-server # Server only (faster iteration)
make frontend # CSS + JS only
make frontend-js # JS only (esbuild bundle)
make dev-frontend # Watch mode for CSS + JS
make start # Build + start server in background (port 8888)
make stop # Stop background server
make restart # Stop + start
make test # go test -v -race ./...
make smoke-test # Full smoke test (build→start→test→stop)
make lint # golangci-lint
make fmt # gofmt -s -w .
make wire # Regenerate Wire DI code (after changing providers)Server requires project root .env with AUTH_SECRET_KEY, JWT_SECRET, MYSQL_PASSWORD, OPENROUTER_API_KEY (see .env.example). Config: conf/app_dev.yaml (uses ${VAR} env interpolation).
DDD + Clean Architecture with Wire DI.
cmd/server/main.go → chi router, routes, graceful shutdown
app/di/ → Wire DI (wire.go → wire_gen.go), App struct
app/handler/ → HTTP handlers (REST API + web pages)
app/application/ → Usecases (url, search, analysis)
app/domain/entity/ → GORM models (URL, ShortLink)
app/domain/repos/ → Repository interfaces
app/domain/services/ → Domain services (visit tracking)
app/infra/ → Infrastructure (db/SQLite, llm/OpenRouter, search/bleve, browser/rod, config)
app/middleware/ → JWT auth middleware
Request flow: chi router → handler → usecase → repo/service → infra
web/templates/spa.html → Single HTML shell (serves all routes)
web/src/js/app.jsx → Preact entry point (Router + Layout)
web/src/js/api.js → JSON API client (fetch wrapper with JWT auth)
web/src/js/store.js → Shared state (@preact/signals)
web/src/js/utils.js → Utilities (getCookie, copyToClipboard)
web/src/js/pages/ → Page components (LoginPage, IndexPage, DetailPage)
web/src/js/components/ → Shared components (Layout, URLCard, SearchBar, ColorPicker)
web/src/css/app.css → Tailwind v4 entry (@import "tailwindcss" + @theme block)
web/static/ → Built assets (served at /static/)
SPA architecture: Go server serves spa.html for all non-API, non-static routes via r.NotFound(). Preact handles client-side routing with preact-router. All data flows through JSON APIs (/api/*). Auth uses JWT stored in linkstash_token cookie.
Design system: Refined Dark theme using Slate color palette (bg-primary #0f172a, bg-surface #1e293b) with Sky accent (#38bdf8). Custom design tokens defined in CSS @theme {} block. Component classes: .input, .btn, .btn-primary, .btn-danger, .surface-card, .link-item, .filter-panel, .filter-chip. Card color themes via .card-theme-* classes.
Key frontend patterns:
- Compact link grid: responsive 1→2→3 column grid of two-line link items, click navigates to detail
- Collapsible filters: SearchBar with Filters button, chip-style type/category selectors, active filter count badge
- Infinite scroll: IntersectionObserver on sentinel div, increments page state
- Search: fetches
/api/searchwith query params, renders client-side - ESC key: clears search query, resets filters, returns to default URL list
- Score filter: client-side min_score dropdown for hybrid search results (inside filter panel)
- State:
@preact/signalsfor auth,useState/useEffectfor component state - JSON field names: API returns lowercase snake_case (
id,created_at,auto_weight), not GORM uppercase
POST /api/auth/token— get JWT (body:{"secret_key":"..."})GET/POST/PUT/DELETE /api/urls[/{id}]— CRUD (JWT required)GET /api/search?q=...&type=keyword|semantic|hybrid— searchPOST /api/short-links— create short URLGET /s/{code}— short URL redirectGET /* (non-API)— SPA catch-all (serves spa.html)
- Go: standard
slogfor logging,chifor routing,gormfor ORM (SQLite/MySQL) - Frontend: Preact SPA with preact-router and @preact/signals; esbuild bundles
web/src/js/app.jsx→web/static/js/app.js(with--jsx=automatic --jsx-import-source=preact); Tailwind v4 standalone CLI (tools/tailwindcss) buildsweb/src/css/app.css→web/static/css/app.css; notailwind.config.js(v4 uses@theme {}in CSS) - npm:
package.jsonmanages preact deps;node_modules/is gitignored; runnpm installafter clone - Config: YAML in
conf/, env vars interpolated with${VAR}syntax - DI: Google Wire — edit
app/di/wire.go, runmake wireto regenerate - Search: Bleve full-text index + OpenRouter embedding for semantic search
- URL analysis: async worker fetches page via headless Chrome (rod), sends to LLM for title/keywords/description/category extraction