Compare commits

..
11 changed files with 424 additions and 153 deletions
+1
View File
@@ -3,6 +3,7 @@ from __future__ import annotations
import copy import copy
import json import json
import re import re
import secrets
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from typing import Any from typing import Any
+8 -8
View File
@@ -309,13 +309,13 @@
"code_hotspots": [ "code_hotspots": [
{ {
"path": "frontend/styles/styles.css", "path": "frontend/styles/styles.css",
"bytes": 361776, "bytes": 360238,
"lines": 15465 "lines": 15385
}, },
{ {
"path": "frontend/styles/redesign-v2.css", "path": "frontend/styles/redesign-v2.css",
"bytes": 263539, "bytes": 262113,
"lines": 8570 "lines": 8533
}, },
{ {
"path": "frontend/index.html", "path": "frontend/index.html",
@@ -344,8 +344,8 @@
}, },
{ {
"path": "frontend/styles/renovation.css", "path": "frontend/styles/renovation.css",
"bytes": 83949, "bytes": 83812,
"lines": 1553 "lines": 1550
}, },
{ {
"path": "frontend/pages/heaven/page.css", "path": "frontend/pages/heaven/page.css",
@@ -354,8 +354,8 @@
}, },
{ {
"path": "backend/features/heaven/service.py", "path": "backend/features/heaven/service.py",
"bytes": 63123, "bytes": 63138,
"lines": 1303 "lines": 1304
}, },
{ {
"path": "backend/features/market/insights.py", "path": "backend/features/market/insights.py",
-10
View File
@@ -771,7 +771,6 @@ tbody tr.clickable{cursor:pointer}
[data-active-view="auctionView"], [data-active-view="auctionView"],
[data-active-view="themeLibraryView"], [data-active-view="themeLibraryView"],
[data-active-view="popularityView"], [data-active-view="popularityView"],
[data-active-view="dragonView"],
[data-active-view="mentorView"], [data-active-view="mentorView"],
[data-active-view="rotationView"] [data-active-view="rotationView"]
) .app-main{ ) .app-main{
@@ -786,7 +785,6 @@ tbody tr.clickable{cursor:pointer}
[data-active-view="auctionView"], [data-active-view="auctionView"],
[data-active-view="themeLibraryView"], [data-active-view="themeLibraryView"],
[data-active-view="popularityView"], [data-active-view="popularityView"],
[data-active-view="dragonView"],
[data-active-view="mentorView"], [data-active-view="mentorView"],
[data-active-view="rotationView"] [data-active-view="rotationView"]
) .overview-strip{flex:0 0 auto} ) .overview-strip{flex:0 0 auto}
@@ -795,7 +793,6 @@ tbody tr.clickable{cursor:pointer}
[data-active-view="auctionView"], [data-active-view="auctionView"],
[data-active-view="themeLibraryView"], [data-active-view="themeLibraryView"],
[data-active-view="popularityView"], [data-active-view="popularityView"],
[data-active-view="dragonView"],
[data-active-view="mentorView"], [data-active-view="mentorView"],
[data-active-view="rotationView"] [data-active-view="rotationView"]
) .workspace-view.active-view{ ) .workspace-view.active-view{
@@ -807,7 +804,6 @@ tbody tr.clickable{cursor:pointer}
#auctionView.active-view, #auctionView.active-view,
#themeLibraryView.active-view, #themeLibraryView.active-view,
#popularityView.active-view, #popularityView.active-view,
#dragonView.active-view,
#mentorView.active-view{display:flex;flex-direction:column} #mentorView.active-view{display:flex;flex-direction:column}
#rotationView.active-view{ #rotationView.active-view{
@@ -827,7 +823,6 @@ tbody tr.clickable{cursor:pointer}
#themeLibraryView .theme-summary-v2, #themeLibraryView .theme-summary-v2,
#popularityView .popularity-page-head-v2, #popularityView .popularity-page-head-v2,
#popularityView .popularity-glance-v2, #popularityView .popularity-glance-v2,
#dragonView .dragon-page-head-v2,
#mentorView .mentor-page-header, #mentorView .mentor-page-header,
#mentorView .member-gate, #mentorView .member-gate,
#mentorView #mentorNotice{flex:0 0 auto} #mentorView #mentorNotice{flex:0 0 auto}
@@ -835,7 +830,6 @@ tbody tr.clickable{cursor:pointer}
#auctionView .auction-workspace-v2, #auctionView .auction-workspace-v2,
#themeLibraryView .theme-library-workspace-v2, #themeLibraryView .theme-library-workspace-v2,
#popularityView .popularity-table-card-v2, #popularityView .popularity-table-card-v2,
#dragonView .dragon-daily-content-v2,
#mentorView .mentor-layout{min-height:0;flex:1 1 auto} #mentorView .mentor-layout{min-height:0;flex:1 1 auto}
#auctionView .auction-workspace-v2{height:100%;grid-template-columns:minmax(0,1fr) var(--right-rail-wide);grid-template-rows:minmax(0,1fr);align-items:stretch;overflow:hidden} #auctionView .auction-workspace-v2{height:100%;grid-template-columns:minmax(0,1fr) var(--right-rail-wide);grid-template-rows:minmax(0,1fr);align-items:stretch;overflow:hidden}
@@ -853,10 +847,6 @@ tbody tr.clickable{cursor:pointer}
#popularityView .popularity-table-card-v2{display:flex;flex-direction:column;overflow:hidden} #popularityView .popularity-table-card-v2{display:flex;flex-direction:column;overflow:hidden}
#popularityView .popularity-table-frame-v2{min-height:0;flex:1 1 auto;overflow:auto} #popularityView .popularity-table-frame-v2{min-height:0;flex:1 1 auto;overflow:auto}
#dragonView .dragon-daily-content-v2{overflow:hidden}
#dragonView .dragon-trader-detail-v2{min-height:0}
#dragonView .dragon-trader-detail .trader-operations{min-height:0;overflow:auto}
#mentorView .mentor-layout{height:auto;overflow:hidden} #mentorView .mentor-layout{height:auto;overflow:hidden}
#mentorView .mentor-sidebar, #mentorView .mentor-sidebar,
#mentorView .mentor-chat-panel, #mentorView .mentor-chat-panel,
+10 -47
View File
@@ -751,9 +751,6 @@ body.sidebar-collapsed .status-bar { left: 64px; }
min-height: 50px; min-height: 50px;
padding: 6px 8px; padding: 6px 8px;
} }
.market-tape { display: none; }
.header-actions { width: 100%; }
.header-command-group { position: absolute; }
.module-nav, .module-nav,
body.sidebar-collapsed .module-nav { body.sidebar-collapsed .module-nav {
inset: auto 0 0; inset: auto 0 0;
@@ -769,14 +766,6 @@ body.sidebar-collapsed .status-bar { left: 64px; }
border: 0; border: 0;
border-top: 1px solid var(--r2-line); border-top: 1px solid var(--r2-line);
} }
.sidebar-brand,
.module-nav .nav-group-label,
.sidebar-collapse-button,
.module-nav .market-sub-tab { display: none; }
.module-nav .nav-group,
body.sidebar-collapsed .module-nav .nav-group { display: contents; }
.module-nav .module-tab,
body.sidebar-collapsed .module-nav .module-tab { display: none; }
.module-nav .module-tab.mobile-primary-tab, .module-nav .module-tab.mobile-primary-tab,
body.sidebar-collapsed .module-nav .module-tab.mobile-primary-tab { body.sidebar-collapsed .module-nav .module-tab.mobile-primary-tab {
min-height: 54px; min-height: 54px;
@@ -789,12 +778,8 @@ body.sidebar-collapsed .status-bar { left: 64px; }
padding: 3px 2px; padding: 3px 2px;
font-size: 10px; font-size: 10px;
} }
.module-nav .module-tab.mobile-primary-tab span { display: inline; }
.app-main { width: 100%; margin: 0; padding: 0 8px 16px; overflow: visible; } .app-main { width: 100%; margin: 0; padding: 0 8px 16px; overflow: visible; }
.overview-strip { margin-inline: -8px; padding-inline: 8px; overflow-x: auto; } .overview-strip { margin-inline: -8px; padding-inline: 8px; overflow-x: auto; }
.overview-strip .metric:nth-of-type(n + 4),
.overview-strip .metric-wide { display: none; }
.overview-toggle { display: none; }
.redesigned-page-head { align-items: flex-start; flex-wrap: wrap; margin-top: 12px; } .redesigned-page-head { align-items: flex-start; flex-wrap: wrap; margin-top: 12px; }
.redesigned-page-head .section-title-group { width: 100%; flex-wrap: wrap; } .redesigned-page-head .section-title-group { width: 100%; flex-wrap: wrap; }
.redesigned-page-head .toolbar-controls { width: 100%; margin-left: 0; justify-content: space-between; } .redesigned-page-head .toolbar-controls { width: 100%; margin-left: 0; justify-content: space-between; }
@@ -4318,7 +4303,6 @@ body.sidebar-collapsed .status-bar { left: 64px; }
#dragonView .dragon-operation-table .dragon-col-direction { width: 72px; } #dragonView .dragon-operation-table .dragon-col-direction { width: 72px; }
#dragonView .dragon-operation-table .dragon-col-number { width: 82px; } #dragonView .dragon-operation-table .dragon-col-number { width: 82px; }
#dragonView .dragon-operation-table .dragon-col-seat { width: 190px; } #dragonView .dragon-operation-table .dragon-col-seat { width: 190px; }
#dragonView .dragon-operation-table .dragon-col-reason { width: auto; }
#dragonView .dragon-operation-table :is(th, td).row-number { padding-inline: 8px; text-align: center; } #dragonView .dragon-operation-table :is(th, td).row-number { padding-inline: 8px; text-align: center; }
#dragonView .dragon-operation-table td.stock-code { font-variant-numeric: tabular-nums; } #dragonView .dragon-operation-table td.stock-code { font-variant-numeric: tabular-nums; }
#dragonView .dragon-operation-table thead th { position: sticky; top: 0; z-index: 2; background: #fafbfc; } #dragonView .dragon-operation-table thead th { position: sticky; top: 0; z-index: 2; background: #fafbfc; }
@@ -4366,17 +4350,11 @@ body.sidebar-collapsed .status-bar { left: 64px; }
.dragon-empty-actions-v2 .lucide { width: 15px; height: 15px; } .dragon-empty-actions-v2 .lucide { width: 15px; height: 15px; }
@media (min-width: 721px) { @media (min-width: 721px) {
body[data-active-view="dragonView"] .app-main { display: flex; flex-direction: column; overflow: hidden; } body[data-active-view="dragonView"] .app-main { height: var(--workspace-height); min-height: 0; display: block; overflow: auto; }
body[data-active-view="dragonView"] .overview-strip { flex: 0 0 auto; }
body[data-active-view="dragonView"] #dragonView.active-view { body[data-active-view="dragonView"] #dragonView.active-view {
min-height: 0; display: block;
flex: 1 1 auto; overflow: visible;
display: flex;
flex-direction: column;
} }
body[data-active-view="dragonView"] .dragon-page-head-v2 { flex: 0 0 auto; }
body[data-active-view="dragonView"] .dragon-daily-content-v2,
body[data-active-view="dragonView"] .dragon-empty-state-v2 { flex: 1 1 auto; }
} }
@media (min-width: 721px) and (max-height: 900px) { @media (min-width: 721px) and (max-height: 900px) {
@@ -8388,40 +8366,25 @@ body.sidebar-collapsed .status-bar { left: 64px; }
white-space: nowrap; white-space: nowrap;
} }
/* The Dragon-Tiger page stays still; only the selected trader's operations scroll. */ /* The Dragon-Tiger page owns vertical scrolling; wide operation tables scroll horizontally. */
@media (min-width: 721px) { @media (min-width: 721px) {
body[data-active-view="dragonView"] .dragon-daily-content-v2 { body[data-active-view="dragonView"] .dragon-daily-content-v2 {
min-height: 0; display: block;
display: grid; overflow: visible;
grid-template-rows: auto auto auto minmax(150px, 1fr) auto;
overflow: hidden;
}
body[data-active-view="dragonView"] #dragonView .dragon-card-stage-v2 {
flex: 0 0 auto;
} }
body[data-active-view="dragonView"] #dragonView .dragon-trader-detail-v2 { body[data-active-view="dragonView"] #dragonView .dragon-trader-detail-v2 {
min-height: 0; display: block;
display: flex;
flex-direction: column;
}
body[data-active-view="dragonView"] #dragonView .dragon-detail-header {
flex: 0 0 auto;
} }
body[data-active-view="dragonView"] #dragonView .dragon-trader-detail .trader-operations { body[data-active-view="dragonView"] #dragonView .dragon-trader-detail .trader-operations {
min-height: 0; max-height: none;
flex: 1 1 auto;
overflow: auto; overflow: auto;
overscroll-behavior: contain;
scrollbar-gutter: stable;
} }
body[data-active-view="dragonView"] #dragonView .dragon-unclassified-v2 { body[data-active-view="dragonView"] #dragonView .dragon-unclassified-v2 {
max-height: 180px; max-height: none;
overflow: auto; overflow: visible;
} }
:is( :is(
-3
View File
@@ -127,7 +127,6 @@ body.sidebar-collapsed .app-main { margin-left: 0; }
background: transparent; background: transparent;
} }
.overview-strip .sentiment-block { padding-left: 0; }
.overview-strip .sentiment-gauge { width: 26px; height: 26px; flex: 0 0 26px; border-width: 2px; font-size: 10px; } .overview-strip .sentiment-gauge { width: 26px; height: 26px; flex: 0 0 26px; border-width: 2px; font-size: 10px; }
.overview-strip .sentiment-text { font-size: 12px; white-space: nowrap; } .overview-strip .sentiment-text { font-size: 12px; white-space: nowrap; }
.overview-strip .metric-label { color: var(--text-tertiary); font-size: 10.5px; white-space: nowrap; } .overview-strip .metric-label { color: var(--text-tertiary); font-size: 10.5px; white-space: nowrap; }
@@ -160,8 +159,6 @@ body.sidebar-collapsed .app-main { margin-left: 0; }
.overview-strip[data-overview-expanded="true"] .metric { min-height: 76px; align-items: flex-start; justify-content: center; flex-direction: column; gap: 4px; } .overview-strip[data-overview-expanded="true"] .metric { min-height: 76px; align-items: flex-start; justify-content: center; flex-direction: column; gap: 4px; }
.overview-strip[data-overview-expanded="true"] .sentiment-block { flex-direction: row; justify-content: flex-start; align-items: center; } .overview-strip[data-overview-expanded="true"] .sentiment-block { flex-direction: row; justify-content: flex-start; align-items: center; }
.overview-strip[data-overview-expanded="true"] .sentiment-gauge { width: 48px; height: 48px; flex-basis: 48px; font-size: 13px; } .overview-strip[data-overview-expanded="true"] .sentiment-gauge { width: 48px; height: 48px; flex-basis: 48px; font-size: 13px; }
.overview-strip[data-overview-expanded="true"] .metric-value { font-size: 18px; }
/* Page frame and shared information architecture. */ /* Page frame and shared information architecture. */
.workspace-view, .workspace-view,
body[data-active-view="screenerView"] .workspace-view, body[data-active-view="screenerView"] .workspace-view,
-80
View File
@@ -666,10 +666,6 @@ time {
box-shadow: var(--shadow-soft); box-shadow: var(--shadow-soft);
} }
.workspace-view.active-view {
display: block;
}
.workspace-view.active-view.view-entering { .workspace-view.active-view.view-entering {
animation: view-enter var(--motion-medium) var(--ease-out) both; animation: view-enter var(--motion-medium) var(--ease-out) both;
} }
@@ -4745,10 +4741,6 @@ textarea {
outline-color: rgba(29, 101, 193, 0.48); outline-color: rgba(29, 101, 193, 0.48);
} }
body.sidebar-collapsed {
grid-template-columns: 64px minmax(0, 1fr);
}
body.sidebar-collapsed .module-nav { body.sidebar-collapsed .module-nav {
width: 64px; width: 64px;
padding-right: 7px; padding-right: 7px;
@@ -4783,10 +4775,6 @@ body.sidebar-collapsed .sidebar-collapse-button .lucide {
} }
@media (min-width: 721px) and (max-width: 1279px) { @media (min-width: 721px) and (max-width: 1279px) {
.market-tape {
display: none;
}
.app-header { .app-header {
grid-template-columns: 190px minmax(0, 1fr) auto; grid-template-columns: 190px minmax(0, 1fr) auto;
} }
@@ -4988,21 +4976,6 @@ body.sidebar-collapsed .sidebar-collapse-button .lucide {
box-shadow: 0 -4px 18px rgba(24, 34, 45, 0.07); box-shadow: 0 -4px 18px rgba(24, 34, 45, 0.07);
} }
.module-nav .nav-brand,
.module-nav .nav-group-label,
.module-nav .market-sub-tab,
.module-nav .sidebar-collapse-button {
display: none;
}
.module-nav .nav-group,
body.sidebar-collapsed .module-nav .nav-group {
display: contents;
margin: 0;
padding: 0;
border: 0;
}
.module-nav .module-tab, .module-nav .module-tab,
body.sidebar-collapsed .module-nav .module-tab { body.sidebar-collapsed .module-nav .module-tab {
min-height: 54px; min-height: 54px;
@@ -5017,11 +4990,6 @@ body.sidebar-collapsed .sidebar-collapse-button .lucide {
text-align: center; text-align: center;
} }
.module-nav .module-tab.mobile-primary-tab,
body.sidebar-collapsed .module-nav .module-tab.mobile-primary-tab {
display: flex;
}
.module-nav .module-tab span, .module-nav .module-tab span,
body.sidebar-collapsed .module-nav .module-tab span { body.sidebar-collapsed .module-nav .module-tab span {
display: block; display: block;
@@ -5369,8 +5337,6 @@ body.sidebar-collapsed .sidebar-collapse-button .lucide {
border-right: 1px solid var(--border); border-right: 1px solid var(--border);
transition: background-color var(--motion-medium) ease, opacity var(--motion-medium) ease; transition: background-color var(--motion-medium) ease, opacity var(--motion-medium) ease;
} }
.rotation-day:last-child { border-right: 0; }
.rotation-day header { display: grid; gap: 2px; margin-bottom: 8px; } .rotation-day header { display: grid; gap: 2px; margin-bottom: 8px; }
.rotation-day header time { font-size: 12px; font-weight: 700; } .rotation-day header time { font-size: 12px; font-weight: 700; }
.rotation-day header span { color: var(--text-secondary); font-size: 10px; } .rotation-day header span { color: var(--text-secondary); font-size: 10px; }
@@ -7130,11 +7096,6 @@ body.sidebar-collapsed .sidebar-collapse-button .lucide {
box-shadow: 1px 0 0 var(--border); box-shadow: 1px 0 0 var(--border);
} }
#screenerView .probability-value strong,
#screenerView .probability-value small {
display: block;
}
#screenerView .probability-value small { #screenerView .probability-value small {
margin-top: 3px; margin-top: 3px;
color: var(--text-secondary); color: var(--text-secondary);
@@ -13032,10 +12993,6 @@ button.account-role-badge:focus-visible { outline: 2px solid var(--blue); outlin
} }
@media (max-width: 720px) { @media (max-width: 720px) {
body.mentor-directory-open {
overflow: hidden;
}
.mentor-layout { .mentor-layout {
height: auto; height: auto;
min-height: 580px; min-height: 580px;
@@ -14889,13 +14846,6 @@ dialog::backdrop {
width: 100%; width: 100%;
} }
body,
body.sidebar-collapsed {
display: block;
min-height: 100dvh;
padding-bottom: calc(68px + env(safe-area-inset-bottom));
}
.app-header { .app-header {
width: 100%; width: 100%;
height: 108px; height: 108px;
@@ -14908,10 +14858,6 @@ dialog::backdrop {
background: rgba(255, 255, 255, .98); background: rgba(255, 255, 255, .98);
} }
.brand-block {
height: 42px;
}
.brand-block .brand-mark, .brand-block .brand-mark,
.brand-block .brand-logo { .brand-block .brand-logo {
width: 34px; width: 34px;
@@ -14927,20 +14873,6 @@ dialog::backdrop {
font-size: 16px; font-size: 16px;
} }
.header-actions {
position: absolute;
inset: 56px 10px auto;
display: flex;
justify-content: space-between;
gap: 6px;
}
.header-date-group {
height: 42px;
min-width: 0;
flex: 1;
}
.header-date-group .date-input { .header-date-group .date-input {
width: 112px; width: 112px;
flex: 1; flex: 1;
@@ -14952,12 +14884,6 @@ dialog::backdrop {
min-width: 30px; min-width: 30px;
} }
.header-actions > .icon-button {
width: 40px;
min-width: 40px;
min-height: 42px;
}
.header-menu-button { .header-menu-button {
display: grid; display: grid;
} }
@@ -15071,9 +14997,7 @@ dialog::backdrop {
font-size: 14px; font-size: 14px;
} }
.overview-strip .metric:nth-of-type(1) { grid-column: 2; grid-row: 1; }
.overview-strip .metric:nth-of-type(2) { grid-column: 3; grid-row: 1; border-right: 0; } .overview-strip .metric:nth-of-type(2) { grid-column: 3; grid-row: 1; border-right: 0; }
.overview-strip .metric:nth-of-type(3) { grid-column: 2; grid-row: 2; }
.overview-strip .metric:nth-of-type(4) { grid-column: 3; grid-row: 2; border-right: 0; } .overview-strip .metric:nth-of-type(4) { grid-column: 3; grid-row: 2; border-right: 0; }
.overview-strip .metric:nth-of-type(5) { grid-column: 1 / 2; grid-row: 3; border-bottom: 0; } .overview-strip .metric:nth-of-type(5) { grid-column: 1 / 2; grid-row: 3; border-bottom: 0; }
.overview-strip .metric:nth-of-type(6) { grid-column: 2 / 4; grid-row: 3; border-right: 0; border-bottom: 0; } .overview-strip .metric:nth-of-type(6) { grid-column: 2 / 4; grid-row: 3; border-right: 0; border-bottom: 0; }
@@ -15330,10 +15254,6 @@ dialog::backdrop {
overflow-y: auto; overflow-y: auto;
} }
.curated-strategy-list {
grid-template-columns: 1fr;
}
.quant-universe-grid { .quant-universe-grid {
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
} }
+41 -3
View File
@@ -1400,25 +1400,31 @@ test("dragon-tiger redesign keeps the merged empty state and independent card hi
await expect(page.locator("#stockDialog")).toBeVisible(); await expect(page.locator("#stockDialog")).toBeVisible();
await page.locator("#closeStockDialog").click(); await page.locator("#closeStockDialog").click();
await page.setViewportSize({ width: 1366, height: 768 });
const scrollOwnership = await page.evaluate(() => { const scrollOwnership = await page.evaluate(() => {
const body = document.querySelector("#dragonTraderDetail tbody"); const body = document.querySelector("#dragonTraderDetail tbody");
const seed = body.querySelector("tr"); const seed = body.querySelector("tr");
for (let index = 0; index < 20; index += 1) body.appendChild(seed.cloneNode(true)); for (let index = 0; index < 20; index += 1) body.appendChild(seed.cloneNode(true));
const main = document.querySelector(".app-main");
const daily = document.querySelector("#dragonDailyContent"); const daily = document.querySelector("#dragonDailyContent");
const operations = document.querySelector("#dragonTraderDetail .trader-operations"); const operations = document.querySelector("#dragonTraderDetail .trader-operations");
return { return {
mainOverflow: getComputedStyle(main).overflowY,
pageScrolls: main.scrollHeight > main.clientHeight,
dailyOverflow: getComputedStyle(daily).overflowY, dailyOverflow: getComputedStyle(daily).overflowY,
dailyFits: daily.scrollHeight <= daily.clientHeight + 1, dailyFits: daily.scrollHeight <= daily.clientHeight + 1,
operationOverflow: getComputedStyle(operations).overflowY, operationOverflow: getComputedStyle(operations).overflowY,
operationsScroll: operations.scrollHeight > operations.clientHeight, operationsFit: operations.scrollHeight <= operations.clientHeight + 1,
descriptionSize: parseFloat(getComputedStyle(document.querySelector("#dragonTraderDetail .dragon-detail-header p")).fontSize), descriptionSize: parseFloat(getComputedStyle(document.querySelector("#dragonTraderDetail .dragon-detail-header p")).fontSize),
}; };
}); });
expect(scrollOwnership).toEqual({ expect(scrollOwnership).toEqual({
dailyOverflow: "hidden", mainOverflow: "auto",
pageScrolls: true,
dailyOverflow: "visible",
dailyFits: true, dailyFits: true,
operationOverflow: "auto", operationOverflow: "auto",
operationsScroll: true, operationsFit: true,
descriptionSize: 13, descriptionSize: 13,
}); });
@@ -1803,6 +1809,38 @@ test("heart breathing prepares once then contracts on each exhale", async ({ pag
await expect(page.locator(".heart-breath-ripple span").first()).toHaveCSS("transition-duration", "4s"); await expect(page.locator(".heart-breath-ripple span").first()).toHaveCSS("transition-duration", "4s");
}); });
test("stylesheet layers do not repeat identical rules in the same cascade context", async ({ page }) => {
await mockApplication(page, session("admin", true));
await page.goto("/index.html");
const duplicates = await page.evaluate(() => {
const occurrences = new Map();
const visitRules = (rules, href, context = []) => {
for (const rule of Array.from(rules || [])) {
if (typeof rule.selectorText === "string" && rule.style) {
const key = `${context.join("\u0001")}\u0000${rule.selectorText}\u0000${rule.style.cssText}`;
const rows = occurrences.get(key) || [];
rows.push(href);
occurrences.set(key, rows);
continue;
}
if (!rule.cssRules) continue;
const condition = rule.conditionText || rule.media?.mediaText || rule.name || "";
visitRules(rule.cssRules, href, [...context, `${rule.type}:${condition}`]);
}
};
for (const sheet of Array.from(document.styleSheets)) {
const href = sheet.href ? new URL(sheet.href).pathname : "inline";
visitRules(sheet.cssRules, href);
}
return Array.from(occurrences.entries())
.filter(([, paths]) => new Set(paths).size > 1)
.map(([rule, paths]) => ({ rule, paths }));
});
expect(duplicates).toEqual([]);
});
test("mobile shell stays within the viewport", async ({ page }) => { test("mobile shell stays within the viewport", async ({ page }) => {
await page.setViewportSize({ width: 375, height: 812 }); await page.setViewportSize({ width: 375, height: 812 });
await mockApplication(page, session("user", true)); await mockApplication(page, session("user", true));
+243
View File
@@ -29,6 +29,228 @@ RETIRED_FRONTEND_SOURCE_RANGES = (
) )
AUDITED_FRONTEND_SOURCE_LINE_COUNT = 9283 AUDITED_FRONTEND_SOURCE_LINE_COUNT = 9283
# CR-12 and CR-13 remove only declarations repeated by a later stylesheet
# layer under the same cascade context. Entries are either an exact retired
# fragment or an exact (source, replacement) pair when surrounding context is
# needed to identify one nested occurrence. Every other byte remains part of
# the accepted CSS baseline.
AUDITED_CSS_RETIREMENTS = {
"styles.css": (
".workspace-view.active-view {\n display: block;\n}\n\n",
"body.sidebar-collapsed {\n grid-template-columns: 64px minmax(0, 1fr);\n}\n\n",
".rotation-day:last-child { border-right: 0; }\n\n",
(
"#screenerView .probability-value strong,\n"
"#screenerView .probability-value small {\n"
" display: block;\n"
"}\n\n"
),
(
(
"@media (min-width: 721px) and (max-width: 1279px) {\n"
" .market-tape {\n"
" display: none;\n"
" }\n\n"
" .app-header {"
),
(
"@media (min-width: 721px) and (max-width: 1279px) {\n"
" .app-header {"
),
),
(
" .module-nav .nav-brand,\n"
" .module-nav .nav-group-label,\n"
" .module-nav .market-sub-tab,\n"
" .module-nav .sidebar-collapse-button {\n"
" display: none;\n"
" }\n\n"
),
(
" .module-nav .nav-group,\n"
" body.sidebar-collapsed .module-nav .nav-group {\n"
" display: contents;\n"
" margin: 0;\n"
" padding: 0;\n"
" border: 0;\n"
" }\n\n"
),
(
" .module-nav .module-tab.mobile-primary-tab,\n"
" body.sidebar-collapsed .module-nav .module-tab.mobile-primary-tab {\n"
" display: flex;\n"
" }\n\n"
),
" body.mentor-directory-open {\n overflow: hidden;\n }\n\n",
(
" body,\n"
" body.sidebar-collapsed {\n"
" display: block;\n"
" min-height: 100dvh;\n"
" padding-bottom: calc(68px + env(safe-area-inset-bottom));\n"
" }\n\n"
),
" .brand-block {\n height: 42px;\n }\n\n",
(
" .header-actions {\n"
" position: absolute;\n"
" inset: 56px 10px auto;\n"
" display: flex;\n"
" justify-content: space-between;\n"
" gap: 6px;\n"
" }\n\n"
),
(
" .header-date-group {\n"
" height: 42px;\n"
" min-width: 0;\n"
" flex: 1;\n"
" }\n\n"
),
(
" .header-actions > .icon-button {\n"
" width: 40px;\n"
" min-width: 40px;\n"
" min-height: 42px;\n"
" }\n\n"
),
" .overview-strip .metric:nth-of-type(1) { grid-column: 2; grid-row: 1; }\n",
" .overview-strip .metric:nth-of-type(3) { grid-column: 2; grid-row: 2; }\n",
" .curated-strategy-list {\n grid-template-columns: 1fr;\n }\n\n",
),
"renovation.css": (
".overview-strip .sentiment-block { padding-left: 0; }\n",
(
'.overview-strip[data-overview-expanded="true"] .metric-value '
"{ font-size: 18px; }\n\n"
),
),
"redesign-v2.css": (
"#dragonView .dragon-operation-table .dragon-col-reason { width: auto; }\n",
(
" .market-tape { display: none; }\n"
" .header-actions { width: 100%; }\n"
" .header-command-group { position: absolute; }\n"
),
(
" .sidebar-brand,\n"
" .module-nav .nav-group-label,\n"
" .sidebar-collapse-button,\n"
" .module-nav .market-sub-tab { display: none; }\n"
" .module-nav .nav-group,\n"
" body.sidebar-collapsed .module-nav .nav-group { display: contents; }\n"
" .module-nav .module-tab,\n"
" body.sidebar-collapsed .module-nav .module-tab { display: none; }\n"
),
" .module-nav .module-tab.mobile-primary-tab span { display: inline; }\n",
(
" .overview-strip .metric:nth-of-type(n + 4),\n"
" .overview-strip .metric-wide { display: none; }\n"
" .overview-toggle { display: none; }\n"
),
),
}
# User-approved product behavior changes remain separate from code-retirement
# records. Each entry is an exact source/replacement pair. Optional trailing
# values declare the expected source count and how many leading occurrences to
# transform when one identical occurrence must remain.
AUDITED_CSS_REPLACEMENTS = {
"redesign-v2.css": (
(
(
'@media (min-width: 721px) {\n'
' body[data-active-view="dragonView"] .app-main { display: flex; flex-direction: column; overflow: hidden; }\n'
' body[data-active-view="dragonView"] .overview-strip { flex: 0 0 auto; }\n'
' body[data-active-view="dragonView"] #dragonView.active-view {\n'
' min-height: 0;\n'
' flex: 1 1 auto;\n'
' display: flex;\n'
' flex-direction: column;\n'
' }\n'
' body[data-active-view="dragonView"] .dragon-page-head-v2 { flex: 0 0 auto; }\n'
' body[data-active-view="dragonView"] .dragon-daily-content-v2,\n'
' body[data-active-view="dragonView"] .dragon-empty-state-v2 { flex: 1 1 auto; }\n'
'}\n'
),
(
'@media (min-width: 721px) {\n'
' body[data-active-view="dragonView"] .app-main { height: var(--workspace-height); min-height: 0; display: block; overflow: auto; }\n'
' body[data-active-view="dragonView"] #dragonView.active-view {\n'
' display: block;\n'
' overflow: visible;\n'
' }\n'
'}\n'
),
),
(
(
"/* The Dragon-Tiger page stays still; only the selected trader's operations scroll. */\n"
'@media (min-width: 721px) {\n'
' body[data-active-view="dragonView"] .dragon-daily-content-v2 {\n'
' min-height: 0;\n'
' display: grid;\n'
' grid-template-rows: auto auto auto minmax(150px, 1fr) auto;\n'
' overflow: hidden;\n'
' }\n\n'
' body[data-active-view="dragonView"] #dragonView .dragon-card-stage-v2 {\n'
' flex: 0 0 auto;\n'
' }\n\n'
' body[data-active-view="dragonView"] #dragonView .dragon-trader-detail-v2 {\n'
' min-height: 0;\n'
' display: flex;\n'
' flex-direction: column;\n'
' }\n\n'
' body[data-active-view="dragonView"] #dragonView .dragon-detail-header {\n'
' flex: 0 0 auto;\n'
' }\n\n'
' body[data-active-view="dragonView"] #dragonView .dragon-trader-detail .trader-operations {\n'
' min-height: 0;\n'
' flex: 1 1 auto;\n'
' overflow: auto;\n'
' overscroll-behavior: contain;\n'
' scrollbar-gutter: stable;\n'
' }\n\n'
' body[data-active-view="dragonView"] #dragonView .dragon-unclassified-v2 {\n'
' max-height: 180px;\n'
' overflow: auto;\n'
' }\n'
),
(
'/* The Dragon-Tiger page owns vertical scrolling; wide operation tables scroll horizontally. */\n'
'@media (min-width: 721px) {\n'
' body[data-active-view="dragonView"] .dragon-daily-content-v2 {\n'
' display: block;\n'
' overflow: visible;\n'
' }\n\n'
' body[data-active-view="dragonView"] #dragonView .dragon-trader-detail-v2 {\n'
' display: block;\n'
' }\n\n'
' body[data-active-view="dragonView"] #dragonView .dragon-trader-detail .trader-operations {\n'
' max-height: none;\n'
' overflow: auto;\n'
' }\n\n'
' body[data-active-view="dragonView"] #dragonView .dragon-unclassified-v2 {\n'
' max-height: none;\n'
' overflow: visible;\n'
' }\n'
),
),
),
"design-system.css": (
(' [data-active-view="dragonView"],\n', "", 4, 3),
(' #dragonView.active-view,\n', ""),
(' #dragonView .dragon-page-head-v2,\n', ""),
(' #dragonView .dragon-daily-content-v2,\n', ""),
(
' #dragonView .dragon-daily-content-v2{overflow:hidden}\n'
' #dragonView .dragon-trader-detail-v2{min-height:0}\n'
' #dragonView .dragon-trader-detail .trader-operations{min-height:0;overflow:auto}\n\n',
"",
),
),
}
def sha256(path: Path) -> str: def sha256(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest() return hashlib.sha256(path.read_bytes()).hexdigest()
@@ -123,6 +345,27 @@ def assert_moved_asset_matches(
frontend_relative: str | None = None, frontend_relative: str | None = None,
) -> None: ) -> None:
target_relative = frontend_relative or original_relative target_relative = frontend_relative or original_relative
if (
original_relative in AUDITED_CSS_RETIREMENTS
or original_relative in AUDITED_CSS_REPLACEMENTS
):
original = (ORIGINAL_STATIC / original_relative).read_text(encoding="utf-8")
for retired in AUDITED_CSS_RETIREMENTS.get(original_relative, ()):
source, replacement = retired if isinstance(retired, tuple) else (retired, "")
testcase.assertEqual(original.count(source), 1, source)
original = original.replace(source, replacement, 1)
for replacement in AUDITED_CSS_REPLACEMENTS.get(original_relative, ()):
source, target, *count_override = replacement
expected_count = count_override[0] if count_override else 1
replacement_count = count_override[1] if len(count_override) > 1 else expected_count
testcase.assertEqual(original.count(source), expected_count, source)
original = original.replace(source, target, replacement_count)
testcase.assertEqual(
(FRONTEND_ROOT / target_relative).read_text(encoding="utf-8"),
original,
original_relative,
)
return
testcase.assertEqual( testcase.assertEqual(
sha256(FRONTEND_ROOT / target_relative), sha256(FRONTEND_ROOT / target_relative),
sha256(ORIGINAL_STATIC / original_relative), sha256(ORIGINAL_STATIC / original_relative),
+44
View File
@@ -124,6 +124,50 @@ class HeavenReadingTests(unittest.TestCase):
len(self.database.list_heaven_readings(self.owner["id"], "fortune")), 1 len(self.database.list_heaven_readings(self.owner["id"], "fortune")), 1
) )
def test_trend_interpretation_saves_successful_agent_result(self):
service = DashboardService.__new__(DashboardService)
service.database = self.database
service._request_context = threading.local()
service._request_context.user_id = self.owner["id"]
setup = {
"trade_date": "20260723",
"chart": {
"available": True,
"sector": "银行",
"stock": {"code": "000001", "name": "平安银行"},
"hexagram": {
"name": "中孚",
"lines": [],
"transformed": {"name": "小畜"},
},
"movement": {},
},
}
with patch.object(service, "heaven_setup", return_value=setup), patch.object(
service,
"_call_heaven_agent",
return_value=(
{"answer": "完整的解势结果", "model": "test", "latency_ms": 1},
"primary",
),
):
result = service.heaven_interpret(
{
"mode": "trend",
"trade_date": "2026-07-23",
"stock_code": "000001",
}
)
self.assertFalse(result["reused"])
self.assertEqual(result["answer"], "完整的解势结果")
self.assertEqual(result["reading"]["subject"], "000001 平安银行")
self.assertEqual(
self.database.list_heaven_readings(self.owner["id"], "trend")[0]["answer"],
"完整的解势结果",
)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
@@ -44,7 +44,7 @@ class FrontendPreservationSliceTests(unittest.TestCase):
(ORIGINAL_STATIC / "index.html").read_text(encoding="utf-8"), (ORIGINAL_STATIC / "index.html").read_text(encoding="utf-8"),
) )
def test_complete_stylesheet_stack_is_byte_identical_after_relocation(self) -> None: def test_stylesheet_stack_matches_baseline_after_audited_retirements(self) -> None:
for original, migrated in ( for original, migrated in (
("shared/tokens.css", "shared/tokens.css"), ("shared/tokens.css", "shared/tokens.css"),
("styles.css", "styles/styles.css"), ("styles.css", "styles/styles.css"),
+76 -1
View File
@@ -29,6 +29,7 @@
| CR-09 | 应用服务门面 | 两个仅服务股池iFinD补全的方法仍错位在全局`DashboardService` | 原函数体机械归位到`PoolServiceMixin` | 已完成 | | CR-09 | 应用服务门面 | 两个仅服务股池iFinD补全的方法仍错位在全局`DashboardService` | 原函数体机械归位到`PoolServiceMixin` | 已完成 |
| CR-10 | Repository所有权 | 五行行业阶段覆盖的三项持久化方法仍错位在根级数据库门面 | 原函数体机械归位到问天Repository,兼容数据继续保留 | 已完成 | | CR-10 | Repository所有权 | 五行行业阶段覆盖的三项持久化方法仍错位在根级数据库门面 | 原函数体机械归位到问天Repository,兼容数据继续保留 | 已完成 |
| CR-11 | 后台任务生命周期 | 导入应用即启动调度线程,启动早于端口绑定,停止只置位但不等待 | 运行时显式启停,每个Runner只拥有一个可等待的调度线程 | 已完成 | | CR-11 | 后台任务生命周期 | 导入应用即启动调度线程,启动早于端口绑定,停止只置位但不等待 | 运行时显式启停,每个Runner只拥有一个可等待的调度线程 | 已完成 |
| CR-12 | CSS跨层精确重复 | 七层样式保留多次视觉改造形成的重复顶层规则,前层声明被后层逐字覆盖 | 只删除能够由CSSOM证明完全重复的前层规则,并建立浏览器级重复门禁 | 已完成 |
## CR-01验收口径 ## CR-01验收口径
@@ -284,9 +285,83 @@
`MiniMax-M2.7-highspeed`在2236毫秒内回复`OK`,证明服务进程的数据与LLM出网链路均已恢复。 `MiniMax-M2.7-highspeed`在2236毫秒内回复`OK`,证明服务进程的数据与LLM出网链路均已恢复。
- 修正后候选329项、纯`app/`导出266项通过;本次只恢复缺失导入和测试,不修改模型配置、额度、 - 修正后候选329项、纯`app/`导出266项通过;本次只恢复缺失导入和测试,不修改模型配置、额度、
提示词、回退策略、行情来源或计算逻辑。 提示词、回退策略、行情来源或计算逻辑。
- 继续验收观势时发现模型已经成功返回,但问天服务在保存解势记录前关闭了HTTP连接。原因是原版
`server.py`已有的`secrets`导入在机械拆分到问天服务时遗漏,生成非观气记录去重键时触发
`NameError`。迁移版恢复该标准库依赖,并增加“模型成功返回后保存观势结果”的完整服务回归测试。
- 使用`000001 平安银行`完成真实页面复测:六爻安全门6/6通过,解势结果正常返回并写入历史,
`8797`错误日志为空。该修正不改变提示词、模型选择、额度、卦象计算、记录结构或前端行为。
本修正基线为`xiaobai-reduction-11-background-jobs-20260801`;检查点为 本修正基线为`xiaobai-reduction-11-background-jobs-20260801`;检查点为
`xiaobai-reduction-11-runtime-connectivity-fix-20260802` `xiaobai-reduction-11-runtime-connectivity-fix-20260802`;后续解势修正检查点为
`xiaobai-reduction-11-heaven-interpret-fix-20260802`
## CR-12验收口径
- 本批只处理不同样式文件中、同为顶层、选择器与完整声明逐字等价的规则;媒体查询、伪状态、
动画、问天隔离样式以及仅外形相似的规则不进入删除范围。
- 删除的必须是较早加载的副本,较晚层继续提供完全相同的最终声明;样式加载顺序、变量、HTML、
JavaScript和主题切换逻辑均不改变。
- 迁移保真测试必须逐段登记允许退休的原始CSS文本,除登记片段外,其余源码继续与原版逐字符相等。
- 浏览器必须验证跨层顶层精确重复为零,并通过日间、夜间、桌面、390px移动端及全站交互回归。
## CR-12结果
- 通过浏览器CSSOM扫描七层运行样式,共发现57组完整重复规则;本批只批准其中7组跨文件顶层重复,
分别涉及工作区显示、折叠侧栏、板块轮动末列、选股概率值、摘要条两项声明及龙虎榜原因列。
其余50组位于同文件或嵌套媒体条件等更复杂环境,证据不足,继续保留。
- 删除7条较早加载的规则,三个生产CSS文件净减少19行、480字节;后层最终规则、选择器优先级与
加载顺序均未改变,没有新增兼容覆盖或第二套样式实现。
- 新增浏览器CSSOM门禁,任何两个样式层再次出现相同顶层选择器与完整声明都会失败;迁移保真门禁
只允许已登记的7个精确源码片段退休,其他CSS差异仍会失败。
- 真实`8797`页面复核情绪周期日间/夜间、龙虎榜和390×844移动端;三个受影响节点的计算样式
与删除前一致,移动端无横向溢出。候选330项、CSS/前端契约33项、迁移对照63项及46项
Playwright全部通过,24个JavaScript文件、API/架构注册表和SQLite完整性检查通过。
- 本批不修改页面布局、颜色、字体、间距、响应式规则、主题、动画、业务功能、API、数据库或部署。
本批基线为`xiaobai-reduction-11-heaven-interpret-fix-20260802`;检查点为
`xiaobai-reduction-12-css-exact-duplicates-20260802`
## CR-13验收口径
- 本批只处理不同样式文件中、处于浏览器规范化后完全相同媒体条件下、选择器与完整CSSOM声明完全
相同的规则;不同媒体上下文、同文件重复、近似声明、动画和问天隔离样式继续保留。
- 删除的必须是较早加载的副本,较晚样式层继续提供相同声明;媒体条件、规则顺序、选择器优先级、
变量、HTML、JavaScript和主题逻辑不得改变。
- 保真门禁必须逐段登记允许退休的原始源码;浏览器门禁必须递归遍历嵌套规则,并只将同一上下文内
跨文件的完整重复判为失败。
- 390×844明暗主题、1000×800中等宽度和1000×600低高度断点必须保持原计算样式与视觉结果。
## CR-13结果
- 浏览器CSSOM确认22组跨文件嵌套重复:1组位于721-1279px媒体条件,12组位于720px移动端条件,
9组位于720px或1023px低高度复合条件;全部删除较早层副本,后层规则原样保留。
- `styles.css`减少65行,`redesign-v2.css`减少15行,生产CSS合计净减少80行、约1.9 KB;没有新增
兼容覆盖、声明值、选择器或样式文件。
- Playwright门禁由顶层扫描扩展为递归上下文扫描,修改后同一嵌套上下文的跨文件完整重复为0;同文件
重复和不同上下文规则不在本批范围,未被误删。
- 1000×800和1000×600修改前后截图逐字节一致;390×844明暗主题的关键显示、定位、间距、网格、
溢出和导航状态一致,页面目视无差异。
- 候选330项、CSS/前端契约33项、迁移对照63项及46项Playwright全部通过;24个JavaScript文件、
API/架构注册表和SQLite完整性检查通过。
- 本批不修改页面布局、颜色、字体、间距、主题、动画、业务功能、API、数据库、数据源、LLM或部署。
本批基线为`xiaobai-reduction-12-css-exact-duplicates-20260802`;检查点为
`xiaobai-reduction-13-css-nested-duplicates-20260802`
## CR-13后续产品修正:龙虎榜整页滚动
- 2026-08-02用户明确要求取消龙虎榜“当日操作明细单独纵向滚动”,改为龙虎榜主内容区整页纵向滚动,
解决低分辨率下操作明细可视高度过小的问题;这是经批准的产品行为变化,不作为CSS去重处理。
- 龙虎榜从桌面固定视口共享规则中独立出来;主内容区继续使用工作区高度并承担纵向滚动,游资卡片、
操作明细和待归类席位按内容自然展开,宽操作表继续保留横向滚动。
- 保真门禁以精确源码替换单独登记本次差异,其他CSS仍与原母版逐字符比较;未新增覆盖层或第二套规则。
- 1366×768真实页面中主内容区为692px、内容高度为2620px,整页滚动可达;操作明细自身高度与内容
高度一致,不再形成纵向小窗口,1180px宽表仍可横向滚动。
- 本次不修改龙虎榜数据、筛选、搜索、游资卡牌、表格字段、游资档案、API、数据库或其他页面的
滚动所有权。
本修正基线为`xiaobai-reduction-13-css-nested-duplicates-20260802`;检查点为
`xiaobai-fix-dragon-page-scroll-20260802`
## 人工验收记录 ## 人工验收记录