Guiding principles and project map for AI coding agents working on WARP. It complements (does not replace) the tracked docs in §2.
WARP — Workspace Autonomous Reservation Program. A hybrid-office desk (and
parking) reservation system: users book/cancel seats on floor maps, admins
manage maps, zones, groups and users. Flask backend (peewee + PostgreSQL),
hand-rolled SPA frontend (native ES modules + webpack), Materialize 2.x CSS,
native <dialog> modals. Installable as a PWA. Auth backends: built-in, LDAP,
Azure AD (MSAL), OIDC, SAML.
warp/ # Flask app package (the backend, Python)
config.py # all settings + WARP_ env-var parsing (single source of truth)
db.py # peewee models / ORM layer
auth*.py # auth backends (built-in, ldap, aad, oidc, saml, mellon)
view.py # server-rendered routes + SPA mount
xhr/ # XHR API endpoints (bookings, plans, zones, users, groups, ...)
sql/ # schema.sql + numbered migration_*.sql + sample_data.sql
static/ # served assets
theme.css # COLOURS ONLY — see Themes/CSS
i18n/ # en/de/fr/es/pl translation JSON
dist/ # webpack output (GITIGNORED — never commit)
sw.js # PWA service worker
templates/ # Jinja templates; headers/ is webpack HTML output (GITIGNORED)
js/ # frontend source (JS, webpack → warp/static/dist/)
base/style.css # structural CSS (spacing, radius, M3 mappings)
app/ # router.js, dialog.js (WarpDialog), modals/, i18n, theme, ...
views/ # one module per admin/booking view
tests/ # pytest suite (pure-Python, no fixtures framework)
e2e/ # Playwright browser suite (see §4)
containers/ # Dockerfile (prod), Dockerfile_debug (e2e), compose/, quadlet/
res/ # demo gif, icons, check_i18n.py, perf/gen scripts
Version-controlled and authoritative — update them when behaviour changes (see §5) and treat them as part of the codebase.
| Doc | Scope |
|---|---|
| README.md | overview, quick start, dev setup, container images |
| FEATURES.md | everything a user/tester/admin can do; basis for the e2e suite |
| CONFIGURATION.md | every warp/config.py setting and WARP_ env var |
| GLOSSARY.md | plain-language definitions (zone, plan, seat, assignment) |
| PERMISSIONS.md | the authoritative access model |
| AUTOBOOK.md | auto-book seat-picking heuristics — only relevant when changing the auto-book logic (warp/xhr/plan.py); no need to read it otherwise |
| e2e/README.md | e2e harness, how to run, test accounts, writing conventions |
| containers/README.md | prod + debug images, compose, Podman Quadlet |
PLAN_*.md and graphify-out/ are gitignored local working documents;
CODE_REVIEW.md is a working review log.
Pure-Python unit tests for non-UI logic: SAML/OIDC metadata & routes, PWA,
group/zone mapping, timezone/time handling, calendar utils. No conftest.py /
fixtures framework — each test_*.py is standalone.
source .venv/bin/activate # python venv (see README dev setup)
pip install -r requirements.txt
pytest # from repo root
pytest tests/test_pwa.py # single fileThe frontend build (cd js && npm ci && npm run build) is only needed to run
the app, not pytest.
External auth providers (LDAP / Azure AD / OIDC / SAML) can't be exercised by
the self-contained e2e container, so their behaviour relies on these unit tests
(test_saml_*, test_oidc_*, …); keep that coverage in step when touching
auth backends.
Browser-driven Playwright suite against the real UI, run in a self-contained
container built from containers/Dockerfile_debug (PostgreSQL + Flask debug)
which the harness builds and starts automatically. Podman is available
(preferred over docker when both exist; auto-detected).
cd e2e
npm ci
npx playwright install chromium
npm test # builds + starts the container automatically
npm run test:headed # watch the browser
npm run test:ui # Playwright UI mode
npx playwright test tests/booking.spec.ts # single file
npm run test:officemap # OfficeMap component suite — backend-free, own config, no container
npm run report # open last HTML reportSee e2e/README.md for the full harness description. Key rules
that catch agents out:
- e2e is e2e: drive the real UI (click, type, navigate). Do not call the XHR/HTTP API directly. The only allowed backchannel is the database for test setup/reset and assertions on persisted state — and even that only when a real UI flow can't set up the precondition (e.g. seeding a specific row).
- Import
test/expectfrom../fixtures, never from@playwright/test(the fixture resets the DB + virtual clock before each test). - After SPA client-side nav,
waitForLoadState('networkidle')is meaningless — usehelpers/spa.ts'swaitForViewReady(page, view?). Directpage.goto()still does a real load. - Suite runs
workers: 1,fullyParallel: falsebecause all tests share one DB. Do not enable parallelism without giving each worker its own database. - The DB reset replays
warp/sql/clean_db.sql+warp/sql/schema.sql+ a frozene2e/sql/sample_data.sql. If you change values inwarp/sql/sample_data.sqlthat e2e asserts on, synce2e/sql/too. - Follow the writing conventions in
e2e/README.md: one feature area per spec file intests/, reusable interactions inhelpers/, test users fromhelpers/users.ts, user-facing locators over CSS. containers/Dockerfile_debugis for demos/e2e only — never deploy it (Werkzeug debugger, hard-coded Postgres password,/debug/*auth bypass, auto-reset state); production runscontainers/Dockerfile(uWSGI, no DB) behind a reverse proxy.
- For non-trivial changes create a
feature/,chore/, orfix/branch. - If the request or plan does not clearly state which to use, ask the user for confirmation before branching — do not assume.
- Clean architecture, no magic numbers, single source of truth — where applicable — but YAGNI first: the first lazy solution that works is the right one. No speculative abstraction, no interface with one implementation, no scaffolding "for later". Deletion over addition; a one-liner stays a one-liner.
- Non-trivial logic leaves behind the smallest runnable check that fails if the
logic breaks (an
assert-based self-check / one small test). Trivial one-liners need no test. warp/config.pyis the single source for configuration. Every setting has aWARP_env-var override; type is inferred from the setting, not the value. Don't read config from ad-hoc env vars or duplicate defaults in JS — the SPA derives the mount prefix etc. fromwindow.warpGlobals.URLsrather than hardcoding paths.- SPA routing is hand-rolled (
js/app/router.js,routes.js). No wildcard patterns —routes.jsis a small explicit registry. A new route gets one entry there; a path that matches nothing renders the client#view-errorview, not a server 500. - Auth backends are pluggable and independent. A change to one of
auth_ldap/auth_aad/auth_oidc/auth_saml/auth_mellonshould not bleed into another.
warp/static/theme.cssis pure colour — the single source of truth for colours and nothing else. Structural CSS lives injs/base/style.css.- Do not introduce new colours or new
--warp-*CSS variables intheme.csswithout explicit permission.
- If the user asks for a plan, a check, or a recommendation, that is literally what they want — analysis/outline, not code changes. You may offer to implement afterwards, but don't jump to it.
- A plan is not implementation: in general it should not contain ready-to-paste code snippets.
- Do not push without a clear order from the user.
- When asked to commit, attribute yourself with a
Co-Authored-By:trailer under your own agent identity (name + email). Examples:- Co-Authored-By: Claude Fable 5 claude@anthropic.com
- Co-Authored-By: Claude Opus 4.8 claude@anthropic.com
- Co-Authored-By: Claude Sonnet 5 claude@anthropic.com
- Co-Authored-By: GLM 5.2 glm-5.2@z.ai
- Co-Authored-By: Grok Build 0.1 noreply@x.ai
- Co-Authored-By: Kimi Code 2.7 noreply@moonshot.ai
- If you are about to implement (or are asked to implement) anything and do
not know your own agent identity, stop and ask the user before proceeding
— never guess, and never reuse another agent's identity for the
Co-Authored-By:trailer. - Never
git addgitignored artifacts:warp/static/dist/,warp/templates/headers/,PLAN_*.md,graphify-out/.
- Run partial tests (relevant specs / pytest files) freely; run full e2e when the change warrants it.
- A full e2e run takes a long time — if unsure whether one is needed, ask the user instead of starting it.
- Before a push, full e2e must have been run; if it hasn't, run it.
- After implementation, update the relevant tracked
.mdfiles — especially when a new configuration (setting/env var) is introduced, update CONFIGURATION.md; new user-facing behaviour updates FEATURES.md. - If e2e tests need updating to not fail (e.g. new endpoints, which the suite detects), update them. New functionality must be covered by e2e.
- If a bug is raised and fixed that was not caught by e2e or pytest, investigate the gap and extend test coverage so it would be caught next time.
- Consistency matters: if something is changed or added, offer to apply the same change to other places that are consistently the same (e.g. a change in one modal dialog should be reflected across all modal dialogs).
- The same applies to bugs: when fixing a bug, check whether the same issue affects other parts of the code and fix/prevent there too.
- All modals go through the unified
WarpDialogcontroller (js/app/dialog.js): button order, dirty-guard behaviour, Esc/outside-click dismissal rules, select dropdown escaping. This unification must be preserved — don't hand-roll modal lifecycle or dismissal in individual views.
- UI strings are client-side and multi-locale: the
TRclass + keys resolved fromwarp/static/i18n/{en,de,fr,es,pl}.json(enis the reference). - New user-facing text must be added to all five locale files, in the same key position in every file — locale files stay consistent in keys and key order so they diff cleanly.
- When text is removed and not used elsewhere, remove its key from all locale files — no stale/unused keys.
res/check_i18n.pyvalidates that every locale defines exactly the same key set asen. Run it after i18n edits:python3 res/check_i18n.py.
- Prefer peewee over raw SQL. Database access goes through peewee
models/queries in
warp/db.pyand thexhr/handlers; reach for rawSQL(...)/execute_sqlonly when peewee genuinely can't express what you need (existing uses are DB-engine primitives likemake_interval/AT TIME ZONEcasts, and the migration runner itself). Don't hand-writeSELECT/INSERT/UPDATEthat peewee could build. - Schema is versioned and migrated in lockstep. The full build is
warp/sql/schema.sql— its final lines write the current version into thedb_initializedtable. The delta path is numberedwarp/sql/migration_NNN_*.sqlfiles registered in theDB_MIGRATIONSlist inwarp/db.py(which runs them in order, up to the tracked version). A schema change must update all three together: add the change toschema.sql, bump itsINSERT INTO db_initializedversion, write a newmigration_NNN_*.sql, and append a matching(NNN, "...")entry inDB_MIGRATIONS. App and e2e share the same SQL scripts, so they can never disagree on structure.
Agents without image input can delegate screenshot / UI analysis to Kimi
K2.7 Code (neuralwatt/kimi-k2.7-code, supports text + image). It runs
as a forked subagent and inherits project context, so it can read both the
images and the code — have it correlate the two, not just caption pixels.
- Do not ask "describe what you see" in isolation. Give Kimi the relevant
file paths / line ranges (or paste the snippet) so it compares the visual
against the markup/logic and reports mismatches — e.g. "the dropdown is open
but the trigger flag never flipped to German, so
setLangUIdid not fire". - Dispatch via the
subagenttool:{ agent: "delegate", model: "neuralwatt/kimi-k2.7-code", task: "<what to check + image file paths toread+ the code references>" }. The subagent'sreadtool attaches images into its vision context, so the task lists the image paths and the questions to answer against them. - For Playwright trace screenshots specifically, the trace's own
frame-snapshotDOM (parse0-trace.trace) and the0-trace.networkHAR are authoritative — staging.jpegscreenshots by filemtimeis unreliable (chronological order is not guaranteed). Verify the frames you hand over actually cover the interaction in question, or parse the DOM/HAR directly instead. - Interpret critically. If Kimi reports "nothing relevant visible", that usually means the wrong frames were staged, not that the app is broken — cross-check against the DOM/network trace before concluding.
Models with vision should just read images directly; this delegation is
only for models that cannot.