feat(diagnostics): the debug recorder — start, the pill, steps, screenshots, stop (E16 S2)

- the start dialog is the consent: level (Részletes/Teljes), what is recorded,
  retention and the cap (now served as diagnostics.session_minutes)
- the pill docks under the toasts: elapsed time, Step (an optional label,
  its own Escape layer), Screenshot (tab capture where the browser has it),
  Stop; compact on a phone
- while recording, lib/log tags every event and every request path (req,
  beacon, uploadReq via one identityHeaders) names the session; applyDiagnostics
  raises the level for the session's run and never lowers a raised one, and
  lapses at whichever ends first
- Stop sends what is waiting before the server closes the session; the cap
  says so and opens the composer
- Settings → Diagnosztika and the command palette start/step/stop it
- the evidence leaves the log upload's own request lines out
- the step shortcut is deliberately palette-only: the keymap's D1 rule bars a
  new modifier chord, and a bare letter collides with the players'
This commit is contained in:
2026-09-19 22:08:10 +02:00
parent 2d9595b0e5
commit 46fce4a767
26 changed files with 1347 additions and 89 deletions
+16 -6
View File
@@ -335,15 +335,16 @@ def test_the_evidence_is_the_sessions_rows_in_time_order(db, make_user, as_user)
sid = _start(client)["id"]
now = datetime.now(timezone.utc)
def row(ts, source, session):
def row(ts, source, session, event="x.y", attrs=None):
return LogEvent(
ts=ts,
level="INFO",
sev=9,
source=source,
logger=source,
event="x.y",
event=event,
msg=source,
attrs=attrs,
user_id=user.id,
debug_session_id=session,
)
@@ -353,15 +354,20 @@ def test_the_evidence_is_the_sessions_rows_in_time_order(db, make_user, as_user)
row(now + timedelta(seconds=2), "api", sid),
row(now + timedelta(seconds=1), "web", sid),
row(now, "web", None),
# The recording's own log upload is plumbing, not evidence…
row(now, "api", sid, "http.request", {"path": debugsession.INGEST_PATH}),
# …but a server line with no event code and no attrs is evidence all the same.
row(now + timedelta(seconds=3), "api", sid, None),
]
)
db.commit()
body = client.get(f"/api/debug-sessions/{sid}/events").json()
assert [(i["source"], i["debug_session_id"]) for i in body["items"]] == [
("web", sid),
("api", sid),
assert [(i["source"], i["event"]) for i in body["items"]] == [
("web", "x.y"),
("api", "x.y"),
("api", None),
]
assert body["total"] == 2
assert body["total"] == 3
# --- screenshots ----------------------------------------------------------------------------
@@ -436,3 +442,7 @@ def test_a_screenshot_can_be_removed_and_is_then_gone(recording):
assert r.json() == {"ok": True, "deleted": shot}
r = client.get(f"/api/debug-sessions/{sid}/screenshots/{shot}")
assert r.status_code == 404 and r.json()["detail"]["reason"] == "screenshot"
def test_the_upload_path_left_out_of_the_evidence_is_the_real_one():
assert debugsession.INGEST_PATH in {r.path for r in diagnostics_routes.router.routes}