From f5b4523b771eef15c233bf03a6b0f427e730431b Mon Sep 17 00:00:00 2001 From: EiSiMo Date: Tue, 21 Apr 2026 18:39:42 +0200 Subject: [PATCH] fix apply-click feedback: create application row synchronously MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Bewerben button wasn't flipping to "läuft…" until the next HTMX poll because the DB row with finished_at=NULL was being inserted inside the background thread — which had barely started by the time the route already returned the re-rendered partial. Split the work: _kick_apply now runs db.start_application() on the request thread so the row exists before the response hits the wire. Only the long-running Playwright call + finish_application are offloaded to a worker via asyncio.create_task + to_thread. The new _finish_apply_background re-reads prefs when it lands so circuit- breaker state is current. Co-Authored-By: Claude Opus 4.7 (1M context) --- web/app.py | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/web/app.py b/web/app.py index f278172..6a59651 100644 --- a/web/app.py +++ b/web/app.py @@ -281,21 +281,12 @@ def _has_running_application(user_id: int) -> bool: return row is not None -def _run_apply_background(user_id: int, flat_id: str, url: str, triggered_by: str) -> None: - prefs = db.get_preferences(user_id) - profile_row = db.get_profile(user_id) - profile = _row_to_profile(profile_row) - submit_forms = bool(prefs["submit_forms"]) - - app_id = db.start_application( - user_id=user_id, flat_id=flat_id, url=url, - triggered_by=triggered_by, submit_forms=submit_forms, - profile_snapshot=profile, - ) - - logger.info("apply.start user=%s flat=%s application=%s submit=%s", +def _finish_apply_background(app_id: int, user_id: int, flat_id: str, url: str, + profile: dict, submit_forms: bool) -> None: + """Called on a worker thread AFTER the application row already exists. + The HTMX response has already shipped the running state to the user.""" + logger.info("apply.running user=%s flat=%s application=%s submit=%s", user_id, flat_id, app_id, submit_forms) - result = apply_client.apply(url=url, profile=profile, submit_forms=submit_forms, application_id=app_id) success = bool(result.get("success")) @@ -306,6 +297,7 @@ def _run_apply_background(user_id: int, flat_id: str, url: str, triggered_by: st db.finish_application(app_id, success=success, message=message, provider=provider, forensics=forensics) + prefs = db.get_preferences(user_id) if success: db.update_preferences(user_id, {"apply_recent_failures": 0}) else: @@ -335,8 +327,22 @@ def _run_apply_background(user_id: int, flat_id: str, url: str, triggered_by: st def _kick_apply(user_id: int, flat_id: str, url: str, triggered_by: str) -> None: + """Insert the application row synchronously so the immediate HTMX response + already renders the row's "läuft…" state. The long-running Playwright call + is then offloaded to a background thread.""" + prefs = db.get_preferences(user_id) + profile_row = db.get_profile(user_id) + profile = _row_to_profile(profile_row) + submit_forms = bool(prefs["submit_forms"]) + + app_id = db.start_application( + user_id=user_id, flat_id=flat_id, url=url, + triggered_by=triggered_by, submit_forms=submit_forms, + profile_snapshot=profile, + ) + asyncio.create_task(asyncio.to_thread( - _run_apply_background, user_id, flat_id, url, triggered_by, + _finish_apply_background, app_id, user_id, flat_id, url, profile, submit_forms, ))