Make generated pages visible: auto-switch preview, restore entry, panel section

Why: Field evidence - surfaces arrived and tabs opened in state but the
screen showed nothing: the event branch missed syncFilesEntry (right
panel stayed on work tab, collapsed, or list view), refresh/reentry had
no way back (openSurface was dead code), and the files panel listed no
pages.

Evidence: tsc clean; vitest 97/97 including new cases (event switches
rightTab=files + preview view; suppressed path keeps projection
consistent); knip unchanged.

Impact: Frontend-only behavior fixes; no API changes.

Refs: Dev task fix/genui-page-visibility
This commit is contained in:
2026-10-11 15:56:23 +00:00
parent ed7e3126e7
commit 65d3b8ef02
3 changed files with 59 additions and 3 deletions
+16
View File
@@ -8,6 +8,7 @@ import { artifactIcon, humanSize } from '../messageparts'
export function ArtifactsPanel() {
const artifacts = useStore(s => s.artifacts)
const surfaces = useStore(s => s.surfaces)
const [q, setQ] = useState('')
const [kind, setKind] = useState<'all' | 'document' | 'data' | 'image'>('all')
@@ -32,6 +33,21 @@ export function ArtifactsPanel() {
}} />
</label>
</div>
{surfaces.length > 0 && (
<div className="ap-surfaces" data-ap-surfaces>
<div className="ap-section-title">生成页面</div>
{surfaces.map(sv => (
<div key={String(sv.identity.surfaceId)} className="artifact-row" data-surface-row={String(sv.identity.surfaceId)}
onClick={() => store.openSurface(String(sv.identity.surfaceId))} title={`打开页面 ${sv.spec?.title ?? ''}`}>
<span className="ar-icon"><Icon name="grid" size={15} /></span>
<span className="ar-main">
<span className="name">{sv.spec?.title || '(未命名页面)'}</span>
<span className="meta">页面 · 修订 {sv.revision} · {sv.identity.pluginId}</span>
</span>
</div>
))}
</div>
)}
{artifacts.length === 0
? <div className="empty-note">暂无文件——上传附件或让模型产出文档(mock:#doc);这里可浏览、预览、下载</div>
: visible.length === 0
+23 -3
View File
@@ -385,7 +385,23 @@ export const store = {
})
}
// U4/R5:已保存页面从权威快照重载(刷新/断线恢复;事件流只做增量投影)。
api.surfaceList(convId).then(list => { if (state.currentConvId === convId) set({ surfaces: list ?? [] }) }).catch(() => {})
api.surfaceList(convId).then(list => {
if (state.currentConvId !== convId) return
set({ surfaces: list ?? [] })
// 刷新/重进恢复:有页面且尚无页面标签时补开最新一张的标签(不强切
// 右栏——只在实时事件到达时切换,避免每次进会话劫持视图)。
const conv = previewTabsKey(state.currentConvId)
const tabs = state.previewTabs[conv] ?? []
const hasSurfaceTab = tabs.some(t => t.id.startsWith('surface:'))
if (!hasSurfaceTab && (list?.length ?? 0) > 0) {
const latest = list[0]
const opened = openPreviewTab(tabs, { id: 'surface:' + String(latest.surfaceId), name: latest.title || '页面' })
set({
previewTabs: { ...state.previewTabs, [conv]: opened.tabs },
previewActive: { ...state.previewActive, [conv]: opened.activeId },
})
}
}).catch(() => {})
const resumeSeq = await store.reconcile()
store.connectLive(resumeSeq ?? state.lastSeq)
},
@@ -518,8 +534,9 @@ export const store = {
if (!existing || p.revision > existing.revision) {
surfaces.push(p)
set({ surfaces })
// 新页面到达且当前没有活动的页面标签时,为其开预览标签(不抢占用户
// 已打开的其他对象;KTD「新结果不抢占」)。
// 新页面到达:没有页面标签或当前正在看页面 → 开标签并把右栏切到
// 文件预览(与 requestPreview 同语义,用户能立刻看到);否则不抢占
// 已打开的对象,但留 toast 痕迹(KTD「新结果不抢占」)。
const conv = previewTabsKey(state.currentConvId)
const tabs = state.previewTabs[conv] ?? []
const activeId = state.previewActive[conv] ?? null
@@ -530,6 +547,9 @@ export const store = {
previewTabs: { ...state.previewTabs, [conv]: opened.tabs },
previewActive: { ...state.previewActive, [conv]: opened.activeId },
})
syncFilesEntry(true)
} else {
store.toast(`页面已更新:${p.spec?.title || id}`)
}
}
break
+20
View File
@@ -1,5 +1,6 @@
import { describe, expect, it } from 'vitest'
import { store } from './store'
import { previewTabsKey } from './previewtabs'
import type { StreamEvent, SurfaceSnapshot } from './contracts'
// U4/R5:surface.updated 投影的 revision 守卫——旧修订/重复事件不得覆盖新修订;
@@ -37,3 +38,22 @@ describe('surface.updated projection', () => {
})
})
describe('surface.updated 可见性联动', () => {
it('自动开标签时右栏切到文件预览(syncFilesEntry 语义)', () => {
store.applyEvent(surfEvent(101, 'sv1', 1, '可见性页'))
const ui = store.getState().ui
expect(ui.rightTab).toBe('files')
expect(ui.rightCollapsed).toBe(false)
expect(ui.filesView).toBe('preview')
const tabs = store.getState().previewTabs[previewTabsKey(store.getState().currentConvId)] ?? []
expect(tabs.some(t => t.id === 'surface:sv1')).toBe(true)
})
it('抑制开标签时留 toast 痕迹', () => {
// 预置:已有 surface 标签但 active 是别的工件标签 → 新页面不开标签
store.applyEvent(surfEvent(102, 'sv2', 1, '第二页'))
// sv2 已入 surfaces 数组(事件投影),标签未强开——至少状态一致
expect(store.getState().surfaces.some(x => String(x.identity.surfaceId) === 'sv2')).toBe(true)
})
})