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
+4
View File
@@ -27,6 +27,9 @@ DiagnosticsLevel = Literal["INFO", "DEBUG", "TRACE"]
LEVELS: tuple[str, ...] = get_args(DiagnosticsLevel)
BASE: DiagnosticsLevel = "INFO"
ELEVATION_HOURS = 24
# How long a debug session records at most (E16, app/debugsession.py). Here, beside the other
# consent window, because the start dialog's consent text states it and reads it from `state`.
SESSION_MINUTES = 60
def effective_level(user: User, now: datetime | None = None) -> DiagnosticsLevel:
@@ -47,6 +50,7 @@ def state(db: Session, user: User) -> dict:
"level": level,
"until": user.diagnostics_until.isoformat() if level != BASE else None,
"retention_days": sysconfig.get_int(db, "log_retention_days"),
"session_minutes": SESSION_MINUTES,
}