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:
parent
5e1fd62ed9
commit
f0850f0785
22 changed files with 1389 additions and 25 deletions
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue