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
14 changes: 8 additions & 6 deletions apps/presentation/dashboard/package.json

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions apps/presentation/dashboard/smoke/answer-report-smoke.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ for (const zh of [false, true]) {
assert.equal(url.searchParams.get("statusUrl"), "/selected/status.json");
assert.equal(url.searchParams.has("reportSessionId"), false);
assert.equal(url.searchParams.has("reportMessageId"), false);
assert.equal(url.searchParams.get("messageSession"), ["manager", "goal.community"].includes(channel_id) ? "old" : null);
assert.equal(url.searchParams.get("message"), ["manager", "goal.community"].includes(channel_id) ? "answer" : null,
"Only a known original conversation receives a message focus");
}
for (const session of [null, {goal_id: "community"}, {goal_id: "loopx-manager", channel_id: "goal.loopx-manager"}]) {
assert.equal(new URL(answerReportNavigation(session, current, "", zh).url).searchParams.get("goalId"), "");
Expand Down Expand Up @@ -49,4 +52,30 @@ for (const [status, verification, expected] of [
assert.ok(html.includes(expected));
assert.ok(html.includes("Progress update"));
}
const original = `${current}&conversationBinding=${"a".repeat(24)}&conversationSession=bound&workAgent=wrong&workSession=wrong&todo=wrong`;
const bound = new URL(answerReportNavigation({session_id: "bound", goal_id: "loopx-manager", channel_id: "manager.external.synthetic"}, original, "/source/status.json", false).url);
assert.equal(bound.searchParams.get("conversationBinding"), "a".repeat(24));
assert.equal(bound.searchParams.get("conversationSession"), "bound");
assert.equal(bound.searchParams.get("messageSession"), "bound");
assert.equal(bound.searchParams.get("message"), "answer");
assert.equal(bound.searchParams.get("todo"), null);
assert.equal(bound.searchParams.get("workAgent"), null);
const wrongBound = new URL(answerReportNavigation({session_id: "other", goal_id: "loopx-manager", channel_id: "manager.external.synthetic"}, original, "", false).url);
assert.equal(wrongBound.searchParams.get("conversationBinding"), null, "An unrelated Session never inherits a bound source");
assert.equal(wrongBound.searchParams.get("message"), null);
const managed = new URL(answerReportNavigation({session_id: "work", goal_id: "community", channel_id: "agent.writer", managed_agent_id: "writer", session_mode: "managed_runtime"}, original, "", false).url);
assert.equal(managed.searchParams.get("goalId"), "community");
assert.equal(managed.searchParams.get("workAgent"), "writer");
assert.equal(managed.searchParams.get("workSession"), "managed:work");
assert.equal(managed.searchParams.get("messageSession"), "work");
assert.equal(managed.searchParams.get("conversationBinding"), null);
const projectRef = "b".repeat(24);
const project = new URL(answerReportNavigation({session_id: "project", goal_id: null, project_ref: projectRef, channel_id: `project.${projectRef}`}, original, "", false).url);
assert.equal(project.searchParams.get("workspace"), projectRef);
assert.equal(project.searchParams.get("goalId"), "");
assert.equal(project.searchParams.get("messageSession"), "project");
assert.equal(project.searchParams.get("conversationBinding"), null);
const externalProject = new URL(answerReportNavigation({session_id: "bound", goal_id: null, project_ref: projectRef, channel_id: `project.external.${"a".repeat(24)}.${"c".repeat(24)}`}, original, "", false).url);
assert.equal(externalProject.searchParams.get("conversationBinding"), "a".repeat(24));
assert.equal(externalProject.searchParams.get("workspace"), null, "An external project retains its original audience binding");
console.log("answer-report: scope isolation, actual phase, delivery uncertainty and legacy answers passed");
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ try {
const state = { done: 23, cost: 24.8, deliveryFails: false };
const session = await openWorkspacePage({ newPage: options => browser.newPage({ locale: 'zh-CN', ...options }) }, url, {
viewport: { width: 1440, height: 1000 },
gotoWaitUntil: 'domcontentloaded',
beforeGoto: async (api, page) => {
page.setDefaultTimeout(10000);
page.setDefaultNavigationTimeout(15000);
await installShowcaseWorkspace(api, page, state);
const transform = api.transformStatus;
api.transformStatus = fixture => {
Expand Down Expand Up @@ -82,8 +85,17 @@ try {
await inlineRail.locator('[data-testid="completed-task-lane"] > header button').click();
await historyResponse;
const overview = page.locator('.goal-overview');
await decision.focus();
await overview.evaluate(element => element.style.maxWidth = '800px');
await page.waitForFunction(() => !document.querySelector('.goal-workspace-rail .goal-all-work'));
await page.waitForFunction(() => document.querySelector('.goal-workspace-composition')?.classList.contains('is-attention-first'));
assert.equal(await decision.count(), 1, 'A narrow blocking decision remains one interactive instance');
assert.equal(await decision.evaluate(element => element === document.activeElement), true, 'Container reflow preserves the focused decision');
assert.ok(await decision.evaluate(element => {
const direction = document.querySelector('.goal-overview-direction');
return Boolean(element.compareDocumentPosition(direction) & Node.DOCUMENT_POSITION_FOLLOWING)
&& element.getBoundingClientRect().top < direction.getBoundingClientRect().top;
}), 'The blocking decision precedes the long Goal in DOM and visual order');
assert.equal(await inventory.isVisible(), false, 'Container reflow hides the inline inventory');
const readsBeforeHiddenRefresh = historyReads;
state.done++;
Expand Down
75 changes: 64 additions & 11 deletions apps/presentation/dashboard/smoke/task-inspector-route-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ try {
browser = await launchBrowser(loadPlaywright().chromium);
await mkdir(outputDir, { recursive: true });
for (const { height, width } of viewports) {
const workspace = await openWorkspacePage({ newPage: options => browser.newPage({ locale: "zh-CN", ...options }) }, url, { viewport: { width, height } });
const workspace = await openWorkspacePage({ newPage: options => browser.newPage({ locale: "zh-CN", ...options }) }, url, {
viewport: { width, height }, gotoWaitUntil: "domcontentloaded",
beforeGoto: (_api, page) => {page.setDefaultTimeout(10000); page.setDefaultNavigationTimeout(15000);},
});
const { page, api } = workspace;
try {
await page.emulateMedia({ reducedMotion: "reduce" });
Expand All @@ -33,15 +36,15 @@ try {
await activeTab.filter({ hasText: "概览" }).waitFor();
const goalLink = new URL(page.url());
goalLink.searchParams.delete("view");
await page.goto(goalLink.href, { waitUntil: "networkidle" });
await page.goto(goalLink.href, { waitUntil: "domcontentloaded" });
await activeTab.filter({ hasText: "概览" }).waitFor();
await page.reload({ waitUntil: "networkidle" });
await page.reload({ waitUntil: "domcontentloaded" });
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 page.goto(explicitLink.href, { waitUntil: "domcontentloaded" });
await activeTab.filter({ hasText: label }).waitFor();
}
const opener = page.locator(".personal-task-card", { hasText: "Current Todo" }).locator(":scope > button");
Expand Down Expand Up @@ -170,7 +173,7 @@ try {
&& getComputedStyle(document.documentElement).overflow !== "hidden"
&& getComputedStyle(document.body).overflow !== "hidden";
});
await page.reload({ waitUntil: "networkidle" });
await page.reload({ waitUntil: "domcontentloaded" });
await opener.waitFor();
assert.equal(await drawer.count(), 0, "Reload cannot resurrect a dismissed Task");
assert.deepEqual(scope(), initialScope);
Expand All @@ -187,26 +190,76 @@ try {
await page.goForward();
await page.waitForURL(value => !value.searchParams.has("todo"));
await drawer.waitFor({ state: "hidden" });
await page.goto(deepLink, { waitUntil: "networkidle" });
await page.goto(deepLink, { waitUntil: "domcontentloaded" });
await drawer.waitFor();
await page.reload({ waitUntil: "networkidle" });
await page.reload({ waitUntil: "domcontentloaded" });
await drawer.waitFor();
assert.equal(new URL(page.url()).searchParams.get("todo"), "todo-progress-current", "A deliberate deep link remains restorable");
await drawer.getByRole("button", { name: /关闭详情/ }).click();
await page.waitForURL(value => !value.searchParams.has("todo"));
await page.reload({ waitUntil: "networkidle" });
await page.reload({ waitUntil: "domcontentloaded" });
await opener.waitFor();
assert.equal(await drawer.count(), 0);
const unavailable = new URL(deepLink);
unavailable.searchParams.set("todo", "todo-missing-route");
await page.goto(unavailable.href, { waitUntil: "networkidle" });
await page.goto(unavailable.href, { waitUntil: "domcontentloaded" });
await drawer.getByRole("button", { name: "重试读取完整要求" }).waitFor();
await page.keyboard.press("Escape");
await page.waitForURL(value => !value.searchParams.has("todo"));
await page.reload({ waitUntil: "networkidle" });
await page.reload({ waitUntil: "domcontentloaded" });
await opener.waitFor();
assert.equal(await drawer.count(), 0, "An unavailable deep link can be dismissed without a recovery loop");
assert.deepEqual(scope(), initialScope);
if (width === 1512 || width === 390) {
const tasks = page.locator('[data-goal-panel="tasks"] .personal-task-board');
const taskSearch = tasks.getByRole("searchbox", {name: "搜索当前已列出的任务"});
await tasks.getByRole("button", {name: "列表", exact: true}).click();
await taskSearch.fill("Current");
await tasks.getByRole("combobox", {name: "按工作 Agent 筛选"}).selectOption("");
await page.waitForURL(value => value.searchParams.get("taskView") === "list" && value.searchParams.get("taskOwner") === "");
await page.locator('.personal-goal-tabs button').filter({hasText: "概览"}).click();
const now = page.getByRole("region", {name: "当前工作", exact: true});
await now.getByRole("button", {name: "看板", exact: true}).click();
const overviewBoard = page.locator('.goal-overview .personal-task-board');
assert.equal(await overviewBoard.getByRole("searchbox", {name: "搜索当前已列出的任务"}).inputValue(), "Current", "Overview and Tasks share the listed-task query");
assert.equal(await overviewBoard.getByRole("button", {name: "列表", exact: true}).getAttribute("aria-pressed"), "true");
await page.reload({waitUntil: "domcontentloaded"});
await overviewBoard.waitFor();
assert.equal(await overviewBoard.getByRole("searchbox", {name: "搜索当前已列出的任务"}).inputValue(), "Current", "Reload restores the scoped query");
assert.equal(await overviewBoard.getByRole("combobox", {name: "按工作 Agent 筛选"}).inputValue(), "", "Unassigned remains an explicit filter");
await page.goBack();
await now.getByRole("button", {name: "按负责人", exact: true}).waitFor();
assert.equal(await now.getByRole("button", {name: "按负责人", exact: true}).getAttribute("aria-pressed"), "true", "Back restores the overview lens");
await page.goForward();
await overviewBoard.waitFor();
await page.locator('.personal-goal-tabs button').filter({hasText: "任务"}).click();
await tasks.waitFor();
if (width === 1512) {
await tasks.locator('.personal-task-card', {hasText: "Current Todo"}).locator(":scope > button").click();
await drawer.waitFor();
await tasks.getByRole("group", {name: "任务状态筛选"}).getByRole("button", {name: "受阻与延后", exact: true}).click();
await tasks.locator(".personal-task-selection-note").waitFor();
assert.equal(new URL(page.url()).searchParams.get("todo"), "todo-progress-current", "Filtering cannot change the selected Task identity");
await tasks.getByRole("button", {name: "显示所选任务", exact: true}).click();
await drawer.getByRole("button", {name: /关闭详情/}).click();
await drawer.waitFor({state: "hidden"});
} else {
await tasks.getByRole("button", {name: "清除筛选", exact: true}).click();
}
await tasks.getByRole("button", {name: "看板", exact: true}).click();
const otherGoal = new URL(page.url());
otherGoal.searchParams.set("goalId", "product-release");
// A copied old filter address must not filter another Goal.
otherGoal.searchParams.set("taskQuery", "Current");
otherGoal.searchParams.set("taskView", "list");
await page.goto(otherGoal.href, {waitUntil: "domcontentloaded"});
const otherTasks = page.locator('[data-goal-panel="tasks"] .personal-task-board');
await otherTasks.waitFor();
assert.equal(await otherTasks.getByRole("searchbox", {name: "搜索当前已列出的任务"}).inputValue(), "");
assert.equal(await otherTasks.getByRole("button", {name: "看板", exact: true}).getAttribute("aria-pressed"), "true", "Another Goal does not inherit route presentation state");
await page.goto(goalLink.href + (goalLink.search ? "&" : "?") + "view=tasks", {waitUntil: "domcontentloaded"});
await opener.waitFor();
}
if (width === 1512) {
await opener.click();
await drawer.waitFor();
Expand Down Expand Up @@ -237,7 +290,7 @@ try {
await drawer.waitFor({ state: "hidden" });
await page.waitForFunction(() => document.activeElement?.classList.contains("personal-task-board"));
assert.equal(
await page.locator(".personal-task-board").evaluate(element => document.activeElement === element),
await page.locator('[data-goal-panel="tasks"]:not([hidden]) .personal-task-board').evaluate(element => document.activeElement === element),
true,
"A removed Task opener falls back to the Task list instead of another Task",
);
Expand Down
3 changes: 3 additions & 0 deletions apps/presentation/dashboard/smoke/team-report-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ const raw = renderToStaticMarkup(createElement(TeamArtifactContent, {artifact, l
assert.doesNotMatch(raw, /<table>|<a /); assert.match(raw, /\| Measure \| Value \|/);
const chat = renderToStaticMarkup(createElement(MarkdownText, {text}));
assert.match(chat, /<table>/, "A conversation renders the same safe report table as an artifact");
assert.match(chat, /<h2[^>]*class="personal-md-heading is-h1"[^>]*>Research<\/h2>/,
"A document heading is navigable semantic HTML beneath the page title");
assert.doesNotMatch(chat, /<h1\b/, "Embedded documents do not create a second page title");
assert.doesNotMatch(chat, /<script|<img|href="javascript:/);
assert.match(chat, /&lt;script&gt;/);
const malformed = renderToStaticMarkup(createElement(MarkdownText, {text: "| One | Two |\n|---|\n| kept | intact |"}));
Expand Down
Loading
Loading