From 92558ee61d7514e99184300b62364361fada482b Mon Sep 17 00:00:00 2001
From: Jesse_Chen
Date: Sat, 22 Aug 2026 09:57:06 +0800
Subject: [PATCH] fix(web): stack mobile audit tables and restore sidebar
hierarchy
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Narrow screens were wrapping 已执行 into a glyph column and painting every sidebar row the same gray, so history looked like top-level chrome.
Co-authored-by: Cursor
---
frontend/DESIGN.md | 8 +-
frontend/src/app/globals.css | 194 ++++++++++++++----
frontend/src/app/page.tsx | 4 +-
frontend/src/components/app-sidebar.tsx | 29 ++-
.../tests/consultation-entrypoint.test.ts | 18 +-
.../consultation-technique-audit.test.ts | 3 +
frontend/tests/sidebar-contract.test.ts | 23 +++
7 files changed, 217 insertions(+), 62 deletions(-)
diff --git a/frontend/DESIGN.md b/frontend/DESIGN.md
index 95d1c06f..a57d443f 100644
--- a/frontend/DESIGN.md
+++ b/frontend/DESIGN.md
@@ -142,6 +142,7 @@ The base unit is 4px. Tokens are `--space-1: 4px`, `--space-2: 8px`, `--space-3:
- **Structure:** title, optional metadata, current-state marker.
- **States:** default, hover, current, focus, disabled.
+- **Hierarchy:** section labels stay tertiary; session titles and primary actions use ink so history rows do not collapse into the same gray as “收藏对话 / 历史对话”.
- **Request behavior:** existing sessions remain selectable for reading while a request is active; creating or sending another request stays locked until the active request settles.
- **Surface:** translucent warm-gray sidebar; current uses a white glass surface and deep-brown marker.
@@ -153,7 +154,7 @@ The base unit is 4px. Tokens are `--space-1: 4px`, `--space-2: 8px`, `--space-3:
- **Tablet:** 64px collapsed by default from 768px through 1023px; 240px when expanded.
- **Mobile:** no icon rail; an off-canvas drawer uses `min(86vw, 320px)` and closes through its scrim or Escape.
- **Collapsed content:** logo, new-chat action, reports action, one history expansion action, and account avatar. Individual sessions do not become indistinguishable repeated icons.
-- **Expanded content:** new chat, my reports, chart list, favorites, then history. Empty untitled sessions stay off the history list. Pin is favorite; archive stays in the session menu.
+- **Expanded content:** new chat, my reports, chart list, favorites, then history. Nested lists indent under their section labels. Actions, section labels, and session rows share 18px icons, caption/body type, and ink/muted tokens. Empty untitled sessions stay off the history list. Pin is favorite; archive stays in the session menu, not as a history-header toggle.
- **Scroll ownership:** header and footer remain fixed; `SidebarContent` is the sole sidebar scroll owner.
- **Scrollbar:** `SidebarContent`, ordinary session `.conversation`, and the rectification house board use a quiet overlay scrollbar: transparent track, no `scrollbar-gutter`, and a 4px warm thumb mixed from `--color-ink`. The thumb stays transparent until hover or keyboard focus inside the scroller, then uses `color-mix(in srgb, var(--color-ink) 26%, transparent)`; thumb hover uses 40%. Increased contrast keeps the thumb visible; forced colors restore the system scrollbar.
- **Motion:** Sidebar state changes are immediate on desktop, tablet, and mobile. The 44px trigger keeps one stable 18px sidebar glyph and never enters an intermediate scale or opacity state.
@@ -165,6 +166,7 @@ The base unit is 4px. Tokens are `--space-1: 4px`, `--space-2: 8px`, `--space-3:
- **Variants:** assistant editorial text on canvas; user text on warm card surface; streaming; error. Streaming uses a timeline of completed steps plus the current step; the thinking body expands while streaming, collapses when answer text appears, and is stored with the assistant message.
- **Identity:** every assistant message carries the 32px Jyotisha logo avatar; user messages stay visually lighter and avatar-free.
- **Typography:** assistant body 16px with serif subheadings; user body 14px.
+- **Tables:** three-column technique audit tables keep 状态 on one line. Below 768px they stack each row as title + status, then the note, instead of squeezing 已执行 into a vertical glyph column.
- **Follow-up:** the latest settled consultation answer may offer two or three grounded next questions under that answer. Clicking one sends it in the current session. The composer never hosts suggestion chips. If the answer does not support a grounded continuation, nothing is shown.
- **Motion:** new messages enter with a short opacity/translate transition only.
@@ -177,10 +179,10 @@ The base unit is 4px. Tokens are `--space-1: 4px`, `--space-2: 8px`, `--space-3:
### Product entrypoint card
-- **Structure:** the homepage daily-reading and birth-time cards are single native-button targets stretched across their article surface. Content remains semantic card copy; a compact action label and arrow sit at the lower right.
+- **Structure:** the homepage daily-reading and birth-time cards are single native-button targets stretched across their article surface. Content remains semantic card copy. On two-column viewports a compact action label and arrow sit at the trailing edge; on a stacked homepage they sit under the supporting line, leading-aligned, so a short label like “深入看今日” does not float to the opposite corner from “开始新的生时校正”.
- **Copy:** chat history titles daily readings as “8月22日 · 今日节奏”, topic cards as “事业 · …”, and rectification as “生时校正 · 8月21日”. Clicking the daily card starts a consultation session immediately; it does not place the question in the composer. Private model instructions are selected by a closed entrypoint identifier and expanded only on the server.
- **States:** default, whole-card hover, pressed, focus-visible, and disabled. The card surface—not an inner promotional button—carries the interaction feedback.
-- **Responsive:** cards stack below 768px without introducing a large nested button; the footer keeps supporting copy flexible and the action label on one line.
+- **Responsive:** cards stack below 900px without introducing a large nested button. Stacked cards hug their copy instead of stretching to a min-height; the footer stacks supporting copy above a leading-aligned one-line action.
- **Accessibility:** each card exposes exactly one native button with a descriptive accessible name, preserves a visible focus ring, and meets the full-card touch target.
### Start greeting
diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css
index c849f6cf..900c86c5 100644
--- a/frontend/src/app/globals.css
+++ b/frontend/src/app/globals.css
@@ -151,8 +151,8 @@ button:disabled { cursor: default; opacity: .45; }
.app-loading-orbit::after { position: absolute; top: -3px; left: 50%; width: 6px; height: 6px; margin-left: -3px; border-radius: 50%; background: var(--color-action); box-shadow: 0 0 0 3px var(--color-canvas); content: ""; }
.app-loading-actions { display: flex; gap: 10px; margin-top: 12px; }
.brand-mark, .welcome-mark, .auth-brand span { width: 28px; height: 28px; display: block; flex: 0 0 auto; background: url("/jyotish-logo.png") center / contain no-repeat; font-size: 0; }
-.new-chat svg { width: 18px; height: 18px; }
-.session-nav { min-height: 0; flex: 1; display: flex; flex-direction: column; }
+.session-nav { min-height: 0; flex: 0 0 auto; }
+.session-nav:last-of-type { min-height: 0; flex: 1 1 auto; display: flex; flex-direction: column; }
.chevron { width: 18px; height: 18px; color: var(--color-ink-tertiary); }
.chat-header strong, .chat-header span { display: block; }
.credit-button span { display: inline-flex; align-items: center; margin: 0; color: inherit; font-size: inherit; line-height: 1; }
@@ -228,7 +228,8 @@ button:disabled { cursor: default; opacity: .45; }
border-bottom: 1px solid color-mix(in srgb, var(--color-border) 72%, transparent);
vertical-align: top;
text-align: start;
- overflow-wrap: anywhere;
+ overflow-wrap: break-word;
+ word-break: keep-all;
}
.markdown-table thead th {
color: var(--color-ink-secondary);
@@ -239,6 +240,11 @@ button:disabled { cursor: default; opacity: .45; }
.markdown-table tbody th {
font-weight: 550;
}
+.markdown-table table:has(thead th:nth-child(3):last-child) td:nth-child(2) {
+ width: 4.5em;
+ color: var(--color-ink-secondary);
+ white-space: nowrap;
+}
.markdown-table tr:last-child th,
.markdown-table tr:last-child td { border-bottom: 0; }
.agent-activity-status,
@@ -446,8 +452,8 @@ button:disabled { cursor: default; opacity: .45; }
.group\/sidebar-provider[data-viewport] { height: 100vh; height: 100dvh; min-height: 0; overflow: hidden; }
.chat-app { width: 100%; height: 100%; min-height: 0; overflow: hidden; display: grid; background: var(--color-canvas); grid-template-columns: minmax(0, 1fr); }
.sidebar { position: relative; width: 100%; height: 100%; min-width: 0; min-height: 0; overflow: hidden; display: flex; flex-direction: column; border-right: 1px solid var(--sidebar-border); color: var(--sidebar-foreground); padding: var(--space-5) var(--space-3) 0; background: var(--sidebar-background); backdrop-filter: saturate(130%) blur(20px); }
-[data-sidebar="header"] { flex: 0 0 auto; }
-[data-sidebar="content"] { min-height: 0; flex: 1 1 auto; overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; }
+[data-sidebar="header"] { flex: 0 0 auto; padding-bottom: var(--space-3); }
+[data-sidebar="content"] { min-height: 0; flex: 1 1 auto; display: flex; flex-direction: column; gap: var(--space-3); overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; }
[data-sidebar="content"],
.conversation,
.rectification-board__body {
@@ -496,59 +502,73 @@ button:disabled { cursor: default; opacity: .45; }
.brand-row strong { font-weight: 400; }
.brand-mark, .auth-brand span { width: 32px; height: 32px; border-radius: 50%; background: var(--color-canvas) url("/jyotish-logo.png") center / contain no-repeat; box-shadow: 0 0 0 1px oklch(0 0 0 / .1); }
.auth-story-brand img { width: 32px; height: 32px; border-radius: 50%; object-fit: contain; box-shadow: 0 0 0 1px oklch(0 0 0 / .1); }
-.new-chat { width: 100%; min-height: 44px; display: flex; align-items: center; justify-content: center; gap: var(--space-2); padding: 0 var(--space-3); border: 0; background: transparent; color: var(--sidebar-foreground); cursor: pointer; font-size: var(--type-body-sm); transition: background-color 120ms ease-out, transform 120ms ease-out; margin: 0; border-radius: var(--radius-md); font-weight: 500; }
-.report-nav-button { width: 100%; min-height: 44px; display: flex; align-items: center; justify-content: center; gap: var(--space-2); padding: 0 var(--space-3); margin-bottom: var(--space-2); border: 0; border-radius: var(--radius-md); color: var(--sidebar-foreground); }
+.new-chat { width: 100%; min-height: 44px; display: flex; align-items: center; justify-content: center; gap: var(--space-2); padding: 0 var(--space-3); border: 0; background: transparent; color: var(--sidebar-foreground); cursor: pointer; font-size: var(--type-body-sm); line-height: 1.35; transition: background-color 120ms ease-out, transform 120ms ease-out; margin: 0; border-radius: var(--radius-md); font-weight: 500; }
+.report-nav-button { width: 100%; min-height: 44px; display: flex; align-items: center; justify-content: center; gap: var(--space-2); padding: 0 var(--space-3); margin: 0; border: 0; border-radius: var(--radius-md); background: transparent; color: var(--sidebar-foreground); cursor: pointer; font-size: var(--type-body-sm); font-weight: 500; line-height: 1.35; transition: background-color 120ms ease-out, transform 120ms ease-out; }
.report-nav-button:hover,
.new-chat:hover { background: var(--sidebar-accent); color: var(--sidebar-accent-foreground); }
[data-state="expanded"] .new-chat,
[data-state="expanded"] .report-nav-button { justify-content: flex-start; }
-.session-nav-header { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--space-2); padding: 0 var(--space-3) var(--space-2); }
-.session-nav-header-inline { padding-top: 0; }
-.sidebar-section { margin: 0 0 var(--space-3); }
+.new-chat svg,
+.report-nav-button svg,
+.sidebar-label > svg,
+.sidebar-section-summary > svg,
+.session-nav-toggle svg {
+ width: 18px;
+ height: 18px;
+ flex: 0 0 auto;
+ stroke-width: 1.75;
+}
+.session-nav-header { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--space-2); padding: 0 var(--space-3); }
+.sidebar-section { margin: 0; }
+.sidebar-label,
.sidebar-section-summary {
display: flex;
min-height: 44px;
align-items: center;
gap: var(--space-2);
+ color: var(--color-ink-tertiary);
+ font-size: var(--type-caption);
+ font-weight: 500;
+ letter-spacing: 0;
+ line-height: 1.35;
+}
+.sidebar-section-summary {
padding: 0 var(--space-3);
list-style: none;
- color: var(--sidebar-muted-foreground);
cursor: pointer;
- font-size: var(--type-overline);
- font-weight: 500;
- letter-spacing: 1.5px;
}
.sidebar-section-summary::-webkit-details-marker { display: none; }
-.sidebar-section-summary > svg { width: 16px; height: 16px; flex: 0 0 auto; }
-.sidebar-section-chevron { margin-left: auto; width: 16px; height: 16px; transition: transform 120ms ease-out; }
+.sidebar-section-summary > .sidebar-section-chevron { margin-left: auto; width: 16px; height: 16px; flex: 0 0 auto; color: currentColor; opacity: .72; transition: transform 120ms ease-out; }
.sidebar-section[open] .sidebar-section-chevron { transform: rotate(180deg); }
-.sidebar-empty { margin: 0; padding: 0 var(--space-3) var(--space-2); color: var(--color-ink-tertiary); font-size: var(--type-caption); }
-.chart-nav-list { display: flex; flex-wrap: wrap; gap: var(--space-2); padding: 0 var(--space-3) var(--space-2); }
+.sidebar-nested { padding: 0 var(--space-3) var(--space-1) calc(var(--space-3) + 18px + var(--space-2)); }
+.sidebar-empty { margin: 0; padding: 0 0 var(--space-2); color: var(--color-ink-tertiary); font-size: var(--type-caption); line-height: 1.35; }
+.chart-nav-list { display: flex; flex-wrap: wrap; gap: var(--space-2); padding: 0 0 var(--space-2); }
.chart-nav-chip {
min-height: 32px;
max-width: 100%;
- padding: 0 12px;
+ padding: 0 var(--space-3);
border: 1px solid var(--sidebar-border);
border-radius: 999px;
background: var(--color-canvas);
color: var(--sidebar-foreground);
cursor: pointer;
font-size: var(--type-caption);
+ font-weight: 400;
+ line-height: 1.35;
}
-.chart-nav-chip:hover { background: var(--sidebar-accent); }
-.sidebar-label { min-height: 44px; display: flex; align-items: center; gap: var(--space-2); color: var(--sidebar-muted-foreground); font-size: var(--type-overline); font-weight: 500; letter-spacing: 1.5px; }
+.chart-nav-chip:hover { background: var(--sidebar-accent); color: var(--sidebar-accent-foreground); }
.sidebar-label:focus-visible { outline: 3px solid color-mix(in srgb, var(--sidebar-ring) 56%, transparent); outline-offset: 2px; border-radius: var(--radius-sm); }
-.session-nav-toggle { min-height: 44px; min-width: 44px; display: inline-flex; align-items: center; justify-content: center; padding: 0 var(--space-2); border: 0; border-radius: var(--radius-md); background: transparent; color: var(--sidebar-muted-foreground); cursor: pointer; font-size: var(--type-overline); transition: background-color 120ms ease-out, color 120ms ease-out; }
-.session-nav-toggle svg { width: 16px; height: 16px; }
+.session-nav-toggle { min-height: 44px; min-width: 44px; display: inline-flex; align-items: center; justify-content: center; padding: 0; border: 0; border-radius: var(--radius-md); background: transparent; color: var(--color-ink-tertiary); cursor: pointer; font-size: var(--type-caption); transition: background-color 120ms ease-out, color 120ms ease-out; }
+.session-nav-toggle:hover { background: var(--sidebar-accent); color: var(--sidebar-foreground); }
.session-list { min-height: 0; display: flex; flex-direction: column; gap: var(--space-1); }
-.session-row { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) 44px; align-items: center; border-radius: var(--radius-lg); color: var(--sidebar-muted-foreground); transition: background-color 120ms ease-out, box-shadow 120ms ease-out, color 120ms ease-out; }
+.session-row { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) 44px; align-items: center; border-radius: var(--radius-lg); color: var(--sidebar-foreground); transition: background-color 120ms ease-out, box-shadow 120ms ease-out, color 120ms ease-out; }
.session-row:hover, .session-row:focus-within { background: var(--sidebar-accent); color: var(--sidebar-accent-foreground); }
.session-row:has(.session-main[data-active="true"]) { background: var(--sidebar-accent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sidebar-ring) 22%, transparent); color: var(--sidebar-accent-foreground); }
-.session-main { position: relative; width: 100%; min-height: 44px; display: grid; gap: 2px; padding: var(--space-2) var(--space-3) var(--space-2) var(--space-4); border: 0; border-radius: var(--radius-md); background: transparent; color: inherit; cursor: pointer; text-align: left; transition: background-color 120ms ease-out, color 120ms ease-out, transform 120ms ease-out; }
+.session-main { position: relative; width: 100%; min-height: 44px; display: grid; gap: 2px; padding: var(--space-2); border: 0; border-radius: var(--radius-md); background: transparent; color: inherit; cursor: pointer; text-align: left; transition: background-color 120ms ease-out, color 120ms ease-out, transform 120ms ease-out; }
.session-main[data-active="true"] { color: var(--sidebar-accent-foreground); background: transparent; }
-.session-main[data-active="true"]::before { position: absolute; border-radius: 3px; content: ""; top: var(--space-3); bottom: var(--space-3); left: var(--space-1); width: 2px; background: var(--sidebar-ring); }
-.session-title { min-width: 0; display: flex; align-items: center; gap: var(--space-1); overflow: hidden; line-height: 1.35; font-size: var(--type-body-sm); font-weight: 500; }
-.session-title > svg { width: var(--space-3); height: var(--space-3); flex: 0 0 auto; color: var(--sidebar-ring); }
+.session-main[data-active="true"]::before { position: absolute; border-radius: 3px; content: ""; top: var(--space-3); bottom: var(--space-3); left: 0; width: 2px; background: var(--sidebar-ring); }
+.session-title { min-width: 0; display: flex; align-items: center; gap: var(--space-1); overflow: hidden; line-height: 1.35; font-size: var(--type-caption); font-weight: 500; }
+.session-title > svg { width: 14px; height: 14px; flex: 0 0 auto; color: currentColor; }
.session-main small { color: var(--color-ink-tertiary); line-height: 1.3; font-size: var(--type-overline); }
.session-menu-trigger { width: 44px; height: 44px; display: grid; place-items: center; justify-self: center; padding: 0; border: 0; border-radius: 0; background: transparent; color: inherit; cursor: pointer; opacity: .64; transition: background-color 120ms ease-out, color 120ms ease-out, opacity 120ms ease-out, transform 120ms ease-out; }
.session-menu-trigger > svg { width: 18px; height: 18px; }
@@ -998,7 +1018,8 @@ button:disabled { cursor: default; opacity: .45; }
border-bottom: 1px solid color-mix(in srgb, var(--color-border) 64%, transparent);
vertical-align: top;
text-align: start;
- overflow-wrap: anywhere;
+ overflow-wrap: break-word;
+ word-break: keep-all;
}
.technique-audit-panel thead th {
color: var(--color-ink-tertiary);
@@ -1019,6 +1040,67 @@ button:disabled { cursor: default; opacity: .45; }
.technique-audit-panel td:last-child { color: var(--color-ink-secondary); }
.technique-audit-panel tr:last-child th,
.technique-audit-panel tr:last-child td { border-bottom: 0; }
+
+@media (max-width: 767px) {
+ .markdown-table table:has(thead th:nth-child(3):last-child) thead,
+ .technique-audit-panel thead {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ overflow: hidden;
+ clip: rect(0 0 0 0);
+ white-space: nowrap;
+ }
+
+ .markdown-table table:has(thead th:nth-child(3):last-child) tbody tr,
+ .technique-audit-panel tbody tr {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ gap: var(--space-1) var(--space-3);
+ padding: var(--space-3) 0;
+ border-bottom: 1px solid color-mix(in srgb, var(--color-border) 64%, transparent);
+ }
+
+ .markdown-table table:has(thead th:nth-child(3):last-child) tbody th,
+ .markdown-table table:has(thead th:nth-child(3):last-child) tbody td,
+ .technique-audit-panel tbody th,
+ .technique-audit-panel tbody td {
+ padding: 0;
+ border: 0;
+ width: auto;
+ }
+
+ .markdown-table table:has(thead th:nth-child(3):last-child) tbody td:nth-child(1),
+ .technique-audit-panel tbody th {
+ grid-column: 1;
+ grid-row: 1;
+ color: var(--color-ink);
+ font-weight: 550;
+ }
+
+ .markdown-table table:has(thead th:nth-child(3):last-child) tbody td:nth-child(2),
+ .technique-audit-panel td[data-status] {
+ grid-column: 2;
+ grid-row: 1;
+ width: auto;
+ color: var(--color-ink-secondary);
+ white-space: nowrap;
+ }
+
+ .markdown-table table:has(thead th:nth-child(3):last-child) tbody td:nth-child(3),
+ .technique-audit-panel td:last-child {
+ grid-column: 1 / -1;
+ grid-row: 2;
+ color: var(--color-ink-secondary);
+ font-size: var(--type-caption);
+ line-height: 1.5;
+ }
+
+ .markdown-table table:has(thead th:nth-child(3):last-child) tbody tr:last-child,
+ .technique-audit-panel tbody tr:last-child {
+ border-bottom: 0;
+ }
+}
.error-message { margin: 12px 0 0; padding: 12px 14px; border-left: 3px solid var(--color-danger); background: var(--color-danger-muted); color: var(--color-danger); font-size: 13px; line-height: 1.6; border-color: var(--color-danger); border-radius: 0 var(--radius-md) var(--radius-md) 0; }
.composer-wrap { position: sticky; bottom: 0; z-index: 2; min-width: 0; border-top: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent); padding: var(--space-3) var(--space-6) var(--space-4); background: var(--color-frosted); backdrop-filter: saturate(130%) blur(20px); }
@@ -1507,6 +1589,10 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
flex: 1;
}
+.product-entrypoint-footer small:empty {
+ display: none;
+}
+
.product-entrypoint-action {
display: inline-flex;
flex: 0 0 auto;
@@ -1657,11 +1743,28 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
grid-template-columns: 1fr;
}
+ .daily-starlanguage-card,
+ .birth-rectification-card {
+ min-height: 0;
+ grid-template-rows: auto auto;
+ }
+
.birth-rectification-card {
border-top: 1px solid var(--color-border);
border-left: 0;
}
+ .product-entrypoint-footer {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: var(--space-3);
+ }
+
+ .product-entrypoint-action {
+ margin-left: 0;
+ justify-content: flex-start;
+ }
+
.starter-theme-accordion {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
@@ -1698,8 +1801,10 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
.daily-starlanguage-card,
.birth-rectification-card {
- min-height: 210px;
- padding: var(--space-6);
+ min-height: 0;
+ grid-template-rows: auto auto;
+ gap: var(--space-4);
+ padding: var(--space-5);
}
.birth-rectification-card {
@@ -1707,6 +1812,24 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
border-left: 0;
}
+ .product-entrypoint-copy h2 {
+ font-size: var(--type-title-md);
+ }
+
+ .product-entrypoint-footer {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: var(--space-3);
+ padding-top: var(--space-4);
+ }
+
+ .product-entrypoint-action {
+ margin-left: 0;
+ justify-content: flex-start;
+ font-size: var(--type-body-sm);
+ font-weight: 500;
+ }
+
.starter-section-heading {
display: grid;
gap: var(--space-1);
@@ -1731,15 +1854,6 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
padding: var(--space-6) var(--space-5);
}
- .product-entrypoint-footer {
- display: grid;
- gap: var(--space-4);
- }
-
- .product-entrypoint-action {
- justify-self: start;
- }
-
.starter-content span {
font-size: var(--type-title-sm);
}
diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx
index 4054467a..32cd6de1 100644
--- a/frontend/src/app/page.tsx
+++ b/frontend/src/app/page.tsx
@@ -3866,7 +3866,9 @@ export default function Home() {
>{natalMinuteAvailable ? dailyStarlanguageTrend : "看看今天的整体节奏、适合推进的事和需要留意的地方。"}
-
{natalMinuteAvailable ? dailyStarlanguageAction : "不支持的个人判断会明确说明,不会补造出生时间。"}
+ {natalMinuteAvailable
+ ? (dailyStarlanguageAction ?
{dailyStarlanguageAction} : null)
+ :
不支持的个人判断会明确说明,不会补造出生时间。}
{natalMinuteAvailable ? dailyStarlanguageQuestion : "查看今日运势"}
diff --git a/frontend/src/components/app-sidebar.tsx b/frontend/src/components/app-sidebar.tsx
index 7de544e2..d167c0b2 100644
--- a/frontend/src/components/app-sidebar.tsx
+++ b/frontend/src/components/app-sidebar.tsx
@@ -168,7 +168,7 @@ export function AppSidebar({
disabled={newChatDisabled}
onClick={handleNewChat}
>
-
+
{showExpandedContent ? {creatingSession ? "正在创建" : "新建对话"} : null}
@@ -179,7 +179,7 @@ export function AppSidebar({
tooltip="我的报告"
onClick={handleOpenReports}
>
-
+
{showExpandedContent ? 我的报告 : null}
@@ -192,7 +192,7 @@ export function AppSidebar({
-
+
星盘列表
-
+
{charts.length === 0 ? 还没有星盘
: (
{charts.map((chart) => (
@@ -225,11 +225,11 @@ export function AppSidebar({
-
+
收藏对话
-
+
-
+
{favoriteSessions.length === 0 ? 还没有收藏
: (
{favoriteSessions.map((session, index) => renderSession(session, index))}
@@ -242,16 +242,11 @@ export function AppSidebar({
-
+
{sessionControls.showingArchived ? "归档记录" : "历史对话"}
-
+
-
-
-
-
+
{historySessions.length === 0 ? 暂无对话
: (
{historySessions.map((session, index) => renderSession(session, favoriteSessions.length + index))}
@@ -270,7 +265,7 @@ export function AppSidebar({
aria-label="聊天记录"
onClick={handleExpandHistory}
>
-
+
diff --git a/frontend/tests/consultation-entrypoint.test.ts b/frontend/tests/consultation-entrypoint.test.ts
index 4772689a..66cf21b0 100644
--- a/frontend/tests/consultation-entrypoint.test.ts
+++ b/frontend/tests/consultation-entrypoint.test.ts
@@ -352,7 +352,7 @@ test("the starter heading is drawn per visit and the rectification card carries
assert.match(source, /useState\(\(\) => Math\.random\(\)\)/);
assert.match(source, /starterGreetingUnseen\.current = true;\s*\n\s*setStarterGreetingSelection\(Math\.random\(\)\)/);
- // Then: the rectification card's footer is the action alone, still flush right.
+ // Then: the rectification card's footer is the action alone, still flush right on two-column layouts.
const rectificationCard = source.slice(
source.indexOf('", source.indexOf(' {
+ const styles = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
+ const phoneCards = styles.match(
+ /\.starter-hero h1 \{\s*font-size: clamp\(38px[\s\S]*?\.daily-starlanguage-card,\s*\.birth-rectification-card \{([^}]*)\}[\s\S]*?\.product-entrypoint-footer \{([^}]*)\}[\s\S]*?\.product-entrypoint-action \{([^}]*)\}/,
+ );
+
+ assert.ok(phoneCards);
+ assert.match(phoneCards[1], /min-height:\s*0;/);
+ assert.match(phoneCards[1], /grid-template-rows:\s*auto auto;/);
+ assert.doesNotMatch(phoneCards[1], /min-height:\s*210px/);
+ assert.match(phoneCards[2], /flex-direction:\s*column;/);
+ assert.match(phoneCards[2], /align-items:\s*flex-start;/);
+ assert.match(phoneCards[3], /margin-left:\s*0;/);
+ assert.match(phoneCards[3], /justify-content:\s*flex-start;/);
+});
+
test("starter homepage keeps its editorial display typography", () => {
const styles = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
const heroHeading = styles.match(/\.starter-hero h1\s*\{([^}]*)\}/)?.[1] ?? "";
diff --git a/frontend/tests/consultation-technique-audit.test.ts b/frontend/tests/consultation-technique-audit.test.ts
index 8696624f..bd3a2350 100644
--- a/frontend/tests/consultation-technique-audit.test.ts
+++ b/frontend/tests/consultation-technique-audit.test.ts
@@ -110,4 +110,7 @@ test("the folded audit control stays closed and never remounts the internal pane
assert.match(globalStyles, /\.markdown-table/);
assert.match(globalStyles, /\.technique-audit >/);
assert.match(globalStyles, /\.markdown-table th,/);
+ assert.match(globalStyles, /word-break:\s*keep-all/);
+ assert.match(globalStyles, /\.markdown-table table:has\(thead th:nth-child\(3\):last-child\) td:nth-child\(2\)/);
+ assert.match(globalStyles, /@media \(max-width: 767px\) \{[\s\S]*\.markdown-table table:has\(thead th:nth-child\(3\):last-child\) tbody tr/);
});
diff --git a/frontend/tests/sidebar-contract.test.ts b/frontend/tests/sidebar-contract.test.ts
index 6c63e3e7..710cfe1e 100644
--- a/frontend/tests/sidebar-contract.test.ts
+++ b/frontend/tests/sidebar-contract.test.ts
@@ -305,6 +305,29 @@ test("styles the session history scrollbar as a quiet overlay", () => {
assert.match(readProjectFile("DESIGN.md"), /ordinary session/);
});
+test("nests sidebar lists under one heading scale without an archive toggle", () => {
+ const appSidebar = readProjectFile("src/components/app-sidebar.tsx");
+ assert.doesNotMatch(appSidebar, /归档 \$\{/);
+ assert.doesNotMatch(appSidebar, /onToggleArchivedView/);
+ assert.match(appSidebar, /className="sidebar-nested"/);
+ assert.match(cssBlock(".sidebar-nested"), /calc\(var\(--space-3\) \+ 18px \+ var\(--space-2\)\)/);
+ assert.match(cssBlock(".session-title"), /font-size:\s*var\(--type-caption\)/);
+ assert.match(cssBlock(".session-title"), /font-weight:\s*500/);
+ assert.match(cssBlock(".session-row"), /color:\s*var\(--sidebar-foreground\)/);
+ assert.match(cssBlock(".sidebar-label"), /color:\s*var\(--color-ink-tertiary\)/);
+ assert.match(cssBlock(".sidebar-section-summary"), /color:\s*var\(--color-ink-tertiary\)/);
+ assert.match(cssBlock(".sidebar-label"), /font-size:\s*var\(--type-caption\)/);
+ assert.match(cssBlock(".sidebar-section-summary"), /font-size:\s*var\(--type-caption\)/);
+ assert.match(cssBlock(".new-chat"), /font-size:\s*var\(--type-body-sm\)/);
+ assert.match(cssBlock(".report-nav-button"), /font-size:\s*var\(--type-body-sm\)/);
+ assert.match(cssBlock(".new-chat svg"), /width:\s*18px/);
+ assert.match(cssBlock(".report-nav-button svg"), /width:\s*18px/);
+ assert.match(cssBlock(".sidebar-label > svg"), /width:\s*18px/);
+ assert.match(cssBlock(".sidebar-section-summary > svg"), /width:\s*18px/);
+ assert.match(cssBlock(".session-title > svg"), /color:\s*currentColor/);
+ assert.doesNotMatch(appSidebar, /session-nav-header-inline/);
+});
+
test("renders each session title and menu as one unified row surface", () => {
assert.match(cssBlock(".session-row"), /grid-template-columns:\s*minmax\(0,\s*1fr\)\s+44px/);
assert.match(globalStyles, /\.session-row:has\(\.session-main\[data-active="true"\]\)[^{]*\{[^}]*background:\s*var\(--sidebar-accent\)/);