Offline listings are a different class of result from real failures:
they mean "this ad is gone", not "the apply pipeline is broken".
- DB migration 0010 adds flats.offline_at; recent_flats() filters those
out globally so they drop off every user's Wohnungen list
- _is_offline_result() matches the four known offline/deactivated
phrases (DE + EN translations)
- On an offline result: mark the flat, reset the failure counter
instead of incrementing, and skip the apply_fail notification
- Bewerbungen history renders a yellow "offline" chip in place of the
red "fehlgeschlagen" one
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1. Bewerben button: hx-disabled-elt + hx-on::before-request flips the
text to "läuft…" and disables the button the moment the confirm is
accepted. .btn[disabled] now renders at 55% opacity with
not-allowed cursor. Existing 3s poll interval picks up the running
state for the chip beside the address.
2. Bewerbungen tab: delete the /bewerbungen/<id> forensics detail page
+ template entirely. The list now shows a plain "Report (ZIP)"
button for every row regardless of success — same download route
(/bewerbungen/<id>/report.zip), same visual style. External link to
the listing moved onto the address itself.
3. Verified retention: web/retention.py runs cleanup_retention() hourly,
which DELETEs errors + audit_log rows older than RETENTION_DAYS (14)
and nulls applications.forensics_json for older rows. No code change
needed.
4. Partner feature. Migration v8 adds partnerships(from_user_id,
to_user_id, status, created_at, accepted_at). Einstellungen →
Partner lets users:
- send a request by username
- accept / decline incoming requests
- withdraw outgoing requests
- unlink the active partnership
A user can only have one accepted partnership; accepting one wipes
stale pending rows involving either side. On the Wohnungen list, if
the partner has applied to a flat, a small primary-colored circle
with the partner's first-name initial sits on the top-right of the
Bewerben button; if they've rejected it, the badge sits on Ablehnen.
Badge is hover-tooltipped with the partner's name + verb.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Bewerbungen chip "Trockenmodus" → "nicht abgeschickt" (list + detail view)
- Profile form: add an off-screen honeypot (username + password) so Chrome's
autofill burns its fill on those instead of the real E-Mail field; switch
the visible E-Mail and Immomio-Email to type=text + inputmode=email so the
browser heuristic no longer tags them as login emails
- Users page: create-form sits on top in its own card (3-column grid with
Administrator checkbox inline); full-width list below with Administrator
chip, aktiv/deaktiviert chip, "du" marker for the current user, plus
disable/activate and a new red "löschen" button (confirm prompt) wired to
new POST /actions/users/delete which cascades through the user's data
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* apply: Recorder.step_snap(page, name) captures both a JPEG screenshot and
the page HTML for every major moment; every provider now calls step_snap at
each logical step so failure reports contain the exact DOM and rendered
state at every stage of the flow
* ZIP report: each snapshot becomes snapshots/NN_<label>.jpg +
snapshots/NN_<label>.html for AI-assisted debugging
* web: Wohnungsliste zeigt nur noch Flats, die die eigenen Filter treffen;
Match-Chip entfernt (Liste ist jetzt implizit matchend)
* UI komplett auf Deutsch: Protokoll statt Logs, Administrator statt admin,
Trockenmodus statt dry-run, Automatik pausiert statt circuit open,
Alarm statt Alert, Abmelden statt Logout
* Wohnungen-Header: Zeile 1 Info (Alarm + Filter), Zeile 2 Schalter mit
echten Radio-Paaren (An/Aus) für Automatisch bewerben und Trockenmodus;
hx-confirm auf den kritischen Radios; per-form CSS für sichtbaren Check-State
* Protokoll: von/bis-Datumsfilter (Berliner Zeit) + CSV-Download
(/logs/export.csv) mit UTC + lokaler Zeit
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Wohnungen header: single slim row with Alert status · Filter summary ·
Auto-Bewerben toggle · Trockenmodus toggle. Big filter panel removed —
filters live only in /einstellungen/filter.
* Alert status: 'nicht eingerichtet' until the user has actual filters (+
valid notification creds if telegram/email). 'aktiv' otherwise.
* Logs tab: admin-only (gated both in layout and server-side). Shows merged
audit + errors across all users, sorted newest-first, capped at 300.
* Apply, auto-apply, trockenmodus and circuit reset buttons post via HTMX and
swap the Wohnungen body. While any application is still running for the
user the poll interval drops from 30s to 3s so status flips to 'beworben'
or 'fehlgeschlagen' almost immediately.
* Browser tab title is now always 'lazyflat'.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* remove the kill-switch: auto-apply toggle is the single on/off; manual
'Bewerben' button now only gated by apply reachability; circuit breaker
stays but only gates auto-apply (manual bypasses, so a user can retry)
* Berlin-timezone date filter (de_dt) formats timestamps as DD.MM.YYYY HH:MM
everywhere; storage stays UTC
* Wohnungen: live 'entdeckt vor X' on every flat + 'nächste Aktualisierung in Xs'
countdown in the header, driven by /static/app.js; HTMX polls body every 30s
* drop the Fehler tab entirely; failed applications now carry a
'Fehler-Report herunterladen (ZIP)' link -> /bewerbungen/{id}/report.zip
bundles application.json, flat.json, profile_snapshot.json, forensics.json,
step_log.txt, page.html, console/errors/network JSONs, and decoded
screenshots/*.jpg for AI-assisted debugging
* trim the 'sensibel' blurb from the Profil tab
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>