- WifiPanel: revert useEffect back to useState initializer (avoids
synchronous setState in effect)
- Device detail: avoid Date.now() during render for push alert check
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add SnapshotResponse interface and getSnapshot API method
- Add deviceName prop to ConfigHistorySection
- Add download handler that fetches snapshot and triggers .rsc file download
- Add Download icon button on each timeline entry with stopPropagation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add click handlers to timeline entries to open diff viewer
- Render DiffViewer inline above timeline when snapshot selected
- Add hover state and cursor-pointer to timeline entries
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add DiffResponse interface and getDiff method to configHistoryApi
- Create DiffViewer component with unified diff rendering
- Green highlighting for added lines, red for removed lines
- Blue styling for hunk headers, loading skeleton, error state
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add ConfigChangeEntry interface and configHistoryApi.list() to api.ts
- Create ConfigHistorySection with timeline, loading skeleton, and empty state
- Poll every 60s via TanStack Query refetchInterval
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The target input showed "8.8.8.8" as placeholder text but the actual
value was empty. Clicking Ping/Traceroute silently returned because
the empty target guard fired. Users saw the placeholder and assumed
the tool was broken.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>