Browse Source

Fix debug logging banner showing negative timer duration

  The debug logging banner displayed a negative elapsed time (e.g. "-60m -59s")
  equal to the server's UTC offset. datetime.now() stored local time without a
  timezone indicator, but the frontend's parseUTCDate() interpreted it as UTC.

  Use datetime.now(tz=timezone.utc) consistently for storing, parsing, and
  comparing the enabled_at timestamp.
maziggy 2 months ago
parent
commit
63208cf6f1
2 changed files with 7 additions and 4 deletions
  1. 1 0
      CHANGELOG.md
  2. 6 4
      backend/app/api/routes/support.py

+ 1 - 0
CHANGELOG.md

@@ -28,6 +28,7 @@ All notable changes to Bambuddy will be documented in this file.
 - **P2S Shows Carbon Rod Maintenance Tasks** ([#640](https://github.com/maziggy/bambuddy/issues/640)) — The P2S was incorrectly classified as a carbon rod printer, showing "Lubricate Carbon Rods" and "Clean Carbon Rods" maintenance tasks. The P2S uses hardened steel linear shafts, not carbon fiber rods. Added a new `steel_rod` motion system category and "Lubricate Steel Rods" / "Clean Steel Rods" maintenance tasks specific to the P2S. X1/P1 series continue to show carbon rod tasks; A1/H2 series continue to show linear rail tasks. Reported by @maziggy.
 - **Dispatch Toast Stuck After Second Print** — The print dispatch progress toast ("Starting prints…") stayed visible forever after the second print dispatch in a session. The dedup guard (`lastDispatchSummaryRef`) that prevents duplicate completion toasts was never reset between batches, so every single-printer dispatch produced the same summary key (`"first-complete:1:0"`). The first print completed normally, but subsequent completions matched the stale ref and skipped creating the done toast — leaving the progress toast stuck in "Processing" state with no way to dismiss except a page reload. Now resets the dedup guard whenever the dispatch toast is dismissed (auto-dismiss timeout, cleanup events) and when a new batch starts.
 - **Archive Card Buttons Overlapping at Narrow Widths** ([#641](https://github.com/maziggy/bambuddy/issues/641)) — The "Reprint" and "Schedule" buttons at the bottom of archive cards overlapped when the browser window was narrower than the card grid expected (e.g. snapped to half-screen on a 2K monitor). The button text labels used a viewport-based `sm:` breakpoint that didn't account for actual card width. Added `overflow-hidden` to the flex buttons and `truncate` to the text spans so labels clip cleanly with ellipsis instead of bleeding into adjacent buttons. Reported by rsocko@outlook.com, confirmed by @dsmitty166.
+- **Debug Logging Banner Timer Shows Negative Time** — When enabling debug logging, the banner showed a negative duration (e.g. "-60m -59s") equal to the server's UTC offset. The `enabled_at` timestamp was stored using `datetime.now()` (local time, no timezone indicator), but the frontend interpreted it as UTC. Now stores and compares all debug logging timestamps in UTC.
 
 
 ## [0.2.2b2] - 2026-03-06

+ 6 - 4
backend/app/api/routes/support.py

@@ -10,7 +10,7 @@ import os
 import platform
 import re
 import zipfile
-from datetime import datetime
+from datetime import datetime, timezone
 from pathlib import Path
 
 from fastapi import APIRouter, HTTPException, Query
@@ -63,6 +63,8 @@ async def _get_debug_setting(db: AsyncSession) -> tuple[bool, datetime | None]:
     if enabled_at_setting and enabled_at_setting.value:
         try:
             enabled_at = datetime.fromisoformat(enabled_at_setting.value)
+            if enabled_at.tzinfo is None:
+                enabled_at = enabled_at.replace(tzinfo=timezone.utc)
         except ValueError:
             pass  # Ignore malformed timestamp; enabled_at stays None
 
@@ -80,7 +82,7 @@ async def _set_debug_setting(db: AsyncSession, enabled: bool) -> datetime | None
         db.add(Settings(key="debug_logging_enabled", value=str(enabled).lower()))
 
     # Update enabled_at timestamp
-    enabled_at = datetime.now() if enabled else None
+    enabled_at = datetime.now(tz=timezone.utc) if enabled else None
     result = await db.execute(select(Settings).where(Settings.key == "debug_logging_enabled_at"))
     at_setting = result.scalar_one_or_none()
     if at_setting:
@@ -127,7 +129,7 @@ async def get_debug_logging_state(
 
     duration = None
     if enabled and enabled_at:
-        duration = int((datetime.now() - enabled_at).total_seconds())
+        duration = int((datetime.now(tz=timezone.utc) - enabled_at).total_seconds())
 
     return DebugLoggingState(
         enabled=enabled,
@@ -149,7 +151,7 @@ async def toggle_debug_logging(
 
     duration = None
     if toggle.enabled and enabled_at:
-        duration = int((datetime.now() - enabled_at).total_seconds())
+        duration = int((datetime.now(tz=timezone.utc) - enabled_at).total_seconds())
 
     return DebugLoggingState(
         enabled=toggle.enabled,