kirche-einfach/PLAN_NEXT.md

263 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Continuation plan — kirche-einfach next steps (written after Phase 0)
> For a future agent restoring this session (e.g. after a container rebuild).
> Read this file + `AGENTS.md` + `README.md` first. All code/comments in
> English; only user-visible website strings are German.
## 0. Where we are
**Phase 0 (this commit) is done** — Docker/devcontainer reproducibility:
* `langflow` image pinned via `${LANGFLOW_VERSION:-1.12.0}` (was `:latest`
with nightly Watchtower drift). `Dockerfile.lfx` pins `lfx~=<same>`
(`ARG LFX_VERSION=1.12.0`). Backend test dep pinned `lfx~=1.12.0`.
Reason: official LFX contract — `major.minor` of Langflow and LFX must
match, patches are independent.
* `langflow` mounts `backend/components` (`LANGFLOW_COMPONENTS_PATH`) and
`backend/flows` (`LANGFLOW_LOAD_FLOWS_PATH`), both `:ro`. Files stay
source of truth; the API watcher (`app/flows/sync.py`) remains the ONLY
sync path — never add a second watcher.
* Backend venv moved to `/opt/venv` (image-local, `UV_PROJECT_ENVIRONMENT`),
managed Python to `/opt/python` (`UV_PYTHON_INSTALL_DIR`), both `a+rX`.
Removed `kirche-venv` + `kirche-node-modules` named volumes (they shadowed
image content with empty volumes on first start = split-brain).
`backend/entrypoint.sh` uses `/opt/venv/bin/*`, ensures `data/` exists and
is `vscode`-owned. `frontend/entrypoint.sh` runs `npm ci` when
`node_modules/.bin/vite` is missing (covers fresh prod checkouts).
* `devcontainer.json`: opencode session persistence via named volumes
`kirche-opencode-share → ~/.local/share/opencode` (sessions DB) and
`kirche-opencode-config → ~/.config/opencode`; interpreter path is now
`/opt/venv/bin/python`. `setup.sh` chowns first, then runs `uv sync` as
`vscode` (was: root `uv sync` into `/root`-backed venv → `Permission
denied` for vscode; `backend/data` root-owned → DB unwritable).
* Stale local `backend/.venv/` (bind-mount leftover) can be deleted manually
after rebuild: `rm -rf backend/.venv`. It is gitignored; nothing references
it anymore.
**Verified in this session:** `uv run pytest` green (after local venv repair,
see §5). NOT verifiable here (no node/docker in this sandbox): `npm run
build`, `docker compose config`, container rebuild. CI covers all three
(`.forgejo/workflows/`). After rebuilding the devcontainer, re-run: `uv run
pytest` (attached api, from `backend/`), `docker compose exec frontend npm
run build`, `docker compose config --quiet`.
**Still open:** nothing from the original list — Phases 1–4 are implemented
(see §0.2). Remaining eyeball checks: `kirche` category in the Langflow
palette, RJSF form UX in a browser, magic-link mail with real SMTP.
## 0.1 Follow-up fixes (commits after Phase 0)
`fix: devcontainer build and setup on fresh clones` + `feat: magic-link
auth` + the LFX/stack repair commit:
* `docker-outside-of-docker` needs `"moby": false` (`python:3.12-slim` is
Debian trixie, no moby-cli in its repos). Removed the manual
`/var/run/docker.sock` bind (breaks Windows, feature handles it).
* `setup.sh` used `su vscode -c` (drops env incl. `UV_PROJECT_ENVIRONMENT`
→ stray `backend/.venv`, blind sync). Now `sudo -u vscode -E` helper.
Missing `.env` is provisioned from `.env.example`, not fatal.
* Compose `env_file` is `required: false` everywhere (fresh clones, CI).
Images install `curl git ca-certificates`; `.dockerignore` files keep
host `node_modules/.venv/data` out of build contexts.
* `settings.database_url` default is now a real `sqlite:///` URL (bare
path crashed `create_engine`/alembic; api could never boot uncconfigured).
* `Dockerfile.lfx` CMD dropped the positional `/flows`: `lfx>=1.12.1`
exits on an empty flow dir (restart-loop → whole stack `unhealthy`).
`lfx` service blanks `LANGFLOW_API_KEY` (server prefers it over
`LFX_API_KEY`, so backend deploys 401'd and `/flows` stayed empty).
* Both entrypoints run via explicit `sh` (git tracks them 100644; the
bind-mount shadows image perms, breaking Windows checkouts).
* Verified live: full `compose up` green (api/frontend/langflow/lfx),
startup sync deploys `backend/flows` to LFX, `uv run pytest` 60 green.
Before Reopen in Container: `docker compose down` any manual stack
(same project name → port clash).
## 0.2 Phases 1–4 (done, with deviations)
* Phase 1 auth as planned (`app/auth/*`, migration `a3f1c9e2b4d7`).
Additionally fixed: `settings.database_url` default is a real
`sqlite:///` URL (bare path crashed `create_engine`/alembic).
* Phase 2: `CustomInputForm` has the 10 planned inputs **plus one hidden
`form_values: MessageTextInput (show=False)` plumbing input**. Without
it, submitted web-form values never reached the run (LFX routes run
input to entry nodes; scalar fields are not connectable). Fixture flow
`intake_form.json`: `ChatInput → form.form_values`,
`form.form_data → ChatOutput`; `collect()` merges the envelope
(`{"payload": {...}}` unwrapped like the other components) over field
defaults. Verified live: run with values returns them in the output.
* Phase 2 generator caveat: lfx 1.12.x `to_frontend_node()` **cannot
re-instantiate `MultiselectInput`** (emits `list`, constructor forbids
extras — their own builtin `SearXNGToolComponent` fails identically).
`kirche_node()` therefore replicates the pre-validation dict build and
appends the `code` field by hand. If a future lfx fixes this, simplify
back to plain `to_frontend_node()`.
* Phase 3 as planned (`input_schema.py`, `FlowVersion.input_schema`,
migration `b7e2d4a1c9f3`, `GET input-schema`). Deviations: schema is
cached in `store.create_version` (single choke point: sync, import, API
all covered); parser skips `show: false` fields; enum-mismatched
defaults omitted; endpoint maps `InvalidError` → 404 too
(`get_latest_deployed_version` raises it, not `NotFoundError`).
* Phase 4 as planned (`react-router-dom@7`, `@rjsf/core` +
`@rjsf/validator-ajv8` (v6), English routes, German labels,
`credentials: include`, old tab shell dropped). `FlowsTab.tsx` deleted;
`ApprovalsTab`/`AdminTab` reused inside Tasks/Admin pages. Verified:
`npm run build` green, routes + `/api` proxy return 200, served schema
validates under JSON-Schema Draft 7.
## 0.3 Frontend design pass (Mantine)
Plain-HTML UI replaced with a coherent Mantine v9 design system:
`@mantine/core` + `@mantine/hooks` + `@rjsf/mantine` (v6, matches core)
+ `@tabler/icons-react`. `src/theme.ts` (kirchenblau primary, system
fonts only, serif display headings), `AppShell` header/nav, per-page
`Card`/`Table`/`Alert`/`Badge` via shared `StatusBadge`, dark mode via
localStorage manager + toggle + inline `ColorSchemeScript` equivalent.
RJSF swap was import-only; SSR smoke test proved all 10 intake labels +
textarea/password/checkbox/slider widgets render. `npm run build` green.
## 0.4 Landing page (conversion structure + screenshot automation)
StartPage rebuilt along the 8-block anatomy (hero with outcome headline,
fact bar, 3 benefits, real product screenshot, 3 steps, trust list, FAQ,
final CTA, footer mini) — one CTA everywhere (`/access`), honest copy
(no fake testimonials/logos/numbers; pricing section omitted, model open).
Centralized hosting in Germany per operator decision (not self-hosted).
`frontend/scripts/capture.mjs` + `npm run screenshots` (Playwright/Chromium
in the frontend container) regenerates `public/screenshots/*.png`; the
workflows shot auto-selects `intake_form` to show the generated RJSF form.
Vite dev `server.watch` polling enabled (Windows bind-mount events often
never reach the Linux watcher → stale modules otherwise).
## 0.5 Email-first login (`POST /api/auth/magic/request`)
`/login` without `?token=` shows an email form (+ "Hier anfragen" link).
Allowed = approved user row or `ADMIN_EMAILS` (admin auto-created on first
request); anything else → 403 and the frontend forwards to
`/access?email=…` (prefilled). Rate-limited (5/hour), token by mail only,
delivery failures never 500 (see §0.4 hardening).
## 0.6 Polish round (bell-tower favicon, lfx-clean flows, HTML mail)
* Favicon simplified to a stylized bell tower (spire + shaft + bell-arch
knockout); PNGs regenerated via `npm run icons`.
* `lfx validate --level 4` now green for ALL canonical flows + regression
test (`test_lfx_validate.py`). Root cause: required form fields have no
edge (values arrive via the form_values envelope). Fix: generator writes
a single-space sentinel value for required template fields; the form
parser treats whitespace-only values as "no default". L3
Message-vs-str warnings are known lfx-validator noise (MessageTextInput
accepts Message at runtime).
* New-input authoring docs: `backend/components/kirche/README.md`.
* Magic-link + access-notification mails are branded multipart
(text + HTML): kirchenblau header, serif headline, real button link,
German copy, email-safe tables/inline styles, no external assets.
## 1. Locked decisions (do not re-ask)
* Auth: SMTP magic-link, 15-min single-use tokens (`secrets.token_urlsafe`,
store `sha256`, `used_at` set before session creation), session = opaque id
in **DB table**, cookie `HttpOnly; Secure; SameSite=Lax; Path=/`.
SMTP creds already in `.env` (`SMTP_HOST/PORT/USERNAME/PASSWORD`,
`MAIL_FROM`, `CONTACT_TO`); dev fallback logs the link when SMTP unset.
* `FileInput` is a **stub** for now (string reference only); real upload later.
* Frontend: `react-router-dom` + RJSF (`@rjsf/core` + `@rjsf/validator-ajv8`).
RJSF renders backend-supplied `{json_schema, ui_schema}`; server
re-validates (client validation is UX only).
* **Routes are pure English** (user correction — no German slugs anywhere,
including URLs):
`/`, `/access` (was once sketched as `/zugang` — do NOT use),
`/login?token=`, `/app/workflows`, `/app/tasks`, `/app/admin`.
Visible labels/nav are German (`Start`, `Zugang`, `Workflows`, `Aufgaben`,
`Verwaltung`); code, props, comments, routes English.
* Langflow preload + mount (done in Phase 0). Custom-component lib in
`backend/components/kirche/` **must stay GUI-editable**: category folder +
`__init__.py` + `Component` subclasses using `lfx.io` inputs only
(stdlib + `lfx`, so the LFX runtime can execute them).
* Watchtower: `langflow` label removed (pinned images must not auto-update).
Version bumps = deliberate `LANGFLOW_VERSION` + `LFX_VERSION` change, then
`lfx upgrade --upgrade-flow=safe` check over `backend/flows/*.json`.
## 2. Phase 1 — Backend auth (magic-link + RBAC)
Files: `backend/app/models/user.py` (`User`, `AccessRequest`,
`MagicLinkToken`, `Session`), `backend/app/auth/{schemas,service,mail,api,
deps}.py`, wire router in `app/main.py`, settings in `app/settings.py`
(`frontend_url, admin_emails, contact_to, mail_from, smtp_*`), ONE new
Alembic revision (never edit applied migrations).
Endpoints:
* `POST /api/access/requests` public, rate-limited: `{name,email,reason}`
→ row `pending` + notify `CONTACT_TO`.
* `POST /api/access/requests/{id}/approve|reject` admin: approve →
upsert `User` + emit magic-link + SMTP send.
* `POST /api/auth/magic/consume {token}` → session cookie; `GET
/api/auth/me`; `POST /api/auth/logout` (delete session row).
* `POST /api/auth/admin-bootstrap`: only when user table empty AND email in
`ADMIN_EMAILS`.
* Tenant from session (`user.tenant_id`); keep old `tenant_id` body param as
deprecated fallback. `require_admin` dependency; keep API handlers thin
(DB only via `app/flows/store.py` + new `app/auth/*` service funcs).
Tests (all in `backend/tests/`): request→approve→consume→me→logout happy
path; token expiry boundary (15 min); token reuse → 400; admin-only → 403;
bootstrap-once; cookie flags (`HttpOnly`, `SameSite`, `Path`); assert no
token/secret in logs.
## 3. Phase 2 — `CustomInput*` library (GUI-editable)
* `backend/components/kirche/__init__.py` +
`backend/components/kirche/form_inputs.py`:
`class CustomInputForm(Component)` with inputs `StrInput`,
`MultilineInput`, `SecretStrInput`, `IntInput`, `FloatInput`, `BoolInput`,
`DropdownInput`, `MultiselectInput`, `SliderInput`, `FileInput` (stub) +
`Output`. Category dir `kirche/` = appears in Langflow GUI.
* Regenerate/add fixture flow using it via
`backend/scripts/make_flow_fixtures.py`; keep `simple_rule.json` as the
textarea-fallback case. Commit flow JSON (source of truth).
## 4. Phase 3 — Dynamic-form parsing (no second watcher)
* New `backend/app/flows/input_schema.py`:
`extract_form_schema(langflow_json) -> {json_schema, ui_schema}`.
Find nodes with `type`/`display_name` starting `CustomInput`; iterate
`template` minus `code`; map `_input_type` → JSON Schema type/widget
(`StrInput`/`MessageTextInput`→string, `MultilineInput`→string+textarea,
`SecretStrInput`→string+password, `IntInput`→integer,
`FloatInput`→number, `BoolInput`→boolean, `DropdownInput`→string+enum,
`MultiselectInput`→array+enum, `SliderInput`→number+range,
`FileInput`→string stub); `display_name/info/value/options/required/
advanced` → `title/description/default/enum/required/uiSchema`.
No such node → `{fallback: "textarea"}`.
* Called from existing `sync_from_directory` (cache per version in new
nullable `input_schema` JSON column via Alembic) + new `GET
/api/flows/{id}/input-schema → {flow_id, version_no, json_schema,
ui_schema}` (latest **deployed** version; 404 when none).
* Tests: per-type mapping table, fallback, sync persistence, endpoint 404.
## 5. Phase 4 — Frontend (router + RJSF, German UI, English routes)
Deps: `react-router-dom`, `@rjsf/core`, `@rjsf/validator-ajv8`.
Routes (English, see §1): `/`, `/access`, `/login`, `/app/workflows`
(flow select → `GET input-schema` → RJSF `<Form>` → `POST run`),
`/app/tasks` (own runs + approval states), `/app/admin` (admin-only:
access-request queue, ALL tasks incl. open human-intervention with
approve/reject + note, import/sync/deploy). `fetch` with
`credentials: "include"`. Refactor `FlowsTab/ApprovalsTab/AdminTab` into
route components; drop old tab shell. Verify with `npm run build`.
## 6. Operational notes for the next agent
* Commands: backend from `backend/`: `uv run pytest`,
`uv run alembic upgrade head`, `uv run python scripts/sync_flows.py
--sync`; frontend via `docker compose exec frontend npm run build`.
* Local venv repair (this sandbox had a root-backed broken `.venv`):
`rm -rf backend/.venv && uv sync` (network needed for `uv python
install`). In rebuilt containers this is unnecessary (`/opt/venv`).
* `.env` (gitignored) still lacks `FRONTEND_URL`/`ADMIN_EMAILS` from the
`.env.example` diff — copy them over locally when starting Phase 1. Never
commit `.env`.
* Flow JSON that fails LFX after a version bump: run
`lfx upgrade --upgrade-flow=safe` on the file, review diff, commit.
* Definition of done per `AGENTS.md`: new behavior has tests, `uv run
pytest` green, `npm run build` green when frontend changed, schema changes
as new Alembic revisions.