From c26b54398c7d6944b15f9b34812939e50fe4b9de Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Sun, 9 Aug 2026 03:15:54 +0200 Subject: [PATCH] docs: replace AGENTS.md with a repo-specific CLAUDE.md One agent doc per repo, and it is CLAUDE.md. Written fresh rather than reformatted. PHILOSOPHY.md linked to the old file, so that pointer moves with it, and its standing requirement to be read before working here is carried forward rather than lost in the rewrite. Two claims did not survive verification. The app is published on 9999, not the tower:8092 the old file gave, and it pointed at portainer-core for full-stack documentation -- that repo is deprecated and must not be used as a source of infra facts. Establishing what is live needs a different method here: there is no sys.modules to read, since the container holds a compiled web build rather than source. A transitive walk of import/export/part directives from lib/main.dart found 8 of 132 files unreachable, and five of those are exactly what runs in production. They are conditional-import targets -- `import 'a.dart' if (dart.library.html) 'b.dart'` -- and a walk that takes the first string misses the branch. Since this ships as Flutter web, the _web half is live and the _stub/_native half is dormant. The naive reading was not merely wrong but inverted. Of the three genuinely unreferenced files, stack_model.dart is imported only by its own test, so the suite is green and vouches for a model the app never uses. permission_gate.dart sits next to an unimplemented auth redesign and is recorded as undetermined rather than dead. Co-Authored-By: Claude --- AGENTS.md | 96 ----------------------------- CLAUDE.md | 163 ++++++++++++++++++++++++++++++++++++++++++++++++++ PHILOSOPHY.md | 2 +- 3 files changed, 164 insertions(+), 97 deletions(-) delete mode 100644 AGENTS.md create mode 100644 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 992fcd8..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,96 +0,0 @@ -# LLM Agent Instructions - -This document contains instructions and documentation references for AI assistants working with this codebase. - -> **๐Ÿ“– Important**: Before working on this project, read [PHILOSOPHY.md](PHILOSOPHY.md) to understand the system vision, architectural patterns, and design goals. All development should work towards realizing those patterns. -# AGENTS.md - -> **Start every session by reading this file.** -> This file outlines the operational protocols, coding standards, and architectural decisions for this Flutter project. - -## 1. Agent Operational Protocols - -### ๐Ÿง  Work Patterns (Plan-Act-Reflect) -* **Plan:** Before writing code, briefly outline your plan. Identify which files you will touch and what the side effects might be. -* **Act:** Execute the changes in small, atomic steps. -* **Reflect:** After coding, verify your work. Did you break existing tests? Did you add new tests? - -### ๐ŸŒ Internal Service Access -* **git.schweitz.net**: Access via `http://localhost:3002` (direct Gitea) to bypass Authentik SSO - * Example: `curl http://localhost:3002/jpmschweitzer/library-desk/raw/branch/main/README.md` - * Public repos are readable without authentication - * Related repos: , `core-api`, `tatlock`, `library-desk`, `scheduler`, `portainer-core` - -### ๐Ÿณ Deployment & Infrastructure - -**โš ๏ธ IMPORTANT: Service Port Reference** -| Service | LAN Port | External URL | Notes | -|---------|----------|--------------|-------| -| **Core API** | 8083 | `api.schweitz.net` | FastAPI backend for this UI | -| **Tatlock API** | 8000 | `tatlock.schweitz.net` | Legacy Python API (Ollama proxy) | -| **Tatlock UI** | 9999 | `home.schweitz.net` | This Flutter app | - -* **Full stack documentation**: Available in the `portainer-core` repo - * Access: `curl http://192.168.86.149:3002/jpmschweitzer/portainer-core/raw/branch/main/CONTAINERS.md` - * Contains: All service ports, URLs, Redis DB allocations, external domains -* **Health checks**: - * Core API: `curl http://192.168.86.149:8083/health` - * Tatlock API: `curl http://192.168.86.149:8000/health` - -### ๐Ÿ›ก๏ธ Git Discipline -* **Commit Messages:** Use the [Conventional Commits](https://www.conventionalcommits.org/) format. - * `feat: add user login endpoint` - * `fix: resolve database connection timeout` - * `refactor: split monolith dependency file` -* **Atomic Commits:** Keep commits small. One logical change = one commit. -* **Version Tagging:** Every version increment (major.minor.patch, not build count) must have a corresponding git tag. - * Format: `v{major}.{minor}.{patch}` (e.g., `v0.3.0`) - * Tag after updating `pubspec.yaml` version and CHANGELOG - * Push tags with `git push --tags` - -### ๐Ÿš€ Release Procedure - -This project uses version-tag-based CI/CD. Releases trigger automated Docker builds and deployments. - -**Release Steps:** - -1. Update version in `pubspec.yaml` (bump major.minor.patch, not build number) -2. Update `CHANGELOG.md` with changes under `## [x.x.x] - YYYY-MM-DD` -3. Commit changes: `git commit -m "chore: release vX.X.X"` -4. Create git tag: `git tag vX.X.X` -5. Push with tags: `git push origin main --tags` - -CI/CD auto-triggers when a tag starting with `v` is pushed. - -**What happens on release:** - -* Gitea CI builds Flutter web app in Docker -* Image pushed to `git.schweitz.net/jpmschweitzer/tatlock-ui:latest` and `:vX.X.X` -* Watchtower detects new image and auto-updates running container -* App available at `http://tower:8092` (and eventually `home.schweitz.net`) - -**Rollback:** - -* In Portainer, update image tag to previous version (e.g., `:v0.2.0`) -* Or: `docker pull git.schweitz.net/jpmschweitzer/tatlock-ui:v0.2.0` - -### ๐Ÿงช Testing Requirements - -* **Always add tests for new code before committing.** No exceptions. -* Tests should cover the happy path and key edge cases. -* Run `flutter test` before committing to ensure all tests pass. -* For widgets: use widget tests. For business logic: use unit tests. -* Code coverage should not decrease with new commits. - -### ๐Ÿ“ Changelog Maintenance - -* **Update `CHANGELOG.md`** with every user-facing change. -* Format: `## [Unreleased] - YYYY-MM-DD` followed by `### Added`, `### Changed`, or `### Fixed`. - -### ๐ŸŽจ UI Patterns (MUST READ BEFORE CHANGES) - -* **Before modifying the widget tree**, read `docs/UI_LAYOUT.md` to understand established patterns. -* Investigate existing implementations in the codebase before creating new components. -* **DO NOT** reinvent wheels - check if shared components already exist in `lib/shared/components/`. -* Look at similar features for reference patterns (e.g., how other list views, forms, or CRUD screens are built). -* Deviating from established patterns creates inconsistency and technical debt. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..ef48929 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,163 @@ +# CLAUDE.md โ€” tatlock-ui + +Flutter/Dart frontend for the homelab โ€” the dashboard at `home.schweitz.net`. Riverpod state, +Material 3, Go-Router. Built as a **Flutter web** app, served as compiled static assets by nginx +in the `tatlock-ui` container. `pubspec.yaml` version **1.7.1+1**, package name `tatlock_ui`, +Dart SDK `^3.10.4`. + +It is a **client**, not a service. It talks to core-api (:8083) and tatlock (:8000); it exposes +no API of its own and has no `/openapi.json`. + +## Read first + +- **[PHILOSOPHY.md](PHILOSOPHY.md)** โ€” the system vision and the architectural patterns all work + should move toward. The previous AGENTS.md made this a mandatory pre-work read and that + requirement is carried forward deliberately. +- **[docs/UI_LAYOUT.md](docs/UI_LAYOUT.md)** โ€” **read before touching the widget tree.** Check + `lib/shared/components/` for an existing component before building a new one, and look at how a + comparable feature already does it. Deviating from the established patterns is the main source + of drift here. +- `docs/` also holds `ARCHITECTURE.md`, `API_INTEGRATION.md`, `TESTING.md`, `THEMING.md`, + `DATAGRID.md`, `DEPLOYMENT.md`. + +## Ports and where it runs + +| | | +|---|---| +| Container | `tatlock-ui`, `127.0.0.1:9999 -> 80` (nginx serving the web build) | +| External | `home.schweitz.net` | +| Backends | core-api `:8083`, tatlock `:8000` | + +The old AGENTS.md said the app is "available at `http://tower:8092`". **That is stale** โ€” the +published port is 9999, verified against `docker ps` on 2026-08-09. + +It also pointed at `portainer-core` for full-stack documentation. **`portainer-core` is +deprecated** and must not be used as a source of infra facts; it was merged into +`system-admin-toj/containers/`. The live inventory is `CONTAINERS.md` there. + +The Gitea SSO-bypass trick is real and still works: `http://localhost:3002` reaches Gitea +directly, verified returning `{"version":"1.27.1"}`. Useful for reading a sibling repo's raw +files without going through Authentik. + +## Layout + +`lib/main.dart` โ†’ `lib/app.dart`; `lib/core/` (api, auth, config, error, providers, semantics, +theme), `lib/features//` (control_room, front_hall, media_room, parlor, security, +settings), `lib/routing/`, `lib/shared/` (components, layouts, theme, widgets). 132 Dart files +under `lib/`, 24 test files. + +## Establishing what is live โ€” and the trap in it + +There is no `sys.modules` here and nothing to `docker exec` into: the container holds compiled +assets, not source. The Dart analogue is a transitive walk of `import`/`export`/`part` +directives from `lib/main.dart`, resolving `package:tatlock_ui/โ€ฆ` to `lib/โ€ฆ`. Run 2026-08-09: +124 of 132 files reachable, 8 not. + +**Do not read that as a delete list. Five of the eight are the code that actually runs in +production.** They are conditional-import targets: + +```dart +import 'api_client_native.dart' if (dart.library.html) 'api_client_web.dart'; +``` + +A naive walk captures the *first* string and misses the branch. Since this app ships as Flutter +**web**, the `_web.dart` half is the live one and the `_stub`/`_native` half is the dormant one โ€” +the exact inverse of what the reachability count suggests. The five: `api_client_web.dart`, +`web_utils_web.dart`, `url_strategy_web.dart`, `url_state_web.dart`, +`iframe_view_web.dart`. Find them all with `grep -rn "if (dart.library" lib/`. + +That leaves three genuinely unreferenced files, and they are **not** all the same thing: + +| File | Status | +|---|---| +| `lib/core/auth/permission_gate.dart` | no reference anywhere in `lib/` or `test/` | +| `lib/core/semantics/semantic_widget.dart` | no reference anywhere in `lib/` or `test/` | +| `lib/features/control_room/stacks/data/models/stack_model.dart` | **referenced only by its own test** | + +The third is the interesting one: `stack_model_test.dart` imports and exercises it, so the suite +is green and gives confidence about a model the app never uses. A passing test is not evidence a +thing is wired in. + +Before deleting any of the three, check whether it is intended groundwork rather than debris โ€” +`TODO_AUTH_REFACTOR.md` describes an unimplemented auth redesign, and `permission_gate.dart` sits +squarely in that area. Neither that file nor `PLAN.md` mentions it by name, so its status is +**undetermined**, not dead. Ask before removing. + +## Tooling + +`flutter` and `dart` resolve from `/snap/bin`, which **is** on the non-interactive `PATH` โ€” so +bare commands work here (unlike `pql`, which needs its absolute path). + +```bash +flutter pub get +flutter test # 24 test files +flutter analyze # static analysis; analysis_options.yaml at the repo root +flutter build web --release +``` + +**Always add tests for new code before committing** โ€” happy path plus key edge cases, widget +tests for widgets, unit tests for logic. Coverage should not decrease. Carried over from the +previous AGENTS.md, which stated it as "no exceptions". + +Note there is **no CI test gate**: `.gitea/workflows/build.yml` triggers only on `v*` tag push +and goes straight to build and release. `flutter test` runs locally or not at all. + +## Work tracking + +Work lives in **pql**, not a markdown TODO. **This repo's vault is standalone** โ€” its tickets and +its internal decisions live here in `.pql/` and `governance/`, and travel with a clone, because +`.pql/changelog/` is committed and replayed by the git hooks (D-15). The databases are gitignored +and rebuildable with `pql plan rebuild`. + +`pql` is **not** on the non-interactive `PATH` โ€” invoke it as `/home/jpmschweitzer/.local/bin/pql`. +From inside this repo no `--vault` is needed: pql anchors at the nearest `.git/` ancestor, which +is this repo. + +```bash +/home/jpmschweitzer/.local/bin/pql ticket list # this repo's open work +/home/jpmschweitzer/.local/bin/pql plan whatsnext # next unblocked item, with context +/home/jpmschweitzer/.local/bin/pql decisions list # this repo's own decisions +``` + +Stack-level decisions that constrain this app live in the **workspace** vault and need the flag: + +```bash +/home/jpmschweitzer/.local/bin/pql --vault /mnt/media/Projects decisions list --domain tatlock-ui +``` + +Note `ticket new --decision D-N` resolves ids within **one** vault, so a ticket here cannot link +to a workspace decision. Cite the id in the ticket body instead. + +`PLAN.md` and `TODO_AUTH_REFACTOR.md` predate this convention. Treat them as research notes; +new work goes in pql. + +## Git + +- **History is linear โ€” no merge commits.** Work on `main`, or a short-lived branch that is + fast-forwarded and deleted. +- **Conventional Commits**: `feat:`, `fix:`, `refactor:`, `docs:`, `chore:`. +- **Stage explicitly. Never `git add -A`** โ€” denied by policy, and it sweeps in whatever else is + dirty. +- **Every version increment gets a tag** โ€” `vX.Y.Z`, on the `major.minor.patch` part, not the + build number. +- Update `CHANGELOG.md` for every user-facing change. + +**`.claude/settings.local.json` is currently tracked in git here.** Contents are benign โ€” a +four-entry permission allow list, no `env` block, no secrets โ€” but it is machine-local state that +should not be shared. There is now a `.gitignore` rule for it, and **that rule is inert**: git +applies ignore rules only to untracked paths, so edits still show in `git status` and still get +committed. It starts working only after `git rm --cached .claude/settings.local.json`, which is a +history decision and was deliberately left out of normalization. + +Consequence for checking: plain `git check-ignore` prints nothing for this path โ€” it consults the +index โ€” which looks identical to "no rule exists". Use `--no-index` to test the rule itself. + +## Releasing + +1. Bump `version` in `pubspec.yaml` (the `major.minor.patch` part). +2. Move `[Unreleased]` entries into a dated section in `CHANGELOG.md`. +3. Stage by name, commit, tag `vX.Y.Z`, `git push origin main --tags`. +4. Gitea CI builds the web app in Docker, pushes `:latest` and `:vX.Y.Z`; Watchtower deploys. +5. Verify at `home.schweitz.net`, or `curl -I http://localhost:9999`. + +**Rollback:** in Portainer, point the image tag at the previous version. diff --git a/PHILOSOPHY.md b/PHILOSOPHY.md index c544e5f..36c8aa5 100644 --- a/PHILOSOPHY.md +++ b/PHILOSOPHY.md @@ -109,7 +109,7 @@ The butler (backend) runs the household. The UI opens the door. **Related Documents**: - **README.md**: Project setup and operational details -- **AGENTS.md**: LLM agent development guidelines +- **CLAUDE.md**: LLM agent development guidelines - **PLAN.md**: Implementation roadmap and phases ---