-
${message.role === "user" ? "我" : escapeHtml(selected?.name || "问师")}
+ ${message.role === "user" ? "我" : escapeHtml(selected?.name || "问师")} · ${escapeHtml(mentorMessageTime(message))}
${message.role === "assistant"
? (message.content ? formatMentorAnswer(message.content) : '
@@ -300,7 +385,7 @@ function renderMentorMessages() {
`).join("");
}
- document.querySelector("#mentorQuickPrompts").hidden = state.mentorMessages.length > 0 || state.mentorLoading;
+ document.querySelector("#mentorQuickPrompts").hidden = false;
document.querySelector("#clearMentorChatButton").disabled = !state.mentorMessages.length || state.mentorLoading;
document.querySelector("#mentorQuestion").disabled = state.mentorLoading || !state.selectedMentorId;
document.querySelector("#sendMentorQuestion").disabled = state.mentorLoading || !state.selectedMentorId;
@@ -342,6 +427,7 @@ async function sendMentorQuestion(event) {
const responseMessage = { role: "assistant", content: "", streaming: true, meta: "", followUps: [] };
state.mentorMessages.push(responseMessage);
input.value = "";
+ syncMentorComposerHeight();
state.mentorLoading = true;
state.mentorController = new AbortController();
hideMentorNotice();
@@ -431,6 +517,7 @@ async function streamMentorRequest(body, signal, onDelta, onMeta) {
function useMentorQuickPrompt(prompt) {
const input = document.querySelector("#mentorQuestion");
input.value = prompt || "";
+ syncMentorComposerHeight();
input.focus();
}
@@ -529,19 +616,21 @@ function bindMentorEvents() {
document.querySelector("#stopMentorQuestion").addEventListener("click", stopMentorGeneration);
document.querySelector("#clearMentorChatButton").addEventListener("click", clearMentorConversation);
document.querySelector("#mentorSortToggle").addEventListener("click", toggleMentorSortMode);
+ document.querySelector("#mentorFilterToggle").addEventListener("click", toggleMentorFilterMenu);
document.querySelector("#mentorSearchInput").addEventListener("input", (event) => {
state.mentorQuery = event.target.value.trim().toLocaleLowerCase("zh-CN");
renderMentorDirectory();
});
document.querySelectorAll("[data-mentor-grade]").forEach((button) => {
button.addEventListener("click", () => {
- state.mentorGrade = button.dataset.mentorGrade || "all";
- document.querySelectorAll("[data-mentor-grade]").forEach((item) => {
- item.classList.toggle("active", item === button);
- });
- renderMentorDirectory();
+ selectMentorGrade(button.dataset.mentorGrade);
+ closeMentorFilterMenu();
});
});
+ document.addEventListener("click", (event) => {
+ if (event.target.closest(".mentor-filter-menu")) return;
+ closeMentorFilterMenu();
+ });
document.querySelectorAll("[data-mentor-prompt]").forEach((button) => {
button.addEventListener("click", () => useMentorQuickPrompt(button.dataset.mentorPrompt));
});
@@ -550,4 +639,70 @@ function bindMentorEvents() {
event.preventDefault();
document.querySelector("#mentorChatForm").requestSubmit();
});
+ document.querySelector("#mentorQuestion").addEventListener("input", syncMentorComposerHeight);
+ document.querySelector("#mentorNoteButton").addEventListener("click", openMentorNoteDialog);
+ document.querySelector("#mentorProfileButton").addEventListener("click", openMentorProfileDialog);
+ document.querySelector("#mentorPinButton").addEventListener("click", () => toggleMentorPin(state.selectedMentorId));
+ document.querySelector("#mentorNoteInput").addEventListener("input", saveMentorNote);
+ document.querySelectorAll("[data-mentor-dialog-close]").forEach((button) => {
+ button.addEventListener("click", () => closeMentorDialog(button.dataset.mentorDialogClose));
+ });
+ window.addEventListener("resize", centerOpenMentorDialogs);
+}
+
+function selectedMentor() {
+ return (state.mentorSetup?.mentors || []).find((item) => item.id === state.selectedMentorId) || null;
+}
+
+function syncMentorComposerHeight() {
+ const input = document.querySelector("#mentorQuestion");
+ if (!input) return;
+ input.style.height = "0";
+ const nextHeight = Math.min(input.scrollHeight, 168);
+ input.style.height = `${nextHeight}px`;
+ input.style.overflowY = nextHeight >= 168 ? "auto" : "hidden";
+}
+
+function openMentorDialog(dialogId) {
+ const dialog = document.querySelector(`#${dialogId}`);
+ if (!(dialog instanceof HTMLDialogElement)) return;
+ if (!dialog.open) dialog.showModal();
+ positionMentorDialog(dialog);
+}
+
+function closeMentorDialog(dialogId) {
+ const dialog = document.querySelector(`#${dialogId}`);
+ if (dialog instanceof HTMLDialogElement && dialog.open) dialog.close();
+}
+
+function openMentorNoteDialog() {
+ const input = document.querySelector("#mentorNoteInput");
+ if (input) input.value = loadMentorNote(selectedMentor());
+ openMentorDialog("mentorNoteDialog");
+ const noteInput = document.querySelector("#mentorNoteInput");
+ if (noteInput) requestAnimationFrame(() => noteInput.focus());
+}
+
+function openMentorProfileDialog() {
+ populateMentorDialogs(selectedMentor());
+ openMentorDialog("mentorProfileDialog");
+}
+
+function positionMentorDialog(dialog) {
+ if (!(dialog instanceof HTMLDialogElement) || !dialog.open) return;
+ const viewportPadding = 12;
+ const rect = dialog.getBoundingClientRect();
+ const width = Math.min(400, window.innerWidth - viewportPadding * 2);
+ const height = Math.min(rect.height, window.innerHeight - viewportPadding * 2);
+ dialog.style.top = `${Math.max(viewportPadding, Math.round((window.innerHeight - height) / 2))}px`;
+ dialog.style.left = `${Math.max(viewportPadding, Math.round((window.innerWidth - width) / 2))}px`;
+ dialog.style.right = "auto";
+ dialog.style.bottom = "auto";
+ dialog.style.margin = "0";
+}
+
+function centerOpenMentorDialogs() {
+ document.querySelectorAll("#mentorNoteDialog, #mentorProfileDialog").forEach((dialog) => {
+ if (dialog instanceof HTMLDialogElement && dialog.open) positionMentorDialog(dialog);
+ });
}
diff --git a/app/frontend/shared/shell.css b/app/frontend/shared/shell.css
index fe99b7c..7ef18ff 100644
--- a/app/frontend/shared/shell.css
+++ b/app/frontend/shared/shell.css
@@ -3222,3 +3222,127 @@ body.sidebar-collapsed .status-bar {
display: none;
}
}
+
+/* Dedicated mentor workspace (final day/night spec). Only the active mentor
+ view enables this immersive state; other pages keep the canonical shell. */
+.app-page-context {
+ min-width: 0;
+ flex: 1 1 auto;
+ flex-direction: column;
+ justify-content: center;
+ gap: 2px;
+ display: none;
+}
+
+#themeModeText {
+ display: none;
+}
+
+.app-page-context strong {
+ overflow: hidden;
+ color: var(--text-primary);
+ font-size: 18px;
+ font-weight: var(--font-weight-semibold);
+ line-height: 1.3;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.app-page-context span {
+ overflow: hidden;
+ color: var(--text-secondary);
+ font-size: 13px;
+ line-height: 1.4;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+@media (min-width: 1024px) {
+ body[data-active-view="mentorView"] .main {
+ width: 100%;
+ min-width: 0;
+ margin-left: 0;
+ height: 100dvh;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ }
+
+ body[data-active-view="mentorView"] .module-nav {
+ display: none;
+ }
+
+ body[data-active-view="mentorView"] .status-bar {
+ display: none;
+ }
+
+ body[data-active-view="mentorView"] .market-tape,
+ body[data-active-view="mentorView"] .mobile-page-context {
+ display: none;
+ }
+
+ body[data-active-view="mentorView"] .overview-strip {
+ display: none;
+ }
+
+ body[data-active-view="mentorView"] .app-header {
+ flex: 0 0 56px;
+ height: 56px;
+ min-height: 56px;
+ gap: 24px;
+ padding: 0 20px;
+ background: #F5F6F7;
+ }
+
+ :root[data-theme="dark"] body[data-active-view="mentorView"] .app-header {
+ background: #141519;
+ }
+
+ body[data-active-view="mentorView"] .app-page-context {
+ display: flex;
+ }
+
+ body[data-active-view="mentorView"] .header-actions > :is(.global-search-button, .alert-button, .assistant-button, .header-menu-button) {
+ display: none;
+ }
+
+ body[data-active-view="mentorView"] .theme-toggle {
+ width: auto;
+ height: 32px;
+ min-height: 32px;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 0 12px;
+ border-radius: var(--radius-md);
+ background: transparent;
+ color: var(--text-secondary);
+ font-size: var(--font-size-label);
+ font-weight: var(--font-weight-medium);
+ }
+
+ body[data-active-view="mentorView"] .theme-toggle .lucide {
+ width: 16px;
+ height: 16px;
+ }
+
+ body[data-active-view="mentorView"] #themeModeText {
+ display: inline-block;
+ }
+
+ body[data-active-view="mentorView"] .app-main {
+ flex: 1 1 auto;
+ height: calc(100dvh - 56px);
+ min-height: 0;
+ display: flex;
+ flex-direction: column;
+ padding: 0;
+ overflow: hidden;
+ }
+
+ body[data-active-view="mentorView"] .workspace-view.active-view {
+ flex: 1 1 auto;
+ min-height: 0;
+ overflow: hidden;
+ }
+}
diff --git a/app/frontend/shared/shell.js b/app/frontend/shared/shell.js
index 5b2ccb2..0d2cc6c 100644
--- a/app/frontend/shared/shell.js
+++ b/app/frontend/shared/shell.js
@@ -60,6 +60,7 @@
const page = registry.get(viewId);
const navigationId = page?.navigation_alias || viewId;
const marketView = page?.group === "market";
+ const previousActiveView = document.body.dataset.activeView;
document.body.dataset.activeView = viewId;
const mobileGroup = document.querySelector("#mobilePageGroup");
const mobileTitle = document.querySelector("#mobilePageTitle");
@@ -69,6 +70,10 @@
: page?.group === "personal" ? "复盘" : "工具";
}
if (mobileTitle) mobileTitle.textContent = page?.title || "小白复盘";
+ const pageTitle = document.querySelector("#currentPageTitle");
+ if (pageTitle) pageTitle.textContent = page?.title || "小白复盘";
+ const pageSubtitle = document.querySelector("#currentPageSubtitle");
+ if (pageSubtitle && previousActiveView !== viewId) pageSubtitle.textContent = "";
document.querySelectorAll(".module-tab").forEach((button) => {
button.classList.toggle("active", button.dataset.view === navigationId);
button.classList.toggle(
diff --git a/app/frontend/shared/theme.js b/app/frontend/shared/theme.js
index 2a73cc5..9e745d0 100644
--- a/app/frontend/shared/theme.js
+++ b/app/frontend/shared/theme.js
@@ -13,6 +13,8 @@ function syncThemeControl() {
button.setAttribute("aria-label", label);
button.setAttribute("aria-pressed", String(dark));
button.querySelector("i")?.setAttribute("data-lucide", dark ? "sun" : "moon");
+ const modeText = document.querySelector("#themeModeText");
+ if (modeText) modeText.textContent = dark ? "夜间模式" : "日间模式";
}
function clearThemeTransitionEffects() {
diff --git a/app/frontend/shared/tokens.css b/app/frontend/shared/tokens.css
index 26eeb14..7174c4a 100644
--- a/app/frontend/shared/tokens.css
+++ b/app/frontend/shared/tokens.css
@@ -219,13 +219,12 @@
--dragon-profile-weight-strong: 750;
--dragon-profile-weight-semibold: 600;
- --mentor-directory-width: 280px;
- --mentor-profile-width: 272px;
- --mentor-pane-header-height: 58px;
- --mentor-avatar-size: 38px;
+ --mentor-directory-width: 300px;
+ --mentor-pane-header-height: 60px;
+ --mentor-avatar-size: 42px;
--mentor-profile-avatar-size: 68px;
--mentor-composer-min-height: 94px;
- --mentor-message-max-width: 82%;
+ --mentor-message-max-width: 60%;
--chart-background: #fbfcfd;
--chart-grid: #e2e8ec;
diff --git a/app/tests/e2e/app-shell.spec.js b/app/tests/e2e/app-shell.spec.js
index ab86c81..d85829b 100644
--- a/app/tests/e2e/app-shell.spec.js
+++ b/app/tests/e2e/app-shell.spec.js
@@ -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 }) => {
diff --git a/app/tests/test_frontend_contract.py b/app/tests/test_frontend_contract.py
index e14b9a0..703c889 100644
--- a/app/tests/test_frontend_contract.py
+++ b/app/tests/test_frontend_contract.py
@@ -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)
正在读取复盘数据并推演...
') : escapeHtml(message.content)}