fix apply-click feedback: create application row synchronously

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) <noreply@anthropic.com>
This commit is contained in:
EiSiMo 2026-04-21 18:39:42 +02:00
parent a212dff4d9
commit f5b4523b77

View file

@ -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,
))