diff --git a/muse-studio/src/features/editor/components/PlanningEditorPanel.test.tsx b/muse-studio/src/features/editor/components/PlanningEditorPanel.test.tsx
new file mode 100644
index 00000000..5b658a9e
--- /dev/null
+++ b/muse-studio/src/features/editor/components/PlanningEditorPanel.test.tsx
@@ -0,0 +1,36 @@
+import { describe, it, expect, beforeEach } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { http, HttpResponse } from 'msw';
+import { server } from '@/test/setup';
+import PlanningEditorPanel from './PlanningEditorPanel';
+import { resetMockDb } from '@/api/mocks/handlers/content';
+import React from 'react';
+
+const renderWithClient = (ui: React.ReactElement) => {
+ const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: 0 } } });
+ return render({ui});
+};
+
+describe('PlanningEditorPanel 组件', () => {
+ beforeEach(() => resetMockDb());
+
+ it('应列出 projection 并默认渲染第一个的字段', async () => {
+ renderWithClient();
+ // projection 选择条出现「故事设定」。
+ expect(await screen.findByRole('button', { name: '故事设定' })).toBeInTheDocument();
+ // 默认选中第一个 → 内嵌 PlanningEditor 渲染其字段。
+ expect(await screen.findByLabelText('时代背景')).toBeInTheDocument();
+ });
+
+ it('meta-projection unavailable 时优雅降级(不崩)', async () => {
+ // 模拟真后端 Meta owner 未接入:meta-projections 返回 unavailable(code 非 0 → api client 抛错)。
+ server.use(
+ http.get('/app-api/muse/works/:workId/meta-projections', () =>
+ HttpResponse.json({ code: 500, msg: 'external owner unavailable', data: null }),
+ ),
+ );
+ renderWithClient();
+ expect(await screen.findByText('暂无可编辑的规划投影')).toBeInTheDocument();
+ });
+});
diff --git a/muse-studio/src/features/editor/components/PlanningEditorPanel.tsx b/muse-studio/src/features/editor/components/PlanningEditorPanel.tsx
new file mode 100644
index 00000000..f27116cc
--- /dev/null
+++ b/muse-studio/src/features/editor/components/PlanningEditorPanel.tsx
@@ -0,0 +1,63 @@
+import { useState } from 'react';
+import { useMetaProjections } from '../hooks/usePlanning';
+import PlanningEditor from './PlanningEditor';
+
+interface PlanningEditorPanelProps {
+ /** 当前作品 ID */
+ workId: string;
+}
+
+/**
+ * 作品规划面板:列出 work 绑定 schema 的所有 meta-projection,选中后按字段编辑。
+ *
+ *
projection 由 Meta owner 投影(GET /works/{workId}/meta-projections)。真后端若 Meta owner
+ * 未接入投影实现,端口返回 unavailable → 此处优雅降级为空态提示,不崩溃(anti-false-green:
+ * 不假装有字段可填)。投影就绪后即可按 schema 字段填值并落字段用量快照。
+ */
+export default function PlanningEditorPanel({ workId }: PlanningEditorPanelProps) {
+ const { data: projections, isLoading, isError } = useMetaProjections(workId);
+ const [selectedKey, setSelectedKey] = useState(null);
+
+ if (isLoading) {
+ return 加载规划投影…
;
+ }
+
+ // meta-projection 端口在真后端可能 unavailable(Meta owner 未接入)或作品未绑 schema → 优雅降级。
+ if (isError || !projections || projections.length === 0) {
+ return (
+
+
暂无可编辑的规划投影
+
该作品未绑定已激活的 Meta Schema,或投影服务尚未就绪。
+
+ );
+ }
+
+ // 当前选中 projection:用户未选时默认第一个(渲染期计算,无 effect setState)。
+ const activeKey = selectedKey ?? projections[0]?.projectionKey ?? null;
+
+ return (
+
+ {/* projection 选择条 */}
+
+ {projections.map((p) => (
+
+ ))}
+
+ {/* 选中 projection 的字段编辑器(key=activeKey:切换时重挂载清本地编辑) */}
+
+
+ );
+}
diff --git a/muse-studio/src/pages/WorkspacePage.tsx b/muse-studio/src/pages/WorkspacePage.tsx
index da43c0f7..ad8e163a 100644
--- a/muse-studio/src/pages/WorkspacePage.tsx
+++ b/muse-studio/src/pages/WorkspacePage.tsx
@@ -11,6 +11,7 @@ import MuseEditor from '../features/editor/components/MuseEditor';
import type { MuseEditorInstance } from '../features/editor/components/MuseEditor';
import AIPanel from '../features/editor/components/AIPanel';
import CandidatePanel from '../features/editor/components/CandidatePanel';
+import PlanningEditorPanel from '../features/editor/components/PlanningEditorPanel';
import type { WorkVO } from '@/types/openapi';
/**
@@ -55,6 +56,8 @@ export default function WorkspacePage() {
const [candidateSuggestionId, setCandidateSuggestionId] = useState(null);
const [originalText, setOriginalText] = useState('');
const [showCompare, setShowCompare] = useState(false);
+ // 右侧面板 Tab:AI 创作助手 / 作品规划(planning 为 work 级,不依赖 activeBlock,故面板始终挂载)。
+ const [rightTab, setRightTab] = useState<'ai' | 'planning'>('ai');
// 本地 revision 覆盖:采纳成功后后端返回 newRevision,先用它即时校准本地乐观锁,避免连续采纳因 revision 滞后误触 409。
const [revisionOverride, setRevisionOverride] = useState(null);
@@ -235,22 +238,50 @@ export default function WorkspacePage() {
)}
- {/* 右侧 AI 助手 / 候选对比面板:仅在已激活 Block 时挂载(AI 生成与采纳都需要目标 Block) */}
- {activeBlock && (
-
- {showCompare ? (
-
+ {/* 右侧面板:Tab 切换「AI 创作助手」(需激活 Block) 与「作品规划」(work 级,始终可用)。 */}
+
+ {/* Tab 导航栏 */}
+
+
+
+
+ {/* Tab 内容区:planning 始终可用;AI 须有激活 Block(生成与采纳都需要目标 Block)。 */}
+
+ {rightTab === 'planning' ? (
+
+ ) : activeBlock ? (
+ showCompare ? (
+
+ ) : (
+
+ )
) : (
-
+
请在左侧大纲选择章节,以使用 AI 创作助手。
)}
- )}
+
);