From 7728bea8094a08866a43299cc3157d3ceaeb10c7 Mon Sep 17 00:00:00 2001 From: "codexbot.disnetdev.com (did:plc:hbonvqr5ysrscg5wdyb5klie)" Date: Mon, 17 Aug 2026 17:02:47 +0000 Subject: [PATCH] Keep Roomy goal views backward compatible Co-Authored-By: codexbot.disnetdev.com (did:plc:hbonvqr5ysrscg5wdyb5klie) --- .../ui/src/lib/components/RoomyThread.svelte | 13 +++++-- .../lib/components/RoomyThread.svelte.test.ts | 38 +++++++++++++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 packages/ui/src/lib/components/RoomyThread.svelte.test.ts diff --git a/packages/ui/src/lib/components/RoomyThread.svelte b/packages/ui/src/lib/components/RoomyThread.svelte index 33c0c93..8a6b5f0 100644 --- a/packages/ui/src/lib/components/RoomyThread.svelte +++ b/packages/ui/src/lib/components/RoomyThread.svelte @@ -21,10 +21,15 @@ let service = $state('did:web:api.roomy.space') let roomId = $state('') let refresh = $state(0) - const source = $derived(view.threadSources[0]) + // A tab can still hold a goal view materialized by the previous bundle while the new bundle is + // taking over. Treat the additive projections as empty until the next fold instead of letting + // that short-lived, structurally older view take down the whole goal page. + const threadSources = $derived(view.threadSources ?? []) + const importedMessages = $derived(view.importedMessages ?? []) + const source = $derived(threadSources[0]) const me = $derived(account.status === 'signed-in' ? account.did : '') const writable = $derived(!space.fixture && participates(space.directory, me)) - const importedIds = $derived(new Set(view.importedMessages.map((entry) => entry.value.source.messageId))) + const importedIds = $derived(new Set(importedMessages.map((entry) => entry.value.source.messageId))) $effect(() => { refresh @@ -94,10 +99,10 @@ {/if} {/if} -{#if view.importedMessages.length} +{#if importedMessages.length}

Imported into agent context

- {#each view.importedMessages as entry (entry.uri)} + {#each importedMessages as entry (entry.uri)}

{entry.value.body}

Claimed author {entry.value.source.authorDid} ยท imported by {entry.did}
{/each}
diff --git a/packages/ui/src/lib/components/RoomyThread.svelte.test.ts b/packages/ui/src/lib/components/RoomyThread.svelte.test.ts new file mode 100644 index 0000000..9d7bf5f --- /dev/null +++ b/packages/ui/src/lib/components/RoomyThread.svelte.test.ts @@ -0,0 +1,38 @@ +// @vitest-environment jsdom +import type { GoalView } from '@radial/core' +import { flushSync, mount, unmount } from 'svelte' +import { afterEach, describe, expect, it, vi } from 'vitest' +import type { Space } from '$lib/space.js' +import RoomyThread from './RoomyThread.svelte' + +vi.mock('$lib/admin.js', () => ({ participates: () => true })) +vi.mock('$lib/auth.svelte.js', () => ({ account: { status: 'signed-in', did: 'did:plc:member' } })) +vi.mock('$lib/ui.svelte.js', () => ({ toast: () => undefined })) +vi.mock('$lib/write.js', () => ({ write: vi.fn() })) + +let target: HTMLElement | undefined +let component: Record | undefined + +afterEach(() => { + if (component) unmount(component as never) + target?.remove() + component = undefined + target = undefined +}) + +describe('Roomy thread', () => { + it('treats additive projections as empty while an older goal view is still active', () => { + const legacyView = { + target: { uri: 'at://did:plc:member/com.disnetdev.radial.goal/g1' }, + } as GoalView + const space = { fixture: false, directory: new Map() } as unknown as Space + + target = document.createElement('div') + document.body.appendChild(target) + component = mount(RoomyThread, { target, props: { view: legacyView, space } }) as Record + flushSync() + + expect(target.textContent).toContain('Attach thread') + expect(target.textContent).not.toContain('Imported into agent context') + }) +}) -- 2.51.2