feat(diagnostics): 'playback' kind, newest-first sort, fix active-route subtitle
Relabel (#1204): route + player_state events fire for every output route, not just UPnP — split them into a new 'playback' kind; 'upnp_sync' now means genuinely UPnP/Sonos signal (drops, resync). Migration 0037 adds 'playback' to the kind CHECK; server whitelist, Android reporter labels, and the web kind filter updated. Web sort: the diagnostics list gains a Newest/Oldest-first sort (default newest at top); export follows the displayed order. Fix (#1205): OutputRoute.isConnected was derived from RouteInfo.connectionState, which stays DISCONNECTED for local SYSTEM routes even when active — so a connected Bluetooth device showed "Available" and reported connected:false. The picker subtitle now uses isSelected (route == selected route); the dead isConnected field is removed and the misleading `connected` field dropped from the diagnostics route event (it only ever logs the active route). Refs Scribe M9 (#119), tasks #1204 #1205. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K55iTxn95BtshocgdE1shW
This commit is contained in:
@@ -22,11 +22,20 @@
|
||||
|
||||
const client = useQueryClient();
|
||||
|
||||
const KINDS = ['connectivity', 'upnp_sync', 'power', 'lifecycle', 'heartbeat', 'http'] as const;
|
||||
const KINDS = [
|
||||
'connectivity',
|
||||
'playback',
|
||||
'upnp_sync',
|
||||
'power',
|
||||
'lifecycle',
|
||||
'heartbeat',
|
||||
'http'
|
||||
] as const;
|
||||
|
||||
function kindLabel(k: string): string {
|
||||
switch (k) {
|
||||
case 'connectivity': return 'Connectivity';
|
||||
case 'playback': return 'Playback';
|
||||
case 'upnp_sync': return 'UPnP sync';
|
||||
case 'power': return 'Power';
|
||||
case 'lifecycle': return 'Lifecycle';
|
||||
@@ -82,10 +91,17 @@
|
||||
const devicesQuery = $derived($devicesStore);
|
||||
const devices = $derived(devicesQuery.data ?? []);
|
||||
|
||||
// Display sort. The API returns newest-first; default the view to that
|
||||
// (most recent at the top), with an Oldest-first option for reading a
|
||||
// timeline top-to-bottom. Export follows whatever's displayed.
|
||||
let sortOrder = $state<'newest' | 'oldest'>('newest');
|
||||
|
||||
const diagStore = $derived(createAdminDiagnosticsQuery(filter));
|
||||
const diagQuery = $derived($diagStore);
|
||||
// API returns newest-first; reverse to chronological for a readable timeline.
|
||||
const rows = $derived([...((diagQuery.data ?? []) as AdminDiagnostic[])].reverse());
|
||||
const rows = $derived.by(() => {
|
||||
const data = [...((diagQuery.data ?? []) as AdminDiagnostic[])]; // newest-first
|
||||
return sortOrder === 'oldest' ? data.reverse() : data;
|
||||
});
|
||||
|
||||
function fmtTime(iso: string): string {
|
||||
const d = new Date(iso);
|
||||
@@ -232,6 +248,16 @@
|
||||
{/each}
|
||||
</select>
|
||||
</label>
|
||||
<label class="block text-xs text-text-secondary">
|
||||
Sort
|
||||
<select
|
||||
bind:value={sortOrder}
|
||||
class="mt-1 block w-36 rounded border border-border bg-surface px-2 py-1.5 text-sm text-text-primary"
|
||||
>
|
||||
<option value="newest">Newest first</option>
|
||||
<option value="oldest">Oldest first</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<div class="ml-auto flex gap-2">
|
||||
<button
|
||||
@@ -311,9 +337,9 @@
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-xs text-text-muted">
|
||||
{#if hasWindow}
|
||||
{rows.length} events in window (oldest first)
|
||||
{rows.length} events in window ({sortOrder === 'newest' ? 'newest' : 'oldest'} first)
|
||||
{:else}
|
||||
Most recent {rows.length} events (oldest first) · set a time window under Advanced
|
||||
Most recent {rows.length} events ({sortOrder === 'newest' ? 'newest' : 'oldest'} first) · set a time window under Advanced
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user