feat: restore mentor page to final day/night spec
Rebuild the mentor workspace per the approved final design (day.png/night.png): - mentorView gets a dedicated full-width immersive shell (independent 56px top bar with page title slot + theme mode text, hidden module-nav/market tape/overview/status bar) scoped to body[data-active-view=mentorView]. - Assistant messages become borderless body text with name/time above; only user messages use a blue bubble; keep loading/error/streaming caret states. - 4 quick topics stay visible with history; composer restored to a framed ~94px card with bottom-left shortcut hint and bottom-right send button. - Directory tools merged into one row (search + filter menu + sort); the filter menu still offers all/A/B/C; list selected state is an inset rounded fill; contact rows are borderless 75px items. - Chat header always shows pin/note/profile/clear; pin reuses /api/mentors/ preferences; theme toggle stays the single #themeToggle. - Night tokens match the spec including the two distinct blues (#316FEF link, #5B8DEF quote/selected icon). Mobile (<768) stacks panels with no horizontal overflow; 1024+ follows the desktop spec. - Update stale test baselines (300px sidebar, 94px composer, hint presence, night bubble color, centered disclaimer) and regenerate the architecture inventory. Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
co-authored by
multica-agent
parent
d95bcb523a
commit
d239519299
+102
-22
@@ -526,7 +526,7 @@ test("admin shell opens every primary workspace and global search", async ({ pag
|
||||
const views = [
|
||||
"auctionView", "sentimentCycleView", "limitPool", "brokenView", "downView", "yesterdayView",
|
||||
"performanceView", "ladderView", "rotationView", "themeLibraryView", "popularityView", "dragonView", "screenerView",
|
||||
"mentorView", "heavenView", "reviewWorkspaceView",
|
||||
"heavenView", "reviewWorkspaceView", "mentorView",
|
||||
];
|
||||
for (const view of views) {
|
||||
await page.locator(`[data-view="${view}"]`).first().click();
|
||||
@@ -567,7 +567,7 @@ test("every primary workspace shares the canonical desktop shell geometry", asyn
|
||||
const views = [
|
||||
"auctionView", "sentimentCycleView", "limitPool", "brokenView", "downView", "yesterdayView",
|
||||
"performanceView", "ladderView", "rotationView", "themeLibraryView", "popularityView", "dragonView",
|
||||
"screenerView", "mentorView", "heavenView", "reviewWorkspaceView",
|
||||
"screenerView", "heavenView", "reviewWorkspaceView",
|
||||
];
|
||||
let reference = null;
|
||||
for (const view of views) {
|
||||
@@ -2805,7 +2805,8 @@ test("mentor directory exposes evidence filters and private owner metadata", asy
|
||||
await page.goto("/index.html");
|
||||
await page.locator('[data-view="mentorView"]').first().click();
|
||||
|
||||
await expect(page.locator("#mentorView .mentor-sidebar .mentor-evidence-filters")).toBeVisible();
|
||||
await expect(page.locator("#mentorView .mentor-sidebar .mentor-directory-tools")).toBeVisible();
|
||||
await expect(page.locator("#mentorFilterToggle")).toBeVisible();
|
||||
await expect(page.locator("#mentorList .mentor-option")).toHaveCount(22);
|
||||
await expect(page.locator('#mentorList [data-mentor-id="private-owner"] .mentor-badge.private')).toContainText("仅自己");
|
||||
await expect(page.locator('#mentorList [data-mentor-id="source-a"] .mentor-badge.grade-a')).toHaveText("A");
|
||||
@@ -2815,36 +2816,58 @@ test("mentor directory exposes evidence filters and private owner metadata", asy
|
||||
await expect(page.locator("#mentorList .mentor-option")).toHaveCount(1);
|
||||
await expect(page.locator("#mentorCount")).toHaveText("1 / 22 位");
|
||||
await page.locator("#mentorSearchInput").fill("");
|
||||
await page.locator("#mentorFilterToggle").click();
|
||||
await page.locator('[data-mentor-grade="B"]').click();
|
||||
await expect(page.locator("#mentorFilterLabel")).toHaveText("B级");
|
||||
await expect(page.locator("#mentorList .mentor-option")).toHaveCount(7);
|
||||
await page.locator('#mentorList [data-mentor-id="source-b"]').click();
|
||||
await expect(page.locator("#activeMentorName")).toHaveText("多源老师");
|
||||
await expect(page.locator("#activeMentorBadges")).toHaveText("B");
|
||||
await expect(page.locator("#activeMentorEvidence")).toHaveText("公开访谈与多源材料");
|
||||
const mentorLibrary = await page.locator("#mentorView .mentor-sidebar").boundingBox();
|
||||
const mentorChat = await page.locator("#mentorView .mentor-chat-panel").boundingBox();
|
||||
const mentorProfile = await page.locator("#mentorView .mentor-profile-panel").boundingBox();
|
||||
const mentorInput = await page.locator("#mentorQuestion").boundingBox();
|
||||
expect(Math.abs(mentorLibrary.width - 280)).toBeLessThanOrEqual(1);
|
||||
expect(Math.abs(mentorProfile.width - 272)).toBeLessThanOrEqual(1);
|
||||
const mentorLayout = await page.locator("#mentorView .mentor-layout").boundingBox();
|
||||
expect(Math.abs(mentorLibrary.width - 300)).toBeLessThanOrEqual(1);
|
||||
expect(Math.abs(mentorChat.x - (mentorLibrary.x + mentorLibrary.width))).toBeLessThanOrEqual(1);
|
||||
expect(Math.abs(mentorProfile.x - (mentorChat.x + mentorChat.width))).toBeLessThanOrEqual(1);
|
||||
expect(mentorInput.height).toBeGreaterThanOrEqual(48);
|
||||
expect(Math.abs(mentorChat.x + mentorChat.width - (mentorLayout.x + mentorLayout.width))).toBeLessThanOrEqual(1);
|
||||
const composerForm = await page.locator("#mentorView .mentor-chat-form").boundingBox();
|
||||
expect(composerForm.height).toBeGreaterThanOrEqual(88);
|
||||
expect(composerForm.height).toBeLessThanOrEqual(104);
|
||||
expect(mentorInput.height).toBeGreaterThanOrEqual(24);
|
||||
expect(mentorInput.height).toBeLessThanOrEqual(34);
|
||||
expect(await page.evaluate(() => document.documentElement.scrollHeight - window.innerHeight)).toBeLessThanOrEqual(1);
|
||||
await page.setViewportSize({ width: 1920, height: 947 });
|
||||
const expandedMentorLayout = await page.locator("#mentorView .mentor-layout").boundingBox();
|
||||
const statusBar = await page.locator(".status-bar").boundingBox();
|
||||
const lowerGap = statusBar.y - (expandedMentorLayout.y + expandedMentorLayout.height);
|
||||
expect(lowerGap).toBeGreaterThanOrEqual(0);
|
||||
expect(lowerGap).toBeLessThanOrEqual(16);
|
||||
await page.locator("#overviewToggle").click();
|
||||
const openOverviewLayout = await page.locator("#mentorView .mentor-layout").boundingBox();
|
||||
const openOverviewGap = statusBar.y - (openOverviewLayout.y + openOverviewLayout.height);
|
||||
expect(openOverviewGap).toBeGreaterThanOrEqual(0);
|
||||
expect(openOverviewGap).toBeLessThanOrEqual(16);
|
||||
const footerBar = await page.locator("#mentorView .mentor-workspace-footer").boundingBox();
|
||||
expect(Math.abs(footerBar.y + footerBar.height - 947)).toBeLessThanOrEqual(1);
|
||||
expect(Math.abs(expandedMentorLayout.y + expandedMentorLayout.height - footerBar.y)).toBeLessThanOrEqual(1);
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth)).toBeLessThanOrEqual(1);
|
||||
expect(await page.evaluate(() => document.documentElement.scrollHeight - window.innerHeight)).toBeLessThanOrEqual(1);
|
||||
});
|
||||
|
||||
test("mentor keeps two columns and opens the profile floating dialog on desktop", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
await mockApplication(page, session("admin", true));
|
||||
await page.goto("/index.html");
|
||||
await page.locator('[data-view="mentorView"]').first().click();
|
||||
await page.locator('#mentorList [data-mentor-id="source-b"]').click();
|
||||
|
||||
await expect(page.locator("#mentorView .mentor-profile-panel")).toHaveCount(0);
|
||||
const mentorLayout = await page.locator("#mentorView .mentor-layout").boundingBox();
|
||||
const mentorLibrary = await page.locator("#mentorView .mentor-sidebar").boundingBox();
|
||||
const mentorChat = await page.locator("#mentorView .mentor-chat-panel").boundingBox();
|
||||
expect(Math.abs(mentorChat.x - (mentorLibrary.x + mentorLibrary.width))).toBeLessThanOrEqual(1);
|
||||
expect(Math.abs(mentorChat.x + mentorChat.width - (mentorLayout.x + mentorLayout.width))).toBeLessThanOrEqual(1);
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth)).toBeLessThanOrEqual(1);
|
||||
|
||||
await page.locator("#mentorProfileButton").click();
|
||||
await expect(page.locator("#mentorProfileDialog")).toBeVisible();
|
||||
await expect(page.locator("#mentorProfileDialogName")).toHaveText("多源老师");
|
||||
await expect(page.locator("#mentorProfileDialogEvidence")).toHaveText("公开访谈与多源材料");
|
||||
await page.locator('[data-mentor-dialog-close="mentorProfileDialog"]').click();
|
||||
await expect(page.locator("#mentorProfileDialog")).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("mentor pins, custom order and streamed replies work together", async ({ page }) => {
|
||||
await mockApplication(page, session("admin", true));
|
||||
await page.goto("/index.html");
|
||||
@@ -2883,8 +2906,8 @@ test("mentor pins, custom order and streamed replies work together", async ({ pa
|
||||
}));
|
||||
expect(darkUserMessageStyle.background).not.toBe("rgb(255, 255, 255)");
|
||||
expect(darkUserMessageStyle.border).not.toBe("rgb(255, 255, 255)");
|
||||
expect(darkUserMessageStyle.contentBackground).toBe("rgb(35, 54, 74)");
|
||||
expect(darkUserMessageStyle.contentColor).toBe("rgb(232, 234, 237)");
|
||||
expect(darkUserMessageStyle.contentBackground).toBe("rgb(53, 89, 140)");
|
||||
expect(darkUserMessageStyle.contentColor).toBe("rgb(234, 241, 251)");
|
||||
const darkMessageStyle = await answer.evaluate((element) => {
|
||||
const style = getComputedStyle(element);
|
||||
const content = element.querySelector(".mentor-message-content");
|
||||
@@ -2906,8 +2929,65 @@ test("mentor pins, custom order and streamed replies work together", async ({ pa
|
||||
expect(darkMessageStyle.shadow).toBe("none");
|
||||
expect(darkMessageStyle.contentColor).toBe("rgb(232, 234, 237)");
|
||||
expect(darkMessageStyle.headingColor).toBe("rgb(232, 234, 237)");
|
||||
expect(darkMessageStyle.labelColor).toBe("rgb(127, 137, 147)");
|
||||
expect(darkMessageStyle.metaColor).toBe("rgb(127, 137, 147)");
|
||||
expect(darkMessageStyle.labelColor).toBe("rgb(124, 130, 138)");
|
||||
expect(darkMessageStyle.metaColor).toBe("rgb(124, 130, 138)");
|
||||
});
|
||||
|
||||
test("mentor floating dialogs open centered, save notes per account, and keep composer one-line", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
await mockApplication(page, session("admin", true));
|
||||
await page.goto("/index.html");
|
||||
await page.locator('[data-view="mentorView"]').first().click();
|
||||
await page.locator('#mentorList [data-mentor-id="source-b"]').click();
|
||||
|
||||
await page.locator("#mentorNoteButton").click();
|
||||
await expect(page.locator("#mentorNoteDialog")).toBeVisible();
|
||||
const noteDialog = await page.locator("#mentorNoteDialog").boundingBox();
|
||||
expect(noteDialog.width).toBeLessThanOrEqual(400);
|
||||
expect(Math.abs(noteDialog.x + noteDialog.width / 2 - 720)).toBeLessThanOrEqual(3);
|
||||
expect(Math.abs(noteDialog.y + noteDialog.height / 2 - 450)).toBeLessThanOrEqual(3);
|
||||
await page.locator("#mentorNoteInput").fill("多源老师:确认后再行动");
|
||||
await expect(page.locator("#mentorNoteInput")).toHaveValue("多源老师:确认后再行动");
|
||||
await page.locator('[data-mentor-dialog-close="mentorNoteDialog"]').click();
|
||||
await expect(page.locator("#mentorNoteDialog")).not.toBeVisible();
|
||||
|
||||
await page.locator("#mentorNoteButton").click();
|
||||
await expect(page.locator("#mentorNoteInput")).toHaveValue("多源老师:确认后再行动");
|
||||
await page.locator('[data-mentor-dialog-close="mentorNoteDialog"]').click();
|
||||
|
||||
await page.locator("#mentorProfileButton").click();
|
||||
await expect(page.locator("#mentorProfileDialog")).toBeVisible();
|
||||
await expect(page.locator("#mentorProfileDialogName")).toHaveText("多源老师");
|
||||
await expect(page.locator("#mentorProfileDialogEvidence")).toHaveText("公开访谈与多源材料");
|
||||
const profileDialog = await page.locator("#mentorProfileDialog").boundingBox();
|
||||
expect(profileDialog.width).toBeLessThanOrEqual(400);
|
||||
expect(Math.abs(profileDialog.x + profileDialog.width / 2 - 720)).toBeLessThanOrEqual(3);
|
||||
await page.locator('[data-mentor-dialog-close="mentorProfileDialog"]').click();
|
||||
await expect(page.locator("#mentorProfileDialog")).not.toBeVisible();
|
||||
|
||||
const disclaimerStyle = await page.locator("#mentorView .mentor-disclaimer").evaluate((el) => ({
|
||||
align: getComputedStyle(el).textAlign,
|
||||
}));
|
||||
expect(disclaimerStyle.align).toBe("center");
|
||||
|
||||
const composerHint = page.locator("#mentorView .mentor-composer-hint");
|
||||
await expect(composerHint).toHaveCount(1);
|
||||
await expect(composerHint).toBeVisible();
|
||||
|
||||
const composerInput = page.locator("#mentorQuestion");
|
||||
const oneLineHeight = (await composerInput.boundingBox()).height;
|
||||
expect(oneLineHeight).toBeGreaterThanOrEqual(24);
|
||||
expect(oneLineHeight).toBeLessThanOrEqual(34);
|
||||
await composerInput.fill("第一行\n第二行");
|
||||
const grownHeight = (await composerInput.boundingBox()).height;
|
||||
expect(grownHeight).toBeGreaterThan(oneLineHeight + 8);
|
||||
await composerInput.press("Shift+Enter");
|
||||
await composerInput.type("第三行");
|
||||
await composerInput.fill("只发送这一行");
|
||||
await page.locator("#sendMentorQuestion").click();
|
||||
await expect(page.locator("#mentorMessages .mentor-message.user").last()).toContainText("只发送这一行");
|
||||
const resetHeight = (await composerInput.boundingBox()).height;
|
||||
expect(resetHeight).toBeLessThanOrEqual(34);
|
||||
});
|
||||
|
||||
test("global dialogs share the stage 18 geometry without changing account or admin access", async ({ page }) => {
|
||||
|
||||
@@ -280,13 +280,18 @@ class FrontendContractTests(unittest.TestCase):
|
||||
self.assertNotIn('context.fillStyle = "#6c7983";', chart)
|
||||
|
||||
def test_dark_mentor_tokens_and_sentiment_bottom_clearance_are_defined(self):
|
||||
self.assertIn(":root[data-theme=\"dark\"] #mentorView {", self.mentor_styles)
|
||||
self.assertIn("--mentor-ink: var(--text-primary);", self.mentor_styles)
|
||||
self.assertIn("--mentor-sub: var(--text-secondary);", self.mentor_styles)
|
||||
self.assertIn(":root[data-theme=\"dark\"] #mentorView .mentor-message {", self.mentor_styles)
|
||||
self.assertIn("border-color: var(--line-soft);", self.mentor_styles)
|
||||
self.assertIn("background: var(--surface-subtle);", self.mentor_styles)
|
||||
self.assertIn("box-shadow: none;", self.mentor_styles)
|
||||
self.assertIn(":root[data-theme=\"dark\"] #mentorView.workspace-view {", self.mentor_styles)
|
||||
self.assertIn("--qp-bg-chat: #232529;", self.mentor_styles)
|
||||
self.assertIn("--qp-bg-bubble-self: #35598C;", self.mentor_styles)
|
||||
self.assertIn("--qp-bg-selected: #2B3B58;", self.mentor_styles)
|
||||
self.assertIn("--qp-link: #316FEF;", self.mentor_styles)
|
||||
self.assertIn("--qp-accent-soft: #5B8DEF;", self.mentor_styles)
|
||||
self.assertIn("--mentor-directory-width: 300px;", self.tokens)
|
||||
self.assertIn("--mentor-composer-min-height: 94px;", self.tokens)
|
||||
self.assertIn("#mentorView .mentor-message.user .mentor-message-content {", self.mentor_styles)
|
||||
self.assertIn("background: var(--qp-bg-bubble-self);", self.mentor_styles)
|
||||
self.assertIn("#mentorView .mentor-quick-prompts button {", self.mentor_styles)
|
||||
self.assertIn("#mentorView .mentor-composer-hint {", self.mentor_styles)
|
||||
self.assertIn("#sentimentCycleView .sentiment-history-frame {", self.sentiment_styles)
|
||||
self.assertIn("margin-bottom: var(--card-gap);", self.sentiment_styles)
|
||||
self.assertIn("padding-bottom: var(--card-gap);", self.sentiment_styles)
|
||||
|
||||
Reference in New Issue
Block a user