perf(web): contain offscreen vehicle rows
This commit is contained in:
@@ -1,9 +1,32 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import mainSource from '../main.tsx?raw';
|
||||
|
||||
const v2Styles = readFileSync(resolve(process.cwd(), 'src/v2/styles/v2.css'), 'utf8');
|
||||
|
||||
describe('V2 production entry', () => {
|
||||
test('does not load the legacy Semi UI theme and global stylesheet', () => {
|
||||
expect(mainSource).not.toContain("./styles/global.css");
|
||||
expect(mainSource).toContain("./v2/styles/v2.css");
|
||||
});
|
||||
|
||||
test('applies rendering containment to off-screen vehicle items instead of the visible scroller', () => {
|
||||
const ruleBody = (selector: string) => {
|
||||
const start = v2Styles.indexOf(`${selector} {`);
|
||||
expect(start).toBeGreaterThanOrEqual(0);
|
||||
const end = v2Styles.indexOf('}', start);
|
||||
expect(end).toBeGreaterThan(start);
|
||||
return v2Styles.slice(start, end);
|
||||
};
|
||||
const scrollRule = ruleBody('.v2-vehicle-scroll');
|
||||
const rowRule = ruleBody('.v2-vehicle-row');
|
||||
const mobileCardRule = ruleBody('.v2-monitor-mobile-cards > article');
|
||||
|
||||
expect(scrollRule).not.toContain('content-visibility');
|
||||
expect(rowRule).toContain('content-visibility: auto');
|
||||
expect(rowRule).toContain('contain-intrinsic-size: auto 60px');
|
||||
expect(mobileCardRule).toContain('content-visibility: auto');
|
||||
expect(mobileCardRule).toContain('contain-intrinsic-size: auto 154px');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -108,8 +108,8 @@ button, a { -webkit-tap-highlight-color: transparent; }
|
||||
.v2-vehicle-rail > header span, .v2-vehicle-rail > footer { color: var(--v2-muted); font-size: 10px; }
|
||||
.v2-rail-search { display: flex; height: 34px; align-items: center; gap: 7px; margin: 0 9px 8px; border: 1px solid var(--v2-border); border-radius: 7px; padding: 0 9px; color: #8a98aa; font-size: 10px; }
|
||||
.v2-rail-search.has-missing { border-color: #fed7aa; background: #fffaf3; color: #b45309; }
|
||||
.v2-vehicle-scroll { min-height: 0; flex: 1; overflow: auto; overscroll-behavior: contain; content-visibility: auto; }
|
||||
.v2-vehicle-row { display: grid; width: 100%; min-height: 60px; grid-template-columns: 10px minmax(0, 1fr) auto; align-items: center; gap: 8px; border: 0; border-top: 1px solid #eef2f7; background: #fff; padding: 8px 10px; text-align: left; cursor: pointer; }
|
||||
.v2-vehicle-scroll { min-height: 0; flex: 1; overflow: auto; overscroll-behavior: contain; }
|
||||
.v2-vehicle-row { display: grid; width: 100%; min-height: 60px; grid-template-columns: 10px minmax(0, 1fr) auto; align-items: center; gap: 8px; border: 0; border-top: 1px solid #eef2f7; background: #fff; padding: 8px 10px; text-align: left; cursor: pointer; content-visibility: auto; contain-intrinsic-size: auto 60px; }
|
||||
.v2-vehicle-row:hover { background: #f8fbff; }
|
||||
.v2-vehicle-row.is-selected { position: relative; z-index: 1; background: var(--v2-blue-soft); box-shadow: inset 3px 0 var(--v2-blue); }
|
||||
.v2-status-dot { width: 7px; height: 7px; border-radius: 50%; background: #94a3b8; }
|
||||
@@ -1119,7 +1119,7 @@ button, a { -webkit-tap-highlight-color: transparent; }
|
||||
.v2-monitor-table-panel > header { align-items: flex-start; flex-direction: column; gap: 4px; padding: 10px 12px; }
|
||||
.v2-monitor-table-scroll { display: none; }
|
||||
.v2-monitor-mobile-cards { display: flex; min-height: 0; flex: 1; flex-direction: column; gap: 8px; overflow: auto; padding: 8px; background: #f5f7fa; }
|
||||
.v2-monitor-mobile-cards > article { border: 1px solid #e0e6ee; border-radius: 10px; background: #fff; padding: 12px; box-shadow: 0 2px 8px rgba(21,32,51,.04); }
|
||||
.v2-monitor-mobile-cards > article { border: 1px solid #e0e6ee; border-radius: 10px; background: #fff; padding: 12px; box-shadow: 0 2px 8px rgba(21,32,51,.04); content-visibility: auto; contain-intrinsic-size: auto 154px; }
|
||||
.v2-monitor-mobile-cards > article > header { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
|
||||
.v2-monitor-mobile-cards > article > header div { display: flex; min-width: 0; flex-direction: column; gap: 3px; }
|
||||
.v2-monitor-mobile-cards > article > header strong { font-size: 15px; }.v2-monitor-mobile-cards > article > header span { color: #7f8da1; font-size: 10px; }
|
||||
|
||||
@@ -88,6 +88,8 @@ React documents that `lazy` caches the import promise and propagates a rejected
|
||||
|
||||
The first continuity pass only warmed Monitor, Tracks, History and Statistics in the background. A production cold-cache audit with 450 ms simulated latency still measured roughly 1.66 seconds for `/monitor` to `/access`, leaving Vehicles, Alerts, Access and Operations dependent on click-time loading. The idle queue now covers all eight route sections in bounded batches of two. It still skips `saveData`, `slow-2g` and `2g` connections, while pointer/focus/down intent preloading remains the fastest path for an immediately selected destination. Unit coverage proves every inactive route is queued and that cleanup cancels the next idle batch.
|
||||
|
||||
A production route-memory loop did not prove a retained application leak: after natural collection, JS heap returned to roughly its initial 31 MB range and old map documents/frames were released. It did expose an ineffective rendering hint: `content-visibility: auto` was attached to the always-visible vehicle scroll viewport instead of the 200 independently off-screen rows. The containment boundary now lives on every desktop rail row and mobile list card, paired with `contain-intrinsic-size` so skipped content keeps stable scroll geometry. This follows the CSS Containment specification's long-scroll-list use case while avoiding the documented scrollbar jump caused by missing intrinsic size: <https://www.w3.org/TR/css-contain-2/#content-visibility>.
|
||||
|
||||
The navigation and progressive-loading direction follows mature observability systems: persistent global controls, collapsible sections and loading only the content needed for the current task. Elastic documents these dashboard interaction and panel-organization patterns here: <https://www.elastic.co/docs/explore-analyze/dashboards/using> and <https://www.elastic.co/docs/explore-analyze/dashboards/arrange-panels>.
|
||||
|
||||
### Remaining audit queue
|
||||
|
||||
Reference in New Issue
Block a user