From 2372827762ade2e6b7c22f34133b44a89dc746fc Mon Sep 17 00:00:00 2001 From: matevip Date: Fri, 21 Aug 2026 22:32:48 -0400 Subject: [PATCH] fix(wiki): open failure records in owning workspace (#614) --- .../views/Wiki/__tests__/failureOpen.test.ts | 44 +++++++++++++++++++ .../Wiki/components/WikiFailureCenter.vue | 6 +-- mateclaw-ui/src/views/Wiki/index.vue | 11 ++--- .../src/views/Wiki/utils/failureOpen.ts | 27 ++++++++++++ 4 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 mateclaw-ui/src/views/Wiki/__tests__/failureOpen.test.ts create mode 100644 mateclaw-ui/src/views/Wiki/utils/failureOpen.ts diff --git a/mateclaw-ui/src/views/Wiki/__tests__/failureOpen.test.ts b/mateclaw-ui/src/views/Wiki/__tests__/failureOpen.test.ts new file mode 100644 index 00000000..5fc9a01d --- /dev/null +++ b/mateclaw-ui/src/views/Wiki/__tests__/failureOpen.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it, vi } from 'vitest' +import { openWikiFailureItem } from '../utils/failureOpen' + +describe('openWikiFailureItem', () => { + it('switches to the owning workspace before opening a cross-workspace KB', async () => { + const calls: string[] = [] + const workspaceStore = { + currentWorkspaceId: 'ws-a', + switchWorkspace: vi.fn(async (id: string) => { + calls.push(`switch:${id}`) + }), + } + const wikiStore = { + selectKB: vi.fn(async (id: string, mode: 'browse' | 'manage') => { + calls.push(`select:${id}:${mode}`) + }), + } + + await openWikiFailureItem( + { kbId: 'kb-2', workspaceId: 'ws-b' }, + { workspaceStore, wikiStore }, + ) + + expect(calls).toEqual(['switch:ws-b', 'select:kb-2:browse']) + }) + + it('opens directly when the failure item belongs to the active workspace', async () => { + const workspaceStore = { + currentWorkspaceId: 'ws-a', + switchWorkspace: vi.fn(), + } + const wikiStore = { + selectKB: vi.fn(async () => {}), + } + + await openWikiFailureItem( + { kbId: 'kb-1', workspaceId: 'ws-a' }, + { workspaceStore, wikiStore }, + ) + + expect(workspaceStore.switchWorkspace).not.toHaveBeenCalled() + expect(wikiStore.selectKB).toHaveBeenCalledWith('kb-1', 'browse') + }) +}) diff --git a/mateclaw-ui/src/views/Wiki/components/WikiFailureCenter.vue b/mateclaw-ui/src/views/Wiki/components/WikiFailureCenter.vue index a5dded70..35ac860a 100644 --- a/mateclaw-ui/src/views/Wiki/components/WikiFailureCenter.vue +++ b/mateclaw-ui/src/views/Wiki/components/WikiFailureCenter.vue @@ -16,7 +16,7 @@
-
+
{{ t(`wiki.status.${it.processingStatus}`) }}
@@ -25,7 +25,7 @@
{{ friendly(it) }}
- +
@@ -36,7 +36,7 @@ import { ref, onMounted } from 'vue' import { useI18n } from 'vue-i18n' import { wikiApi, type WikiFailureItem } from '@/api/index' -defineEmits<{ (e: 'open', kbId: string): void }>() +defineEmits<{ (e: 'open', item: WikiFailureItem): void }>() const { t } = useI18n() const items = ref([]) diff --git a/mateclaw-ui/src/views/Wiki/index.vue b/mateclaw-ui/src/views/Wiki/index.vue index 190258e0..4ee755e7 100644 --- a/mateclaw-ui/src/views/Wiki/index.vue +++ b/mateclaw-ui/src/views/Wiki/index.vue @@ -48,18 +48,21 @@ import { ref, reactive, watch, onMounted, computed } from 'vue' import { useI18n } from 'vue-i18n' import { useRoute, useRouter } from 'vue-router' import { useWikiStore, type WikiKB } from '@/stores/useWikiStore' -import { wikiApi } from '@/api/index' +import { useWorkspaceStore } from '@/stores/useWorkspaceStore' +import { wikiApi, type WikiFailureItem } from '@/api/index' import { mcConfirm } from '@/components/common/useConfirm' import { mcToast } from '@/composables/useMcToast' import WikiLibrary from './components/WikiLibrary.vue' import WikiWorkspace from './components/WikiWorkspace.vue' import WikiFailureCenter from './components/WikiFailureCenter.vue' +import { openWikiFailureItem } from './utils/failureOpen' const route = useRoute() const router = useRouter() const { t } = useI18n() const store = useWikiStore() +const workspaceStore = useWorkspaceStore() // The cross-KB failure center spans every workspace, so it is admin-only — // mirrors the gate on the backing endpoint. @@ -95,10 +98,8 @@ async function enterKB(id: number) { await store.selectKB(id, 'browse') } -// The failure center emits a Snowflake kbId as a string — keep it a string end -// to end (snowflake-precision-ok) and let the store cast satisfy its signature. -async function openFromFailureCenter(kbId: string) { - await store.selectKB(kbId as unknown as number, 'browse') +async function openFromFailureCenter(item: WikiFailureItem) { + await openWikiFailureItem(item, { workspaceStore, wikiStore: store }) } async function enterKBManage(id: number) { diff --git a/mateclaw-ui/src/views/Wiki/utils/failureOpen.ts b/mateclaw-ui/src/views/Wiki/utils/failureOpen.ts new file mode 100644 index 00000000..dee0a9f8 --- /dev/null +++ b/mateclaw-ui/src/views/Wiki/utils/failureOpen.ts @@ -0,0 +1,27 @@ +export interface WikiFailureOpenItem { + kbId: string + workspaceId?: string | null +} + +export interface WikiFailureOpenDeps { + workspaceStore: { + currentWorkspaceId: string | null + switchWorkspace: (id: string) => Promise | void + } + wikiStore: { + // The wiki store is still typed as number in places, but Snowflake KB IDs + // are passed as strings at runtime to avoid precision loss. + selectKB: (id: any, mode: 'browse' | 'manage') => Promise | void + } +} + +export async function openWikiFailureItem( + item: WikiFailureOpenItem, + deps: WikiFailureOpenDeps, +) { + const targetWorkspaceId = item.workspaceId || null + if (targetWorkspaceId && deps.workspaceStore.currentWorkspaceId !== targetWorkspaceId) { + await deps.workspaceStore.switchWorkspace(targetWorkspaceId) + } + await deps.wikiStore.selectKB(item.kbId, 'browse') +}