From 86227cec37e463af31a5752aade7e2a674853d44 Mon Sep 17 00:00:00 2001 From: leefer Date: Sun, 2 Aug 2026 11:54:00 +0800 Subject: [PATCH] refactor: remove exact cross-layer css duplicates --- app/config/architecture-inventory.json | 12 +++--- app/frontend/styles/redesign-v2.css | 1 - app/frontend/styles/renovation.css | 3 -- app/frontend/styles/styles.css | 15 ------- app/tests/e2e/app-shell.spec.js | 24 ++++++++++++ app/tests/preservation_helpers.py | 39 +++++++++++++++++++ app/tests/test_preservation_slice_frontend.py | 2 +- docs/governance/code-reduction.md | 27 +++++++++++++ 8 files changed, 97 insertions(+), 26 deletions(-) diff --git a/app/config/architecture-inventory.json b/app/config/architecture-inventory.json index c227bec..53d0961 100644 --- a/app/config/architecture-inventory.json +++ b/app/config/architecture-inventory.json @@ -309,13 +309,13 @@ "code_hotspots": [ { "path": "frontend/styles/styles.css", - "bytes": 361776, - "lines": 15465 + "bytes": 361501, + "lines": 15450 }, { "path": "frontend/styles/redesign-v2.css", - "bytes": 263539, - "lines": 8570 + "bytes": 263467, + "lines": 8569 }, { "path": "frontend/index.html", @@ -344,8 +344,8 @@ }, { "path": "frontend/styles/renovation.css", - "bytes": 83949, - "lines": 1553 + "bytes": 83812, + "lines": 1550 }, { "path": "frontend/pages/heaven/page.css", diff --git a/app/frontend/styles/redesign-v2.css b/app/frontend/styles/redesign-v2.css index 1460dc6..83931ac 100644 --- a/app/frontend/styles/redesign-v2.css +++ b/app/frontend/styles/redesign-v2.css @@ -4318,7 +4318,6 @@ body.sidebar-collapsed .status-bar { left: 64px; } #dragonView .dragon-operation-table .dragon-col-direction { width: 72px; } #dragonView .dragon-operation-table .dragon-col-number { width: 82px; } #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 td.stock-code { font-variant-numeric: tabular-nums; } #dragonView .dragon-operation-table thead th { position: sticky; top: 0; z-index: 2; background: #fafbfc; } diff --git a/app/frontend/styles/renovation.css b/app/frontend/styles/renovation.css index f6b23b0..d54fff9 100644 --- a/app/frontend/styles/renovation.css +++ b/app/frontend/styles/renovation.css @@ -127,7 +127,6 @@ body.sidebar-collapsed .app-main { margin-left: 0; } 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-text { font-size: 12px; 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"] .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"] .metric-value { font-size: 18px; } - /* Page frame and shared information architecture. */ .workspace-view, body[data-active-view="screenerView"] .workspace-view, diff --git a/app/frontend/styles/styles.css b/app/frontend/styles/styles.css index ae4c0fb..819e6c4 100644 --- a/app/frontend/styles/styles.css +++ b/app/frontend/styles/styles.css @@ -666,10 +666,6 @@ time { box-shadow: var(--shadow-soft); } -.workspace-view.active-view { - display: block; -} - .workspace-view.active-view.view-entering { animation: view-enter var(--motion-medium) var(--ease-out) both; } @@ -4745,10 +4741,6 @@ textarea { outline-color: rgba(29, 101, 193, 0.48); } -body.sidebar-collapsed { - grid-template-columns: 64px minmax(0, 1fr); -} - body.sidebar-collapsed .module-nav { width: 64px; padding-right: 7px; @@ -5369,8 +5361,6 @@ body.sidebar-collapsed .sidebar-collapse-button .lucide { border-right: 1px solid var(--border); 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 time { font-size: 12px; font-weight: 700; } .rotation-day header span { color: var(--text-secondary); font-size: 10px; } @@ -7130,11 +7120,6 @@ body.sidebar-collapsed .sidebar-collapse-button .lucide { box-shadow: 1px 0 0 var(--border); } -#screenerView .probability-value strong, -#screenerView .probability-value small { - display: block; -} - #screenerView .probability-value small { margin-top: 3px; color: var(--text-secondary); diff --git a/app/tests/e2e/app-shell.spec.js b/app/tests/e2e/app-shell.spec.js index 565947e..3f7b743 100644 --- a/app/tests/e2e/app-shell.spec.js +++ b/app/tests/e2e/app-shell.spec.js @@ -1803,6 +1803,30 @@ 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"); }); +test("stylesheet layers do not repeat identical top-level rules", async ({ page }) => { + await mockApplication(page, session("admin", true)); + await page.goto("/index.html"); + + const duplicates = await page.evaluate(() => { + const occurrences = new Map(); + for (const sheet of Array.from(document.styleSheets)) { + const href = sheet.href ? new URL(sheet.href).pathname : "inline"; + for (const rule of Array.from(sheet.cssRules || [])) { + if (typeof rule.selectorText !== "string" || !rule.style) continue; + const key = `${rule.selectorText}\u0000${rule.style.cssText}`; + const rows = occurrences.get(key) || []; + rows.push(href); + occurrences.set(key, rows); + } + } + 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 }) => { await page.setViewportSize({ width: 375, height: 812 }); await mockApplication(page, session("user", true)); diff --git a/app/tests/preservation_helpers.py b/app/tests/preservation_helpers.py index 664b099..47d9db9 100644 --- a/app/tests/preservation_helpers.py +++ b/app/tests/preservation_helpers.py @@ -29,6 +29,34 @@ RETIRED_FRONTEND_SOURCE_RANGES = ( ) AUDITED_FRONTEND_SOURCE_LINE_COUNT = 9283 +# CR-12 removes only declarations that are repeated verbatim by a later +# stylesheet layer under the same top-level cascade context. Keeping the exact +# source fragments here makes every other byte of the accepted CSS baseline a +# preservation requirement. +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" + ), + ), + "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", + ), +} + def sha256(path: Path) -> str: return hashlib.sha256(path.read_bytes()).hexdigest() @@ -123,6 +151,17 @@ def assert_moved_asset_matches( frontend_relative: str | None = None, ) -> None: target_relative = frontend_relative or original_relative + if original_relative in AUDITED_CSS_RETIREMENTS: + original = (ORIGINAL_STATIC / original_relative).read_text(encoding="utf-8") + for retired in AUDITED_CSS_RETIREMENTS[original_relative]: + testcase.assertEqual(original.count(retired), 1, retired) + original = original.replace(retired, "", 1) + testcase.assertEqual( + (FRONTEND_ROOT / target_relative).read_text(encoding="utf-8"), + original, + original_relative, + ) + return testcase.assertEqual( sha256(FRONTEND_ROOT / target_relative), sha256(ORIGINAL_STATIC / original_relative), diff --git a/app/tests/test_preservation_slice_frontend.py b/app/tests/test_preservation_slice_frontend.py index c00e701..0bc7a88 100644 --- a/app/tests/test_preservation_slice_frontend.py +++ b/app/tests/test_preservation_slice_frontend.py @@ -44,7 +44,7 @@ class FrontendPreservationSliceTests(unittest.TestCase): (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 ( ("shared/tokens.css", "shared/tokens.css"), ("styles.css", "styles/styles.css"), diff --git a/docs/governance/code-reduction.md b/docs/governance/code-reduction.md index f8dda48..ebfdec0 100644 --- a/docs/governance/code-reduction.md +++ b/docs/governance/code-reduction.md @@ -29,6 +29,7 @@ | CR-09 | 应用服务门面 | 两个仅服务股池iFinD补全的方法仍错位在全局`DashboardService` | 原函数体机械归位到`PoolServiceMixin` | 已完成 | | CR-10 | Repository所有权 | 五行行业阶段覆盖的三项持久化方法仍错位在根级数据库门面 | 原函数体机械归位到问天Repository,兼容数据继续保留 | 已完成 | | CR-11 | 后台任务生命周期 | 导入应用即启动调度线程,启动早于端口绑定,停止只置位但不等待 | 运行时显式启停,每个Runner只拥有一个可等待的调度线程 | 已完成 | +| CR-12 | CSS跨层精确重复 | 七层样式保留多次视觉改造形成的重复顶层规则,前层声明被后层逐字覆盖 | 只删除能够由CSSOM证明完全重复的前层规则,并建立浏览器级重复门禁 | 已完成 | ## CR-01验收口径 @@ -294,6 +295,32 @@ `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`。 + ## 人工验收记录 - 2026-08-01:用户检查CR-02与CR-03运行结果,确认未发现明显异常。本记录仅表示本轮可见功能与