diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..418d07c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,181 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Lenz-Web is a Go web application that serves digitized historical letters. It's built with: +- Go 1.24 using Fiber web framework +- Custom templating engine with hot reload for development +- Git-based content management (clones/updates from remote repository) +- XML parsing for letter metadata and content +- Static asset serving with compression and ETags + +## Development Commands + +### Build and Run +```bash +go build # Build the application +./lenz-web # Run the built binary +go run . # Build and run directly +``` + +### Docker +```bash +docker-compose up # Run with Docker (exposes port 8085) +docker build . # Build Docker image +``` + +### Testing +The project does not currently have automated tests. Check for `*_test.go` files before assuming test frameworks. + +## Architecture + +### Main Application Flow (lenz.go:30-69) +1. Loads configuration from JSON files and environment variables +2. Clones/opens Git repository containing letter data +3. Parses XML models from the repository +4. Sets up templating engine with custom functions +5. Configures Fiber web server with routes and middleware +6. In debug mode, enables file watchers for hot reload + +### Core Components + +**Configuration (config/)** +- `config.go`: Configuration management with JSON files, environment variables, and defaults +- Uses `KGPZ_` prefix for environment variables +- Default config file: `config.dev.json` + +**Templating (templating/)** +- Custom template engine with layout and route registries +- WebSocket-based hot reload in debug mode +- Template functions for XML data processing + +**Controllers (controllers/)** +- Route handlers for web endpoints +- `routes.go`: Route registration and URL constants +- Main routes: `/`, `/briefe`, `/brief/:letter`, static pages + +**XML Models (xmlmodels/)** +- Parses historical letter data from XML files +- Provides template functions for data access +- Core models: Library, Letter, Traditions, References + +**File Watching (watcher.go)** +- Custom file watcher with debouncing (300ms) +- Two watch modes: + - REFRESH_CHANGES: `./views/assets` (clears cache, sends refresh signal) + - RESET_CHANGES: `./views/layouts`, `./views/routes` (clears cache, reloads templates) + +### Key Directories +- `views/`: Frontend templates, layouts, and static assets +- `helpers/`: Utility functions and middleware +- `server/`: Fiber server configuration and caching +- `git/`: Git repository management + +## Configuration + +Configuration is loaded in order: JSON files → environment variables → defaults + +Key settings: +- `debug`: Enables hot reload and verbose logging +- `git_url`: Repository URL for letter content +- `base_dir`: Cache directory (default: `_cache`) +- `port`: Server port (default: `8085`) +- `webhook_secret`: GitHub webhook integration + +Environment variables use `KGPZ_` prefix (e.g., `KGPZ_DEBUG=true`). + +## Views Directory Structure + +The `views/` directory contains all frontend templates and assets: + +``` +views/ +├── assets/ # Static files (CSS, JS, fonts, images) +├── layouts/ # Base layout templates +│ ├── components/ # Reusable layout components (_header, _footer, _menu, etc.) +│ └── default/ # Default layout (root.gohtml) +└── routes/ # Page-specific templates + ├── brief/ # Individual letter view (head.gohtml, body.gohtml) + ├── briefe/ # Letter listing view + ├── components/ # Page components (_letterhead, _letterlist, _lettertrad) + ├── datenschutz/ # Privacy page + ├── edition/ # Edition info pages + └── kontakt/ # Contact page +``` + +### Template System Architecture + +**Build-Time Embedding vs Development** +- Production: Templates embedded via `//go:embed` directives in `views/embed.go` +- Development: Direct filesystem access via `views/embed_dev.go` (when built with `-tags dev`) +- File watchers enable hot reload in debug mode + +**Template Structure** +Each route directory contains: +- `head.gohtml`: `` section content for the page +- `body.gohtml`: Main page content + +**Layout System (templating/engine.go)** +- Base layout: `views/layouts/default/root.gohtml` +- Uses Go template blocks: `{{ block "head" . }}`, `{{ block "body" . }}`, etc. +- Layout registry loads and caches layouts with template functions +- Template registry loads route-specific templates + +**Template Functions Available** +Core functions (templating/engine.go:118-131): +- `Safe`: Render HTML without escaping +- `Today`: Current date +- `GetMonth`: Month name from number +- `Minus`: Subtraction for template math + +XML Data Functions (added from xmlmodels/library.go): +- `Person`: Get person data by reference ID +- `Place`: Get location data by reference ID +- `ParseGeneric`: Parse and render letter text with markup + +**Template Data Flow** +1. Controllers pass `fiber.Map` data to templates +2. Engine merges with `GlobalData` (debug flags, etc.) +3. Layout loaded and cloned from registry +4. Route templates added to layout clone +5. Combined template executed with merged data + +**Hot Reload System** +- WebSocket server on port 9000 for live reload in debug mode +- File watchers trigger different actions: + - `./views/assets` changes: Clear cache + browser refresh signal + - `./views/layouts`, `./views/routes` changes: Clear cache + reload templates +- 300ms debounce to prevent excessive reloads + +### Working with Templates + +**Template Syntax** +- Standard Go template syntax with custom functions +- Variables: `{{ $model := . }}` for cleaner references +- Conditionals: `{{ if .isDev }}...{{ end }}` +- Loops: `{{ range $item := .items }}...{{ end }}` +- Components: `{{ template "_letterhead" .meta }}` + +**Debugging Templates** +- Enable debug mode in config: `"debug": true` +- Templates reload automatically on file changes +- Browser refreshes automatically via WebSocket +- Console shows reload notifications + +**Letter Data Templates** +Letter pages have access to rich XML data: +- `.meta`: Letter metadata (dates, persons, places) +- `.text`: Parsed letter content with markup +- `.prev/.next`: Navigation to adjacent letters +- Navigation uses custom functions like `Person` and `Place` to resolve references + +## Development Notes + +- File watchers only active in debug mode +- Templates are cached; use debug mode for development +- Static assets served with compression and ETags +- Git repository is cloned to `base_dir/git_path` on startup +- WebSocket endpoint available for live reload in debug mode +- Template files use `.gohtml` extension for proper syntax highlighting \ No newline at end of file diff --git a/config.dev.json b/config.dev.json index db5ae87..fa9d630 100644 --- a/config.dev.json +++ b/config.dev.json @@ -1,6 +1,6 @@ { "debug": true, "watch": true, - "git_url": "git@github.com:Theodor-Springmann-Stiftung/lenz-briefe.git", + "git_url": "https://github.com/Theodor-Springmann-Stiftung/lenz-briefe.git", "webhook_secret": "test_secret" } diff --git a/views/package-lock.json b/views/package-lock.json index b40cc21..d429d49 100644 --- a/views/package-lock.json +++ b/views/package-lock.json @@ -8,6 +8,9 @@ "name": "caveman_views", "version": "1.0.0", "license": "MIT", + "dependencies": { + "pagedjs": "^0.4.3" + }, "devDependencies": { "@laynezh/vite-plugin-lib-assets": "^1.1.0", "@tailwindcss/postcss": "^4.1.4", @@ -31,6 +34,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@babel/polyfill": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.12.1.tgz", + "integrity": "sha512-X0pi0V6gxLi6lFZpGmeNa4zxtwEmCs42isWLNjZZDE0Y8yVfgu0T2OAHlzBbdYlqbW/YXVvoBHpATEM+goCj8g==", + "deprecated": "🚨 This package has been deprecated in favor of separate inclusion of a polyfill and regenerator-runtime (when needed). See the @babel/polyfill docs (https://babeljs.io/docs/en/babel-polyfill) for more information.", + "license": "MIT", + "dependencies": { + "core-js": "^2.6.5", + "regenerator-runtime": "^0.13.4" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@esbuild/linux-x64": { "version": "0.25.0", "cpu": [ @@ -254,6 +277,12 @@ "fsevents": "~2.3.2" } }, + "node_modules/clear-cut": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/clear-cut/-/clear-cut-2.0.2.tgz", + "integrity": "sha512-WVgn/gSejQ+0aoR8ucbKIdo6icduPZW6AbWwyUmAUgxy63rUYjwa5rj/HeoNPhf0/XPrl82X8bO/hwBkSmsFtg==", + "license": "MIT" + }, "node_modules/cliui": { "version": "8.0.1", "dev": true, @@ -283,6 +312,40 @@ "dev": true, "license": "MIT" }, + "node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true, + "license": "MIT" + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/daisyui": { "version": "5.0.28", "dev": true, @@ -324,6 +387,46 @@ "node": ">=10.13.0" } }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/esbuild": { "version": "0.25.0", "dev": true, @@ -382,6 +485,40 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "license": "ISC", + "dependencies": { + "type": "^2.7.2" + } + }, "node_modules/fdir": { "version": "6.4.4", "dev": true, @@ -592,6 +729,12 @@ "node": ">= 12.13.0" } }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "license": "CC0-1.0" + }, "node_modules/mrmime": { "version": "1.0.1", "dev": true, @@ -617,6 +760,12 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "license": "ISC" + }, "node_modules/normalize-path": { "version": "3.0.0", "dev": true, @@ -625,6 +774,19 @@ "node": ">=0.10.0" } }, + "node_modules/pagedjs": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/pagedjs/-/pagedjs-0.4.3.tgz", + "integrity": "sha512-YtAN9JAjsQw1142gxEjEAwXvOF5nYQuDwnQ67RW2HZDkMLI+b4RsBE37lULZa9gAr6kDAOGBOhXI4wGMoY3raw==", + "license": "MIT", + "dependencies": { + "@babel/polyfill": "^7.10.1", + "@babel/runtime": "^7.21.0", + "clear-cut": "^2.0.2", + "css-tree": "^1.1.3", + "event-emitter": "^0.3.5" + } + }, "node_modules/picocolors": { "version": "1.1.1", "dev": true, @@ -832,6 +994,12 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, "node_modules/require-directory": { "version": "2.1.1", "dev": true, @@ -899,6 +1067,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "dev": true, @@ -975,6 +1152,12 @@ "node": ">=8.0" } }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "license": "ISC" + }, "node_modules/ulid": { "version": "2.3.0", "dev": true,