评审后续:修正标题栏项目标签的空态、视图门控与文案
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m7s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 5m38s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m42s
Project CI / Frontend tests (pull_request) Successful in 3m37s
Project CI / Backend tests (pull_request) Successful in 8m12s
Project CI / AI game creator shell web tests (pull_request) Successful in 2m54s
Project CI / Repository checks (pull_request) Successful in 6m58s
Project CI / Native shell tests (pull_request) Successful in 9m32s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 7m7s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 5m38s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 4m42s
Project CI / Frontend tests (pull_request) Successful in 3m37s
Project CI / Backend tests (pull_request) Successful in 8m12s
Project CI / AI game creator shell web tests (pull_request) Successful in 2m54s
Project CI / Repository checks (pull_request) Successful in 6m58s
Project CI / Native shell tests (pull_request) Successful in 9m32s
- 面板形态:快照读取成功但没有在跑回合时不再渲染「未能读取正在运行的项目」,补回 !readFailed 守卫 - 发布门控:currentProjectPath / currentProjectName 只在 launcherView === 'project-development' 时发布,离开工作台按口径回落到最近启动的在跑项目 - 菜单头部只报运行中数量,读取失败结论由 role=status 一行独占,避免同一句连排两遍 - 标签提示拆成「作用域 + 运行状态」:回落态记为「最近运行的项目:X」、状态位「共 N 个项目在运行」,不再重复「正在运行」 - 数量徽标补 title 说明数字含义 - 用例:新增面板空态回归与两条视图门控回归,更新两处标签断言;三条定向文件 25 passed - 文档:决策日志补 2026-10-05 评审后续条目并修正实施计划交叉引用,里程碑补 2026-10-05 修订,实施计划与主规范同步口径
This commit is contained in:
@@ -20,7 +20,8 @@ export type WindowChromeActiveProjectRuns = {
|
||||
currentProjectPath?: string | null;
|
||||
/**
|
||||
* 当前工作台打开的项目名(清单项目名)。标题栏标签的主文案认这一个:正在运行的项目只在
|
||||
* 徽标和展开菜单里表达,否则「我在哪个项目」会被后台别的项目顶掉。
|
||||
* 徽标和展开菜单里表达,否则「我在哪个项目」会被后台别的项目顶掉。只在项目视图发布:
|
||||
* 离开工作台后置空,标签回落到最近启动的在跑项目。
|
||||
*/
|
||||
currentProjectName?: string | null;
|
||||
readFailed?: boolean;
|
||||
|
||||
@@ -120,14 +120,18 @@ export function ActiveProjectRunsPanel({
|
||||
</span>
|
||||
);
|
||||
}
|
||||
const runningLabel = readFailed
|
||||
/**
|
||||
* 标签提示 = 作用域 + 运行状态,分成两段是为了不在一句话里重复「正在运行」:
|
||||
* 回落态的主文案本身就是「最近启动的在跑项目」,状态位再写一遍会读成「正在运行的项目:X;
|
||||
* 正在运行 N 个项目」。
|
||||
*/
|
||||
const scopeLabel = currentName
|
||||
? `当前项目:${primaryName}`
|
||||
: `最近运行的项目:${primaryName}`;
|
||||
const runStateLabel = readFailed
|
||||
? '正在运行的项目读取失败'
|
||||
: `正在运行 ${runningCount} 个项目`;
|
||||
const triggerLabel = `${
|
||||
currentName
|
||||
? `当前项目:${primaryName}`
|
||||
: `正在运行的项目:${primaryName}`
|
||||
};${runningLabel}`;
|
||||
: `共 ${runningCount} 个项目在运行`;
|
||||
const triggerLabel = `${scopeLabel};${runStateLabel}`;
|
||||
const openProject = (projectPath: string) => {
|
||||
setOpen(false);
|
||||
onOpenProject?.(projectPath);
|
||||
@@ -155,7 +159,12 @@ export function ActiveProjectRunsPanel({
|
||||
{primaryName}
|
||||
</span>
|
||||
{runningCount > 0 ? (
|
||||
<span className="launcher-runs-titlebar-count">{runningCount}</span>
|
||||
<span
|
||||
className="launcher-runs-titlebar-count"
|
||||
title={`${runningCount} 个项目正在运行`}
|
||||
>
|
||||
{runningCount}
|
||||
</span>
|
||||
) : null}
|
||||
<ChevronDown
|
||||
className={`launcher-runs-titlebar-chevron${open ? ' is-open' : ''}`}
|
||||
@@ -167,7 +176,8 @@ export function ActiveProjectRunsPanel({
|
||||
<div className="launcher-runs-titlebar-menu" role="menu">
|
||||
<div className="launcher-runs-titlebar-menu-header">
|
||||
<strong>正在运行的项目</strong>
|
||||
<span>{runningLabel}</span>
|
||||
{/* 头部只报数量:读取失败的结论由下面那行 role=status 独占,避免同一句连排两遍。 */}
|
||||
<span>{runningCount > 0 ? `${runningCount} 个` : null}</span>
|
||||
</div>
|
||||
{readFailed ? (
|
||||
<p className="launcher-runs-titlebar-menu-note" role="status">
|
||||
@@ -229,6 +239,10 @@ export function ActiveProjectRunsPanel({
|
||||
}
|
||||
|
||||
if (runningCount === 0) {
|
||||
// 快照读到了、只是没有在跑回合:整块不渲染,不能报成读取失败。
|
||||
if (!readFailed) {
|
||||
return null;
|
||||
}
|
||||
// 三次都没读到快照:只说"没读到",不改写成业务、权限或审批结论。
|
||||
return (
|
||||
<aside className="launcher-runs-panel" aria-label="正在运行的项目">
|
||||
|
||||
@@ -300,10 +300,22 @@ export function WorkspaceLauncherShell({
|
||||
openActiveProjectRef.current = openActiveProject;
|
||||
|
||||
useEffect(() => {
|
||||
/**
|
||||
* 只在项目视图发布"当前项目"身份。
|
||||
*
|
||||
* `currentProjectContext` 是会话级状态:离开工作台(首页 / 项目组 / 模板库)不会被清空,
|
||||
* 而活动回合快照是全局轮询。若照搬上下文,回首页后标签会一直是「当前项目:<上次打开的项目>」,
|
||||
* 徽标数字却属于别的项目;和同文件里按 `launcherView` 门控的窗口标题 effect 也对不上。
|
||||
*/
|
||||
const inProjectView = launcherView === 'project-development';
|
||||
setActiveProjectRuns({
|
||||
activeTurns,
|
||||
currentProjectPath: currentProjectContext?.projectPath ?? null,
|
||||
currentProjectName: currentProjectContext?.projectName ?? null,
|
||||
currentProjectPath: inProjectView
|
||||
? (currentProjectContext?.projectPath ?? null)
|
||||
: null,
|
||||
currentProjectName: inProjectView
|
||||
? (currentProjectContext?.projectName ?? null)
|
||||
: null,
|
||||
readFailed: snapshotReadFailed,
|
||||
onOpenProject: (projectPath: string) =>
|
||||
openActiveProjectRef.current(projectPath),
|
||||
@@ -312,6 +324,7 @@ export function WorkspaceLauncherShell({
|
||||
activeTurns,
|
||||
currentProjectContext?.projectName,
|
||||
currentProjectContext?.projectPath,
|
||||
launcherView,
|
||||
setActiveProjectRuns,
|
||||
snapshotReadFailed,
|
||||
]);
|
||||
|
||||
@@ -164,11 +164,11 @@ describe('WindowChrome', () => {
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '显示运行项目' }));
|
||||
expect(
|
||||
screen.getByRole('button', { name: /正在运行的项目:后开始/ }),
|
||||
screen.getByRole('button', { name: /最近运行的项目:后开始/ }),
|
||||
).toBeTruthy();
|
||||
expect(screen.queryByRole('menu')).toBeNull();
|
||||
await user.click(
|
||||
screen.getByRole('button', { name: /正在运行的项目:后开始/ }),
|
||||
screen.getByRole('button', { name: /最近运行的项目:后开始/ }),
|
||||
);
|
||||
expect(screen.getAllByRole('menuitem')).toHaveLength(2);
|
||||
});
|
||||
@@ -205,7 +205,7 @@ describe('WindowChrome', () => {
|
||||
expect(screen.queryByText('别的项目')).toBeNull();
|
||||
expect(
|
||||
screen.getByRole('button', {
|
||||
name: '当前项目:飞鸟 v2;正在运行 1 个项目',
|
||||
name: '当前项目:飞鸟 v2;共 1 个项目在运行',
|
||||
}),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -308,6 +308,16 @@ describe('ActiveProjectRunsPanel', () => {
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* 回归:快照读到了、只是没有在跑回合时,面板形态曾把这个健康空态渲染成
|
||||
* 「未能读取正在运行的项目」,把"没在跑"说成"没读到"。
|
||||
*/
|
||||
it('面板形态下快照读取正常但没有在跑回合时整块不渲染', () => {
|
||||
const { container } = render(<ActiveProjectRunsPanel activeTurns={[]} />);
|
||||
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it('不在项目内时标题栏入口只显示最后开始的项目,展开后列出全部项目', () => {
|
||||
const onOpenProject = vi.fn();
|
||||
render(
|
||||
@@ -372,7 +382,7 @@ describe('ActiveProjectRunsPanel', () => {
|
||||
);
|
||||
|
||||
const trigger = screen.getByRole('button', {
|
||||
name: '当前项目:飞鸟 v2;正在运行 1 个项目',
|
||||
name: '当前项目:飞鸟 v2;共 1 个项目在运行',
|
||||
});
|
||||
expect(trigger.textContent).toContain('飞鸟 v2');
|
||||
expect(trigger.textContent).toContain('1');
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/** @vitest-environment jsdom */
|
||||
import type { ReactNode } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { vi } from 'vitest';
|
||||
|
||||
import type { LauncherProjectContext } from '../src/app/types';
|
||||
import { WindowChrome } from '../src/components/WindowChrome';
|
||||
import {
|
||||
useWindowChrome,
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
import { WorkspaceLauncherShell } from '../src/features/app-shell/WorkspaceLauncher';
|
||||
import {
|
||||
act,
|
||||
createGameCreationAppManifest,
|
||||
expect,
|
||||
it,
|
||||
React,
|
||||
@@ -24,6 +26,7 @@ const homeProjectOverride = vi.hoisted(() => ({
|
||||
openProject: null as
|
||||
| null
|
||||
| ((path: string, mode: 'open' | 'create') => Promise<void>),
|
||||
projectContext: null as LauncherProjectContext | null,
|
||||
}));
|
||||
|
||||
vi.mock('../src/features/app-shell/useHomeProjectCreation', async () => {
|
||||
@@ -36,9 +39,16 @@ vi.mock('../src/features/app-shell/useHomeProjectCreation', async () => {
|
||||
...args: Parameters<typeof actual.useHomeProjectCreation>
|
||||
) {
|
||||
const result = actual.useHomeProjectCreation(...args);
|
||||
return homeProjectOverride.openProject
|
||||
? { ...result, openProject: homeProjectOverride.openProject }
|
||||
const withContext = homeProjectOverride.projectContext
|
||||
? {
|
||||
...result,
|
||||
currentProjectContext: homeProjectOverride.projectContext,
|
||||
setCurrentProjectContext: (() => undefined) as never,
|
||||
}
|
||||
: result;
|
||||
return homeProjectOverride.openProject
|
||||
? { ...withContext, openProject: homeProjectOverride.openProject }
|
||||
: withContext;
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -112,3 +122,113 @@ it('真实窗口与工作台状态同步收敛,回调读取最新处理器且
|
||||
rendered.unmount();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 只记录工作台发布给窗口标题栏的「当前项目」名;转发超过 12 次就不再往下传,
|
||||
* 仅用于防止回归时测试进入无限 setState。
|
||||
*/
|
||||
function ProjectScopeBridge({
|
||||
children,
|
||||
record,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
record: (projectName: string | null) => void;
|
||||
}) {
|
||||
const chrome = useWindowChrome();
|
||||
const { setActiveProjectRuns } = chrome;
|
||||
const forwardCount = useRef(0);
|
||||
const publish = useCallback(
|
||||
(next: WindowChromeActiveProjectRuns | null) => {
|
||||
if (next) record(next.currentProjectName ?? null);
|
||||
if (forwardCount.current >= 12) return;
|
||||
forwardCount.current += 1;
|
||||
setActiveProjectRuns(next);
|
||||
},
|
||||
[record, setActiveProjectRuns],
|
||||
);
|
||||
return (
|
||||
<WindowChromeContext.Provider
|
||||
value={{ ...chrome, setActiveProjectRuns: publish }}
|
||||
>
|
||||
{children}
|
||||
</WindowChromeContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
/** 会话里已打开过项目:`currentProjectContext` 在离开工作台后不会被清空。 */
|
||||
function openedProjectContext(): LauncherProjectContext {
|
||||
return {
|
||||
projectPath: '/tmp/window-scope-project',
|
||||
projectName: '飞鸟 v2',
|
||||
projectKind: 'web',
|
||||
manifest: createGameCreationAppManifest('window-scope-project', '飞鸟 v2'),
|
||||
projectRevision: null,
|
||||
creationType: null,
|
||||
startMode: null,
|
||||
initialPrompt: '',
|
||||
attachments: [],
|
||||
recentRunStatus: null,
|
||||
recentRunStopReason: null,
|
||||
createdAt: 1,
|
||||
};
|
||||
}
|
||||
|
||||
function renderScopeRecordedAt(
|
||||
initialView: 'projects' | 'project-development',
|
||||
record: (projectName: string | null) => void,
|
||||
) {
|
||||
return render(
|
||||
<WindowChrome>
|
||||
<ProjectScopeBridge record={record}>
|
||||
<WorkspaceLauncherShell
|
||||
currentUser={testAuthUser}
|
||||
onLogout={() => undefined}
|
||||
initialView={initialView}
|
||||
ProjectChat={() => null}
|
||||
/>
|
||||
</ProjectScopeBridge>
|
||||
</WindowChrome>,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 回归:`currentProjectContext` 是会话级的(离开工作台不清空),发布时若照搬它,
|
||||
* 首页 / 项目组 / 模板库上标签会一直写「当前项目:<上次打开的项目>」,徽标数字却属于
|
||||
* 别的项目。只有工作台视图才认这个身份。
|
||||
*/
|
||||
it('不在工作台视图时不发布会话残留的项目身份', async () => {
|
||||
const publishedNames: Array<string | null> = [];
|
||||
homeProjectOverride.projectContext = openedProjectContext();
|
||||
resetTauriRuntime();
|
||||
const rendered = renderScopeRecordedAt('projects', (name) => {
|
||||
publishedNames.push(name);
|
||||
});
|
||||
try {
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
expect(publishedNames.length).toBeGreaterThan(0);
|
||||
expect(publishedNames.every((name) => name === null)).toBe(true);
|
||||
} finally {
|
||||
homeProjectOverride.projectContext = null;
|
||||
rendered.unmount();
|
||||
}
|
||||
});
|
||||
|
||||
it('工作台视图里照常发布当前项目名', async () => {
|
||||
const publishedNames: Array<string | null> = [];
|
||||
homeProjectOverride.projectContext = openedProjectContext();
|
||||
resetTauriRuntime();
|
||||
const rendered = renderScopeRecordedAt('project-development', (name) => {
|
||||
publishedNames.push(name);
|
||||
});
|
||||
try {
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
expect(publishedNames).toContain('飞鸟 v2');
|
||||
} finally {
|
||||
homeProjectOverride.projectContext = null;
|
||||
rendered.unmount();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@ Milestone: `【里程碑】Direct回合跨页面生命周期与运行中项目
|
||||
|
||||
1. Rust:扩展活动回合注册表并暴露只读快照命令,配定向用例(进入 / 进度 / 终态移除 / 多项目并存)。
|
||||
2. 前端:接入快照读取,实现“重新进入项目 → 恢复忙碌态与进度 → 以快照 sequence 续接 → 阻止并发提交”。
|
||||
3. 前端:在窗口标题栏挂载项目入口;2026-10-04 修订为**主文案恒为当前项目名**,运行中项目只占圆点、数量徽标与展开菜单(展开后按开始时间列出全部在跑项目,当前项目带「当前」标记),复用既有组件与设计 token。原口径「标题栏只显示最后开始的项目」见里程碑文末修订记录。
|
||||
3. 前端:在窗口标题栏挂载项目入口;2026-10-04 修订为**工作台内主文案恒为当前项目名**(只在工作台视图发布这个身份,不在项目内时回落到最近启动的在跑项目),运行中项目只占圆点、数量徽标与展开菜单(展开后按开始时间列出全部在跑项目,当前项目带「当前」标记),复用既有组件与设计 token。原口径「标题栏只显示最后开始的项目」见里程碑文末修订记录。
|
||||
4. 报错归类:按审计结论修正会误导的映射,逐条加回归用例;真实权限拒绝保持原提示。
|
||||
5. 文档:主规范与共享记忆同步;里程碑验收后删除临时计划文件。
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ Parent Spec: `docs/technical/【技术方案】AI游戏创作智能体App实施
|
||||
- 重新进入有在跑回合的项目后:界面进入“正在处理”、显示最近一次进度、以快照 `sequence` 续接后续事件;回合结束前提交第二条需求不会真正发起第二条付费回合。
|
||||
- 回合结束(completed / failed)后:忙碌态解除、可以再次发送;不重复追加助手消息。
|
||||
- 无在跑回合的项目:行为与今天一致(可正常发送,不出现额外提示或阻塞)。
|
||||
- 窗口标题栏入口:常态显示当前项目名(不在项目内时回落到最后开始的在跑项目);正在运行的项目只占圆点与数量徽标,点击后按 `startedAt` 升序列出所有在跑项目,显示项目名(缺失时回退目录名)与状态/时长、当前项目带「当前」标记,点击进入对应项目;没有在跑回合且没有读取失败时不渲染入口;读取失败不顶掉当前项目名,结论在展开菜单里。
|
||||
- 窗口标题栏入口:常态显示当前项目名(只在工作台视图才算「当前项目」,不在项目内时回落到最近启动的在跑项目);正在运行的项目只占圆点与数量徽标,点击后按 `startedAt` 升序列出所有在跑项目,显示项目名(缺失时回退目录名)与状态/时长、当前项目带「当前」标记,点击进入对应项目;没有在跑回合且没有读取失败时不渲染入口;读取失败不顶掉当前项目名,结论落在圆点告警色、标签提示与展开菜单里。
|
||||
- 快照读取失败不得阻断发送、不得显示成业务失败。
|
||||
- 已修的错误映射不回归:`direct-codex-turn-already-running:` 与历史同义中文正文都归一到“仍在处理这个项目的上一条需求”;真正的 `项目权限策略拒绝执行:<command>` 仍显示审批提示。
|
||||
|
||||
@@ -47,6 +47,14 @@ Parent Spec: `docs/technical/【技术方案】AI游戏创作智能体App实施
|
||||
- 背景:issue #618。窗口标题栏那枚标签原来被「正在运行的项目」整块接管(`WindowChrome` 在 `activeTurns.length > 0 || readFailed` 时替换掉当前工作区标题),主文案取开始时间最晚的在跑项目。于是「当前项目没在跑、后台别的项目在跑」时标签显示的是别的项目,当前项目名在整个窗口上消失(现场:当前项目 `gameagent-ff7f3240`,标签显示 `gameagent-c3af9c7e`)。
|
||||
- 决策(主文案优先级):标签主文案恒为当前项目名(`currentProjectContext.projectName`,经 `WindowChromeActiveProjectRuns.currentProjectName` 由 `WorkspaceLauncher` 发布);不在项目内时才回落到最后开始的在跑项目。这与主规范既有条目「打开项目后居中标题切换为当前项目名」一致,运行中项目不再和它抢位置。
|
||||
- 决策(运行态表达):圆点(读取失败转告警色)+ 运行中数量徽标(≥1 即显示)+ 展开菜单;菜单里当前项目那一行用与标签同名的项目名并带「当前」标记(`aria-current` 保留)。
|
||||
- 决策(读取失败):不再整块替换标签,结论只留在圆点与菜单里;既没有在跑项目、又没有当前项目可显示时才保留原来的纯提示。
|
||||
- 决策(读取失败):不再整块替换标签,结论落在圆点告警色、标签提示(`aria-label` / `title`)与展开菜单里,不占主文案;既没有在跑项目、又没有当前项目可显示时才保留原来的纯提示。
|
||||
- 有意未做:后台项目在菜单里仍是目录名。运行中项目名来自快照 `projectName`(Rust 侧取 `thread_id` 目录名),要改成清单项目名就得让 `ThreadManager` 在认领回合时读 `.agent/manifest.json`,给纯内存模块的 claim 路径加锁内文件 I/O;当前项目那一行已用清单项目名对齐。
|
||||
- 验证:`npx vitest run apps/ai-game-creator-shell/tests/directActiveTurns.test.tsx apps/ai-game-creator-shell/tests/WindowChrome.test.tsx apps/ai-game-creator-shell/tests/workspaceWindowSync.test.tsx` → 22 passed(含三条新回归:别的项目在跑时标签仍是当前项目、当前项目自己跑时列表带「当前」、读取失败不顶掉当前项目名);全量 AGC Web 用例与真实浏览器冒烟见 PR。
|
||||
|
||||
## 2026-10-05 修订(评审后续)
|
||||
|
||||
- 面板形态误报读取失败:`ActiveProjectRunsPanel` 的空态早退只判 `runningCount === 0`,把「快照读取成功、只是没有在跑回合」渲染成「未能读取正在运行的项目」,与本组件自身注释相反。补回 `!readFailed` 守卫;`panel` 是默认 prop,虽当前唯一生产调用方 `WindowChrome` 恒用 `titlebar`,但组件注释对外宣称面板布局可复用。
|
||||
- 会话残留的项目身份:`currentProjectContext` 是会话级状态(离开工作台不清空),活动回合快照却是全局轮询;照搬上下文会让首页 / 项目组 / 模板库上的标签一直写「当前项目:<上次打开的项目>」,徽标数字却属于别的项目,也令「不在项目内回落到最近启动的在跑项目」实际不可达。发布时按 `launcherView === 'project-development'` 门控 `currentProjectPath` / `currentProjectName`,与同文件窗口标题 effect 同口径。
|
||||
- 文案去重:读取失败的结论曾在菜单头部与 `role=status` 行连排两遍,头部改为只报数量;回落态触发文案原为「正在运行的项目:X;正在运行 N 个项目」,拆成「作用域 + 运行状态」(回落态作用域记为「最近运行的项目:X」,状态位「共 N 个项目在运行」);数量徽标补 `title` 说明数字含义。
|
||||
- 验证:同三条定向用例文件 → 25 passed;对照旧实现,分别临时禁用 `launcherView` 门控与去掉面板空态守卫,对应用例各自转红(回归用例确有判别力)。
|
||||
- 仍未取得证据(需要真实运行):真机重新进入正在跑的项目后恢复忙碌态与进度、标题栏入口点击进入对应项目、回合结束后可再次发送。
|
||||
|
||||
@@ -9609,11 +9609,11 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
|
||||
## 2026-10-04 AGC 窗口标题栏项目标签改认「当前项目」,运行中项目降级为圆点与徽标
|
||||
|
||||
- 背景:issue #618。窗口标题栏那枚标签原来被「正在运行的项目」整块接管——`WindowChrome` 只要存在活动回合或运行快照读取失败,就把当前工作区标题换成 `ActiveProjectRunsPanel`(titlebar 形态),主文案取开始时间最晚的在跑项目。于是「当前项目没在跑、后台别的项目在跑」时,标签显示的是别的项目(现场:当前项目 `gameagent-ff7f3240`,标签 `gameagent-c3af9c7e`),用户以为自己开错了项目;读取失败时标签还会变成「正在运行的项目读取失败」,同样顶掉当前项目名。
|
||||
- 决策(主文案):标签主文案恒为当前项目名,与当前项目是否在跑回合无关;不在项目内(首页 / 项目组 / 模板库)才回落到最后开始的在跑项目。名字由 `WorkspaceLauncher` 经 `WindowChromeActiveProjectRuns.currentProjectName` 发布(`currentProjectContext.projectName`,即清单项目名)。这同时消掉了同一项目「有回合在跑显示目录名、没在跑显示项目名」的两种口径。
|
||||
- 决策(主文案):标签主文案恒为当前项目名,与当前项目是否在跑回合无关;不在项目内(首页 / 项目组 / 模板库)才回落到最后开始的在跑项目(该回落按视图门控,见 2026-10-05 条目)。名字由 `WorkspaceLauncher` 经 `WindowChromeActiveProjectRuns.currentProjectName` 发布(`currentProjectContext.projectName`,即清单项目名)。这同时消掉了同一项目「有回合在跑显示目录名、没在跑显示项目名」的两种口径。
|
||||
- 决策(运行态):圆点 + 运行中数量徽标(≥1 即显示)+ 展开菜单;菜单里当前项目那一行用与标签同名的项目名,meta 首位带「当前」标记,`aria-current` 保留。读取失败改成告警色圆点 + 菜单内一行结论;只有既没有在跑项目、又没有当前项目可显示时才保留原来的纯提示。
|
||||
- 有意未做:后台项目在菜单里仍是目录名(快照 `projectName` 由 Rust 取 `thread_id` 目录名)。改成清单项目名要在 `ThreadManager` 的 claim 路径里读 `.agent/manifest.json`,等于给纯内存模块加锁内文件 I/O,本轮不做。
|
||||
- 影响范围:`apps/ai-game-creator-shell/src/components/WindowChrome.tsx`、`src/components/windowChromeContext.ts`、`src/features/app-shell/ActiveProjectRunsPanel.tsx`、`src/features/app-shell/WorkspaceLauncher.tsx`、`src/styles.css` 与两个定向用例文件;同步修正主规范 2026-09-15 段的标题栏条目、里程碑与实施计划文末修订。不改 Rust、快照命令字段、公共 API 与持久化。
|
||||
- 验证方式:`npx vitest run apps/ai-game-creator-shell/tests/directActiveTurns.test.tsx apps/ai-game-creator-shell/tests/WindowChrome.test.tsx apps/ai-game-creator-shell/tests/workspaceWindowSync.test.tsx` → 22 passed(三条新回归:别的项目在跑时标签仍是当前项目、当前项目自己跑时列表带「当前」、读取失败不顶掉当前项目名);全量 `npm run test -- apps/ai-game-creator-shell/tests` 与真实 Chromium 冒烟见 PR。
|
||||
- 影响范围:`apps/ai-game-creator-shell/src/components/WindowChrome.tsx`、`src/components/windowChromeContext.ts`、`src/features/app-shell/ActiveProjectRunsPanel.tsx`、`src/features/app-shell/WorkspaceLauncher.tsx`、`src/styles.css` 与两个定向用例文件;同步修正主规范 2026-09-15 段的标题栏条目、里程碑文末修订与实施计划 `修改顺序` 条目。不改 Rust、快照命令字段、公共 API 与持久化。
|
||||
- 验证方式:`npx vitest run apps/ai-game-creator-shell/tests/directActiveTurns.test.tsx apps/ai-game-creator-shell/tests/WindowChrome.test.tsx apps/ai-game-creator-shell/tests/workspaceWindowSync.test.tsx` → 22 passed(三条新回归:别的项目在跑时标签仍是当前项目、当前项目自己跑时列表带「当前」、读取失败不顶掉当前项目名);评审后续修订后为 25 passed,见下条。全量 `npm run test -- apps/ai-game-creator-shell/tests` 与真实 Chromium 冒烟见 PR。
|
||||
- 关联:issue #618、`docs/project-memory/plans/【里程碑】Direct回合跨页面生命周期与运行中项目可见性-2026-09-15.md`(2026-10-04 修订)。
|
||||
|
||||
## 2026-10-04:Direct 普通操作与交付复核解耦
|
||||
@@ -9645,3 +9645,13 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
|
||||
- Linux 排除僵尸时必须保守处理不完整扫描;数字 PID 的读取/解析不确定性不能成为组已空的证据,已确认消失的 PID 可以忽略。
|
||||
- 正常关闭连接的迟到通知不创建或覆盖成功回合的终态报告;无合同收尾新产生的预算报告优先返回,其他真实关闭错误保留。
|
||||
- 权威边界见[Direct 合同规则](../../technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md#宿主验收与执行许可合同)。视觉/玩法判据和预算值不变。
|
||||
|
||||
## 2026-10-05:AGC 标题栏项目标签评审后续(空态、视图门控与文案)
|
||||
|
||||
- 背景:对 issue #618 的标题栏修复做 PR 评审,发现四处与文档或组件自身注释相反的行为,逐条修正;#618 现场(当前项目没在跑、后台别的项目在跑)的结论不变。
|
||||
- 面板形态误报读取失败:`ActiveProjectRunsPanel` 的空态早退只判「没有在跑回合」,把「快照读取成功、只是没有在跑回合」渲染成「未能读取正在运行的项目」。补回 `!readFailed` 守卫;`panel` 是默认 prop,唯一生产调用方 `WindowChrome` 恒用 `titlebar`,但组件注释对外宣称面板布局可复用。
|
||||
- 会话残留的项目身份:`currentProjectContext` 是会话级状态(离开工作台不清空),活动回合快照却是全局轮询;照搬上下文会让首页 / 项目组 / 模板库上的标签一直写「当前项目:<上次打开的项目>」,徽标数字却属于别的项目,也让「不在项目内回落到最近启动的在跑项目」实际不可达。发布时按 `launcherView === 'project-development'` 门控 `currentProjectPath` / `currentProjectName`,与同文件窗口标题 effect 同口径。
|
||||
- 文案去重:读取失败结论曾在菜单头部与 `role=status` 行连排两遍,头部改为只报数量;回落态触发文案原为「正在运行的项目:X;正在运行 N 个项目」,拆成「作用域 + 运行状态」(回落态作用域记为「最近运行的项目:X」,状态位「共 N 个项目在运行」);数量徽标补 `title` 说明数字含义。
|
||||
- 仍有意不做:后台项目在菜单里是目录名;判定 `aria-current` 的路径匹配仍是 `projectPathsMatchForInvalidation`;不改 Rust、快照命令字段、公共 API 与持久化。
|
||||
- 验证方式:`npx vitest run apps/ai-game-creator-shell/tests/directActiveTurns.test.tsx apps/ai-game-creator-shell/tests/WindowChrome.test.tsx apps/ai-game-creator-shell/tests/workspaceWindowSync.test.tsx` → 25 passed;对照旧实现分别临时禁用 `launcherView` 门控、去掉面板空态守卫,对应用例各自转红。真机运行时证据仍缺,见里程碑文末「仍未取得证据」。
|
||||
- 关联:issue #618、本文件上一条(2026-10-04 标题栏项目标签)、`docs/project-memory/plans/【里程碑】Direct回合跨页面生命周期与运行中项目可见性-2026-09-15.md`(2026-10-05 修订)。
|
||||
|
||||
@@ -1734,7 +1734,7 @@ DirectProject、Agent Runtime、Provider、app-server、内置 MCP、命令执
|
||||
|
||||
Direct 回合的所有权属于进程内项目身份锁,不属于当前页面。离开工作台或切换到首页时,正在运行的回合继续执行;重新进入项目时,只读活动回合快照负责恢复 clientTurnId 与运行状态,Direct 回合事件负责更新进度。Thread Manager 的 Provider turnId 与事件序列不得当成 clientTurnId 或展示事件序列;其通知只触发快照和历史同步,回放的终态不能重新创建活动回合或“正在提交回复”。定期复核同一份活动快照以弥补页面切换时丢失的结束事件;失败读取保留已知状态,过期读取不能覆盖新回合。活动回合结束后移除快照并解除发送阻断;没有活动回合的项目保持原有发送行为。
|
||||
|
||||
壳层窗口标题栏的项目入口只呈现活动 Direct 回合快照:常态显示当前打开的项目名(不在项目内时回落到最后开始的在跑项目),正在运行的项目只占圆点、数量徽标与展开菜单;展开后按开始时间排序,显示全部在跑项目的项目名、状态、活动时长、当前项目标记并允许进入对应项目。快照读取失败保留上一份结果、不顶掉当前项目名,读取结论只在展开菜单里给出,不得改写成权限、审批或业务失败;入口不建立第二份运行真相。应用重启后的恢复、取消入口和非 Direct Agent 项目不在本合同内。
|
||||
壳层窗口标题栏的项目入口只呈现活动 Direct 回合快照:常态显示当前打开的项目名(只在工作台视图认这个身份,不在项目内时回落到最近启动的在跑项目),正在运行的项目只占圆点、数量徽标与展开菜单;展开后按开始时间排序,显示全部在跑项目的项目名、状态、活动时长、当前项目标记并允许进入对应项目。快照读取失败保留上一份结果、不顶掉当前项目名,读取结论落在圆点告警色、标签提示与展开菜单里、不占主文案,不得改写成权限、审批或业务失败;入口不建立第二份运行真相。应用重启后的恢复、取消入口和非 Direct Agent 项目不在本合同内。
|
||||
|
||||
活动回合快照命令是进程内 Tauri 只读命令,不进入公共 API 或持久化协议;字段包含 `projectPath / projectName / turnId / status / activity / startedAt / updatedAt / sequence`,状态和序号与既有 Direct 回合进度事件一致。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user