- sqm_price now renders with 2 decimals + German comma separator
(e.g. "(12,35 €/m²)" instead of "(12 €/m²)").
- Map link covers BOTH address lines, not just the street. Telegram
Markdown can't span link text across newlines, so each line gets its
own [text](gmaps_url) — same URL, both lines blue + clickable, layout
stays two-line.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- notifications: round sqm_price to whole € in Telegram match messages
(was emitting raw float like "12.345614 €/m²").
- wohnungen: remove the admin-only "Bilder nachladen (N)" button. It
flickered into view whenever a freshly-scraped flat was still in
pending state, which was effectively random from the user's point of
view, and the manual backfill it triggered isn't needed anymore — new
flats are auto-enriched at scrape time. Also drops the dead helpers
it was the sole caller of: enrichment.kick_backfill,
enrichment._backfill_runner, db.flats_needing_enrichment,
db.enrichment_counts.
- lightbox: the modal didn't appear because Tailwind's Play CDN injects
its own .hidden { display: none } rule at runtime, which kept fighting
our class toggle. Switch the show/hide to inline style.display so no
external stylesheet can mask it. Single-class .lightbox now only owns
the layout — the initial-hidden state is on the element via
style="display:none".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New Telegram match layout:
Karl-Ziegler-Straße 7 (linked → Google Maps)
12489 Treptow-Köpenick
Miete: 944.12 (18.51 €/m²)
Fläche: 51.0
Zimmer: 2.0
WBS: nicht erforderlich
Zur original Anzeige (→ flat URL)
Zur lazyflat Seite (→ /?flat=<id>)
Deep-link behavior on lazyflat: ?flat=<id> expands the matching row,
scrolls it into view, and pulses a yellow highlight for 3s. The query
param is stripped from history afterwards so reload stays clean.
Unknown flat IDs drop the param silently.
Helpers: _address_lines splits the scraper's "Street, PLZ, District"
into two display lines; _gmaps_url falls back to a maps.google query
when the payload has no explicit link; _wbs_label normalises the
German WBS variants to "erforderlich" / "nicht erforderlich".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New "Test senden" button next to Speichern posts current form
credentials (not DB) to /actions/notifications/test, which fires a
test message and redirects back with a flash chip showing the outcome
(including the Telegram API's error description on failure).
telegram_send is now public and returns (ok, detail) so the UI can
surface real error messages ("chat not found", "Unauthorized", etc.).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Container names, FastAPI titles, email subjects, filenames, brand text,
session cookie, User-Agent, docstrings, README. Volume lazyflat_data and
/data/lazyflat.sqlite already used the new name, so on-disk data is
preserved; dropped the now-obsolete legacy-rename comments.
Side effect: SESSION_COOKIE_NAME change logs everyone out on deploy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per review §2:
- web/db.py: new _tx() context manager wraps multi-statement writers in
BEGIN IMMEDIATE … COMMIT/ROLLBACK (our connections run in autocommit
mode, so plain `with _lock:` doesn't give atomicity). partnership_accept
(UPDATE + DELETE) and cleanup_retention (3 deletes/updates) now use it.
- Fire-and-forget tasks: add module-level _bg_tasks sets in web/app.py and
web/enrichment.py. A _spawn() helper holds a strong ref until the task
finishes so the GC can't drop it mid-flight (CPython's event loop only
weakly references pending tasks).
- apply/main.py: require_api_key uses hmac.compare_digest, matching web's
check. Also imports now use explicit names instead of `from settings *`.
- apply/language.py: replace `from settings import *` + `from paths import *`
with explicit imports — this is the pattern that caused the LANGUAGE
NameError earlier.
- alert/utils.py: pickle-based hash_any_object → deterministic JSON+sha256.
Cheaper, portable across Python versions, no pickle attack surface.
- web/notifications.py: /fehler links repointed to /bewerbungen (the
former page doesn't exist).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- App is now called "wohnungsdidi" everywhere user-facing (page title,
nav brand, login header, notification subjects, report filename,
FastAPI titles, log messages)
- Brand dot replaced with an image of Didi (web/static/didi.webp),
rendered as a round 2.25rem avatar in _layout + login
- "Programmiert für Annika ♥" footer now shows for every logged-in user,
not only Annika
- Count-up shows only seconds ("vor 73 s") regardless of age — no
rollover to minutes/hours
- Data continuity: DB file stays /data/lazyflat.sqlite and the Docker
volume stays lazyflat_data so the rename doesn't strand existing data
- Session cookie renamed to wohnungsdidi_session (one-time logout on
rollout)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Surface "X/Y passende Wohnungen mit Koordinaten" on the Karte view +
admin-only "Koordinaten nachladen" button (POST /actions/backfill-coords)
that geocodes missing flats via Google Maps directly from the web container
- Add googlemaps dep + GMAPS_API_KEY env to web service
- Light console.log in map.js ("rendering N/M markers", "building Leaflet…")
so the browser DevTools shows what's happening
- Drop e-mail channel from notifications UI, notify dispatcher, and _alert_status;
coerce legacy 'email' channel rows back to 'ui' on save
- Countdown said "Aktualisierung läuft…" next to "nächste Aktualisierung" →
shortened to "aktualisiere…"
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* DB: users + user_profiles/filters/notifications/preferences; applications gets
user_id + forensics_json + profile_snapshot_json; new errors table
with 14d retention; schema versioning via MIGRATIONS list
* auth: password hashes in DB (argon2); env vars seed first admin; per-user
sessions; CSRF bound to user id
* apply: personal info/WBS moved out of env into the request body; providers
take an ApplyContext with Profile + submit_forms; full Playwright recorder
(step log, console, page errors, network, screenshots, final HTML)
* web: five top-level tabs (Wohnungen/Bewerbungen/Logs/Fehler/Einstellungen);
settings sub-tabs profil/filter/benachrichtigungen/account/benutzer;
per-user matching, auto-apply and notifications (UI/Telegram/SMTP); red
auto-apply switch on Wohnungen tab; forensics detail view for bewerbungen
and fehler; retention background thread
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>