feat: @agor/stores package (3 stores) + 58 BackendAdapter tests

@agor/stores:
- theme.svelte.ts, notifications.svelte.ts, health.svelte.ts extracted
- Original files replaced with re-exports (zero consumer changes needed)
- pnpm workspace + Vite/tsconfig aliases configured

BackendAdapter tests (58 new):
- backend-adapter.test.ts: 9 tests (lifecycle, singleton, testing seam)
- tauri-adapter.test.ts: 28 tests (invoke mapping, command names, params)
- electrobun-adapter.test.ts: 21 tests (RPC names, capabilities, stubs)

Total: 523 tests passing (was 465, +58)
This commit is contained in:
Hibryda 2026-03-22 04:45:56 +01:00
parent 5e1fd62ed9
commit f0850f0785
22 changed files with 1389 additions and 25 deletions

View file

@ -64,6 +64,9 @@
let dmMessages = $state<DM[]>([]);
let input = $state('');
let loading = $state(false);
// Feature 7: Channel member list
let channelMembers = $state<Array<{ agentId: string; name: string; role: string }>>([]);
let showMembers = $state(false);
// ── Data fetching ────────────────────────────────────────────────────
@ -119,7 +122,19 @@
function selectChannel(id: string) {
activeChannelId = id;
showMembers = false;
loadChannelMessages(id);
loadChannelMembers(id);
}
// Feature 7: Load channel members
async function loadChannelMembers(channelId: string) {
try {
const res = await appRpc.request['btmsg.getChannelMembers']({ channelId });
channelMembers = res.members;
} catch (err) {
console.error('[CommsTab] loadChannelMembers:', err);
}
}
function selectDm(otherId: string) {
@ -156,22 +171,38 @@
}
}
// ── Init + polling ───────────────────────────────────────────────────
// ── Init + event-driven updates (Feature 4) ─────────────────────────
let pollTimer: ReturnType<typeof setInterval> | null = null;
// Feature 4: Listen for push events
function onNewMessage(payload: { groupId: string; channelId?: string }) {
if (mode === 'channels' && activeChannelId) {
if (!payload.channelId || payload.channelId === activeChannelId) {
loadChannelMessages(activeChannelId);
}
} else if (mode === 'dms' && activeDmAgentId) {
loadDmMessages(activeDmAgentId);
}
}
$effect(() => {
loadChannels();
loadAgents();
appRpc.addMessageListener('btmsg.newMessage', onNewMessage);
// Feature 4: Fallback 30s poll for missed events
pollTimer = setInterval(() => {
if (mode === 'channels' && activeChannelId) {
loadChannelMessages(activeChannelId);
} else if (mode === 'dms' && activeDmAgentId) {
loadDmMessages(activeDmAgentId);
}
}, 5000);
}, 30000);
return () => { if (pollTimer) clearInterval(pollTimer); };
return () => {
if (pollTimer) clearInterval(pollTimer);
appRpc.removeMessageListener?.('btmsg.newMessage', onNewMessage);
};
});
</script>
@ -264,6 +295,20 @@
</div>
{/if}
<!-- Feature 7: Channel member list toggle -->
{#if mode === 'channels' && activeChannelId}
<button class="members-toggle" onclick={() => showMembers = !showMembers}>
Members ({channelMembers.length})
</button>
{#if showMembers}
<div class="members-list">
{#each channelMembers as m}
<span class="member-chip">{m.name} <span class="member-role">{m.role}</span></span>
{/each}
</div>
{/if}
{/if}
<!-- Input bar -->
<div class="msg-input-bar">
<input
@ -518,4 +563,42 @@
opacity: 0.4;
cursor: not-allowed;
}
/* Feature 7: Member list */
.members-toggle {
background: var(--ctp-surface0);
border: none;
border-top: 1px solid var(--ctp-surface0);
padding: 0.25rem 0.5rem;
font-size: 0.625rem;
color: var(--ctp-overlay1);
cursor: pointer;
text-align: left;
font-family: var(--ui-font-family);
}
.members-toggle:hover { color: var(--ctp-text); }
.members-list {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
padding: 0.25rem 0.5rem;
background: var(--ctp-surface0);
border-top: 1px solid var(--ctp-surface0);
}
.member-chip {
font-size: 0.625rem;
color: var(--ctp-text);
background: var(--ctp-mantle);
padding: 0.125rem 0.375rem;
border-radius: 0.25rem;
border: 1px solid var(--ctp-surface1);
}
.member-role {
color: var(--ctp-overlay0);
font-size: 0.5625rem;
}
</style>