Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ assert.match(
);
assert.match(
styles,
/\.personal-channel-scroll:has\(\.personal-task-board\) \{ overflow: hidden; \}/,
/\.personal-channel-scroll\[data-active-goal-view="tasks"\]:has\(\.personal-task-board\) \{ overflow: hidden; \}/,
"Desktop Tasks keeps the page chrome fixed while lanes scroll",
);
assert.match(
Expand All @@ -52,7 +52,7 @@ assert.match(
);
assert.match(
styles,
/@media \(max-width: 720px\)[\s\S]*\.personal-channel-scroll:has\(\.personal-task-board\) \{ overflow-y: auto; \}[\s\S]*\.personal-task-lane-scroll,[^}]*\{[^}]*overflow: visible;/,
/@media \(max-width: 720px\)[\s\S]*\.personal-channel-scroll\[data-active-goal-view="tasks"\]:has\(\.personal-task-board\) \{ overflow-y: auto; \}[\s\S]*\.personal-task-lane-scroll,[^}]*\{[^}]*overflow: visible;/,
"Narrow screens fall back to one page scroller instead of nested scroll traps",
);
assert.match(
Expand Down Expand Up @@ -137,12 +137,13 @@ async function main() {
const url = `http://127.0.0.1:${port}/?statusUrl=/status.json`;
await waitForHttp(url);
browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 1512, height: 760 } });
const page = await browser.newPage({ locale: "zh-CN", viewport: { width: 1512, height: 760 } });
const fixture = statusFixture();
await page.route(`http://127.0.0.1:${port}/status.json**`, (route) => route.fulfill({ contentType: "application/json", json: fixture, status: 200 }));
await page.route("**/api/**", (route) => route.fulfill({ contentType: "application/json", json: { ok: true }, status: 200 }));
await page.goto(url, { waitUntil: "networkidle" });
await page.locator(".personal-goal-link").filter({ hasText: "Task board scroll fixture" }).click();
await page.getByRole("navigation", { name: "Goal 视图" }).getByRole("button", { name: /^(Tasks|任务)$/ }).click();

const lane = page.getByRole("region", { name: "待执行 / 进行中" });
await lane.waitFor({ state: "visible" });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,21 @@ try {
const menu = page.locator(".personal-mobile-menu");
if (await menu.isVisible()) await menu.click();
await page.locator(".personal-goal-link", { hasText: "Progress Projection" }).click();
const activeTab = page.locator('.personal-goal-tabs button[aria-current="page"]');
await activeTab.filter({ hasText: "概览" }).waitFor();
const goalLink = new URL(page.url());
goalLink.searchParams.delete("view");
await page.goto(goalLink.href, { waitUntil: "networkidle" });
await activeTab.filter({ hasText: "概览" }).waitFor();
await page.reload({ waitUntil: "networkidle" });
await activeTab.filter({ hasText: "概览" }).waitFor();
await page.screenshot({ path: resolve(outputDir, `goal-default-overview-${width}.png`), animations: "disabled" });
for (const [view, label] of [["conversation", "对话"], ["files", "成果"], ["tasks", "任务"]]) {
const explicitLink = new URL(goalLink);
explicitLink.searchParams.set("view", view);
await page.goto(explicitLink.href, { waitUntil: "networkidle" });
await activeTab.filter({ hasText: label }).waitFor();
}
const opener = page.locator(".personal-task-card", { hasText: "Current Todo" }).locator(":scope > button");
const drawer = page.getByRole("dialog", { name: "Todo 详情" });
const scope = () => {
Expand Down Expand Up @@ -86,7 +101,7 @@ try {
await workspace.close();
}
}
console.log("task-inspector-route: passed (packaged desktop/390px, keyboard/reduced motion, close/reload, Back/Forward, deliberate and unavailable deep links, no model or authority writes)");
console.log("task-inspector-route: passed (packaged desktop/390px, default overview and explicit view links, keyboard/reduced motion, close/reload, Back/Forward, deliberate and unavailable deep links, no model or authority writes)");
} finally {
await browser?.close();
server?.kill("SIGTERM");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -924,7 +924,7 @@ export function PersonalWorkspacePage({
const [taskInspectorExpanded, setTaskInspectorExpanded] = useState(false);
const [activeSessionRun, setActiveSessionRun] = useState<WorkspaceRun | null>(null);
const [proposals, setProposals] = useState<Record<string, WorkspaceActionPreview>>({});
const [localView, setLocalView] = useState<WorkspaceGoalTab>(controlledGoalId ? "chat" : "overview");
const [localView, setLocalView] = useState<WorkspaceGoalTab>("overview");
const selectedGoalTab = controlledView ?? localView;
const agentWorkActive = Boolean(controlledGoalId === undefined ? localGoalId : controlledGoalId) && selectedGoalTab === "chat" && selectedWorkAgentId != null;
const workReturn = useRef<{scope: string; agentId: string} | null>(null);
Expand Down Expand Up @@ -1904,7 +1904,7 @@ export function PersonalWorkspacePage({
onOpenOutput: drawerCallbacks.onOpenOutput,
} : drawerCallbacks;

function selectGoal(goalId: string | null, view: WorkspaceGoalTab = goalId ? "tasks" : "overview") {
function selectGoal(goalId: string | null, view: WorkspaceGoalTab = "overview") {
setLocalGoalId(goalId);
setManagerConversationReceiptVisible(false);
setGoalConversationReceiptVisible(false);
Expand Down
2 changes: 1 addition & 1 deletion apps/presentation/dashboard/src/views/dashboard-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3509,7 +3509,7 @@ export function DashboardPage() {
void navigate({search: current => "goalId" in current && current.goalId === goalId && current.statusUrl === statusUrl
? {...current, todo: todoId ?? undefined} : current});
}}
selectedView={search.view === "conversation" ? "chat" : search.goalId ? search.view ?? "chat" : "overview"}
selectedView={search.view === "conversation" ? "chat" : search.goalId ? search.view ?? "overview" : "overview"}
onSelectView={(view) => {
void navigate({ search: current => ({ ...current, workAgent: undefined, view: view === "chat" ? "conversation" : view }) });
}}
Expand Down
3 changes: 3 additions & 0 deletions docs/architecture/rfcs/goal-workspace-surface-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ screen.*
| 8 | **All-work rail**: tabs All work / Activity, filter chips, sections | ≥1200px; drawer below | Complete inventory without leaving the brief |
| 9 | Existing **Delivery and evidence** section | As today | Stage 4 contract unchanged, below Results |

Opening a Goal from the sidebar, workspace home or a Goal URL without a view
starts on Overview. Explicit Tasks, Chat and Results links keep their destination.

Chat remains a direct destination. "Discuss" on a decision or row opens Goal
Chat with that subject attached through the existing composer; it does not
create a second conversation store.
Expand Down
8 changes: 5 additions & 3 deletions examples/personal-workspace-browser/navigation-sorting.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ export const navigationSortingScenario = {
await page.getByRole("button", { name: /关闭详情/ }).click();
pass(26, "Overview keeps direct decisions, explicit unknown usage and read failures; output provenance is one click away.");
await page.locator(".personal-goal-link").filter({ hasText: "Progress Projection" }).click();
await page.getByRole("navigation", { name: "Goal 视图" }).getByRole("button", { name: /^(Tasks|任务)$/ }).click();
await page.getByRole("button", { name: /^(Tasks|任务)$/, current: "page" }).waitFor({ state: "visible" });
await page.locator(".personal-task-card", { hasText: "Deferred queue task" }).locator("button").first().click();
const dateResumeDrawer = page.getByRole("dialog", { name: "Todo 详情" });
Expand All @@ -179,13 +180,13 @@ export const navigationSortingScenario = {
pass(22, "Todo detail renders the shared typed date condition, ready state, and stable receipt id.");

// A visit to another view or settings must not discard loaded task history.
const historyScroll = page.getByTestId("completed-task-lane").getByRole("region");
const historyScroll = page.locator('[data-goal-panel="tasks"]').getByTestId("completed-task-lane").getByRole("region");
await page.waitForFunction(() => {
const lane = document.querySelector('[data-testid="completed-task-lane"] .personal-task-lane-scroll');
const lane = document.querySelector('[data-goal-panel="tasks"] [data-testid="completed-task-lane"] .personal-task-lane-scroll');
return lane && lane.scrollHeight > lane.clientHeight + 740;
});
await historyScroll.evaluate(element => { element.scrollTop = 740; });
await page.waitForFunction(() => document.querySelector('[data-testid="completed-task-lane"] .personal-task-lane-scroll').scrollTop === 740);
await page.waitForFunction(() => document.querySelector('[data-goal-panel="tasks"] [data-testid="completed-task-lane"] .personal-task-lane-scroll').scrollTop === 740);
const retainedHistory = await historyScroll.elementHandle();
const goalViews = page.getByRole("navigation", { name: "Goal 视图" });
for (const name of ["概览", "成果", "对话", "任务"]) {
Expand All @@ -203,6 +204,7 @@ export const navigationSortingScenario = {
throw new Error("Settings return reset loaded task history");
}
await page.locator(".personal-goal-link", { hasText: "Multi Agent Projection" }).click();
await goalViews.getByRole("button", { name: "任务", exact: true }).click();
const laneFilter = page.getByRole("combobox", { name: "按工作 Agent 筛选", exact: true });
await laneFilter.selectOption("codex-older-lane");
await goalViews.getByRole("button", { name: "概览", exact: true }).click();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,6 @@ export async function installShowcaseWorkspace(api, page, state) {
export async function openShowcaseGoal(page) {
await page.locator(".personal-goal-row").filter({ has: page.locator("strong", { hasText: title }) }).first().locator(".personal-goal-link").click();
await page.getByTestId("goal-vitals").waitFor({ state: "visible", timeout: 15_000 });
await page.getByRole("navigation", { name: /^(Goal 视图|Goal view)$/ }).getByRole("button", { name: /^(概览|Overview)$/ }).click();
await page.getByRole("navigation", { name: /^(Goal 视图|Goal view)$/ }).getByRole("button", { name: /^(概览|Overview)$/, current: "page" }).waitFor();
await page.locator(".goal-now, .work-map, .delivery-review [role=alert]").first().waitFor({ timeout: 15_000 });
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const taskInspectorReturnScenario = {
const menu = page.locator(".personal-mobile-menu");
if (await menu.isVisible()) await menu.click();
await page.locator(".personal-goal-link", { hasText: "Progress Projection" }).click();
await page.getByRole("navigation", { name: "Goal 视图" }).getByRole("button", { name: /^(Tasks|任务)$/ }).click();
const card = page.locator(".personal-task-card", { hasText: "Current Todo" });
const opener = card.locator(":scope > button");
await opener.waitFor();
Expand Down
24 changes: 15 additions & 9 deletions examples/personal-workspace-browser/typed-actions.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -841,8 +841,9 @@ export const typedActionsScenario = {
const goalButton = page.locator(".personal-goal-link").first();
await goalButton.click();
const goalNavigation = page.getByRole("navigation", { name: "Goal 视图" });
const defaultTasksTab = goalNavigation.getByRole("button", { name: /^(Tasks|任务)$/ });
if (await defaultTasksTab.getAttribute("aria-current") !== "page") throw new Error("Selecting a Goal did not prioritize its Tasks view");
await goalNavigation.getByRole("button", { name: /^(Overview|概览)$/, current: "page" }).waitFor();
const tasksTab = goalNavigation.getByRole("button", { name: /^(Tasks|任务)$/ });
await tasksTab.click();
await page.screenshot({ path: resolve(outputDir, "goal-tasks-loopx-theme.png"), fullPage: false, animations: "disabled" });
await goalNavigation.getByRole("button", { name: /^(Files|成果)$/ }).click();
const publicFiles = page.locator(".personal-files-list > button");
Expand All @@ -852,7 +853,7 @@ export const typedActionsScenario = {
await goalNavigation.getByRole("button", { name: /^(Chat|对话)$/ }).click();
await page.locator(".personal-channel-timeline").waitFor({ state: "visible" });
await page.screenshot({ path: resolve(outputDir, "goal-chat-loopx-theme.png"), fullPage: false, animations: "disabled" });
await defaultTasksTab.click();
await tasksTab.click();
const desktopNavigationTrigger = page.locator(".personal-mobile-menu");
if (await desktopNavigationTrigger.isVisible()) {
throw new Error("Mobile-only Goal navigation trigger leaked into the persistent desktop sidebar layout");
Expand Down Expand Up @@ -891,6 +892,7 @@ export const typedActionsScenario = {
await page.screenshot({ path: resolve(outputDir, "goal-header-compact-width.png"), fullPage: false, animations: "disabled" });
if (fullDesktopViewport) await page.setViewportSize(fullDesktopViewport);
await page.locator(".personal-goal-link", { hasText: "Progress Projection" }).click();
await tasksTab.click();
await page.getByRole("heading", { name: "Progress Projection" }).waitFor({ state: "visible" });
await page.locator(".personal-task-card").getByText("Current Todo", {exact: true}).waitFor();
const unclaimedCard = page.locator(".personal-task-card", {hasText: "Current Todo"});
Expand Down Expand Up @@ -948,7 +950,7 @@ export const typedActionsScenario = {
await historyScroll.evaluate(element => { element.scrollTop = element.scrollHeight; });
await response;
await page.waitForFunction(minimum => {
const window = document.querySelector('[data-testid="completed-task-lane"] .personal-completed-window');
const window = document.querySelector('[data-goal-panel="tasks"] [data-testid="completed-task-lane"] .personal-completed-window');
return window && Number.parseFloat(window.style.height) >= minimum;
}, Math.min(4087, (batch + 1) * 40) * 148);
if (await completedColumn.locator('.personal-completed-row').count() > 20) throw new Error('Completed history DOM grew with accumulated pages');
Expand All @@ -975,7 +977,7 @@ export const typedActionsScenario = {
page.on('request', request => { if (request.url().includes('/api/chat/completed-todos?')) historyRequests += 1; });
await page.getByRole('button', { name: '列表', exact: true }).click();
await assertDeferredTask();
const listHistory = page.getByTestId('completed-task-lane');
const listHistory = page.locator('[data-goal-panel="tasks"]').getByTestId('completed-task-lane');
await listHistory.getByRole('button', { name: '已完成', exact: false }).click();
await listHistory.getByText('4087', { exact: true }).waitFor();
await listHistory.getByText('Completed A', { exact: true }).waitFor();
Expand All @@ -987,6 +989,7 @@ export const typedActionsScenario = {
await completedColumn.getByText('Completed A', { exact: true }).waitFor();
if (historyRequests) throw new Error('Switching presentation replaced the completed-history snapshot');
await page.locator(".personal-goal-link", { hasText: "Multi Agent Projection" }).click();
await tasksTab.click();
const multiAgentHeader = await page.locator(".personal-channel-title").innerText();
if (multiAgentHeader.includes("codex-older-lane")) {
throw new Error(`Multi-Agent Goal header still implies arbitrary single-lane ownership: ${multiAgentHeader}`);
Expand All @@ -1005,6 +1008,7 @@ export const typedActionsScenario = {
const runtimeSelector = page.getByRole("combobox", { name: "选择聊天 Runtime" });
if (!await runtimeSelector.count()) throw new Error("Chat runtime selector is not explicitly labelled independently from work-Agent lanes");
await goalButton.click();
await tasksTab.click();
const readBoardGeometry = async () => {
const kanban = page.locator(".personal-task-kanban");
await kanban.waitFor({ state: "visible" });
Expand All @@ -1028,6 +1032,7 @@ export const typedActionsScenario = {
};
const selectFirstGoal = async () => {
await page.locator(".personal-goal-link").first().click();
await tasksTab.click();
await page.locator(".personal-task-kanban").waitFor({ state: "visible" });
};
const selectProductReleaseGoal = async () => {
Expand All @@ -1037,6 +1042,7 @@ export const typedActionsScenario = {
if (await stoppedGoals.getAttribute("open") === null) await stoppedGoals.locator("summary").click();
}
await goal.click();
await tasksTab.click();
await page.locator(".personal-task-kanban").waitFor({ state: "visible" });
};
const populatedGeometry = await readBoardGeometry();
Expand All @@ -1062,7 +1068,7 @@ export const typedActionsScenario = {
await page.setViewportSize(desktopViewport);
await page.waitForTimeout(200);
await selectFirstGoal();
await page.locator(".personal-object-list").first().waitFor({ state: "visible" });
await page.locator('[data-goal-panel="tasks"] .personal-object-list').first().waitFor({ state: "visible" });
if (await page.locator(".personal-task-capability-callout").count()) throw new Error("Goal capability settings still consume a full-width Tasks row");
await page.getByRole("button", { name: "Goal 设置", exact: true }).click();
await page.getByRole("heading", { level: 1, name: "能力中心", exact: true }).waitFor({ state: "visible" });
Expand Down Expand Up @@ -1602,7 +1608,7 @@ export const typedActionsScenario = {
await page.getByRole("button", { name: "返回工作区", exact: true }).click();
await selectProductReleaseGoal();
await page.getByRole("navigation", { name: "Goal 视图" }).getByRole("button", { name: /^(Tasks|任务)$/ }).click();
await page.locator(".personal-object-list").first().waitFor({ state: "visible" });
await page.locator('[data-goal-panel="tasks"] .personal-object-list').first().waitFor({ state: "visible" });
await page.getByRole("navigation", { name: "Goal 视图" }).getByRole("button", { name: /^(Files|成果)$/ }).click();
const reportOutput = page.getByTestId("personal-goal-outputs").getByRole("button", { name: /Product Release milestone report/ });
await reportOutput.waitFor({ state: "visible" });
Expand Down Expand Up @@ -1986,7 +1992,7 @@ export const typedActionsScenario = {
api.nextStatusDelayMs = 1_600;
await page.getByRole("button", { name: "查看更新后的 Goal", exact: true }).click();
await page.getByRole("dialog").filter({ hasText: "执行结果" }).waitFor({ state: "hidden", timeout: 600 });
await goalNavigation.getByRole("button", { name: /^(Tasks|任务)$/, current: "page" }).waitFor({ state: "visible", timeout: 600 });
await goalNavigation.getByRole("button", { name: /^(Overview|概览)$/, current: "page" }).waitFor({ state: "visible", timeout: 600 });
await goalNavigation.getByRole("button", { name: /^(Chat|对话)$/ }).click();
} else {
await page.getByRole("button", { name: "关闭", exact: true }).click();
Expand Down Expand Up @@ -2066,7 +2072,7 @@ export const typedActionsScenario = {
await needsYouCard.click();
await page.getByRole("heading", { name: needsYouSource }).waitFor({ state: "visible" }).catch(() => {});
await page.getByText(needsYouAction, { exact: true }).first().waitFor({ state: "visible" });
await page.locator(".personal-object-list").first().getByRole("button").first().click();
await page.locator(".goal-overview-attention button", { hasText: needsYouAction }).first().click();
await page.getByText("需要你", { exact: true }).last().waitFor({ state: "visible" });
await page.getByText("更多决定").click();
const writesBeforeDecision = api.durableWriteCount;
Expand Down
Loading
Loading