|
| 1 | +// One-shot helper: define the shell/theme/layout "chrome" parity units that the manifest |
| 2 | +// scan never captured (the nav sidebar, theme application, status bar, top toolbar, dashboard |
| 3 | +// layout system, digital twin) and merge honest live-visual rows into windows-ledger.json. |
| 4 | +// Scores/deltas are recorded from real screenshot comparison vs http://localhost:3000. |
| 5 | +const fs = require('fs'); |
| 6 | +const path = require('path'); |
| 7 | + |
| 8 | +const dir = __dirname; |
| 9 | +const SHOTS = 'apps/windows/.loop-logs/shots'; |
| 10 | + |
| 11 | +const units = [ |
| 12 | + { |
| 13 | + unitId: 'component:shell/Sidebar', |
| 14 | + title: 'Navigation sidebar', |
| 15 | + parityChecklist: [ |
| 16 | + 'Favorites section above the groups', |
| 17 | + 'Collapsible route groups with item-count badges', |
| 18 | + 'Groups collapsed by default', |
| 19 | + 'Colour-coded group icons', |
| 20 | + 'Logo + product name at top', |
| 21 | + 'Search affordance', |
| 22 | + ], |
| 23 | + status: 'todo', |
| 24 | + visualScore: 88, |
| 25 | + shotPath: `${SHOTS}/component-shell-Sidebar.png`, |
| 26 | + deltas: [ |
| 27 | + 'group taxonomy uses the native 17 RouteGroups, not the web sidebar 19 groups (HOME/VEHICLES/DRIVING/…)', |
| 28 | + 'search lives in the title bar rather than inside the sidebar (platform difference)', |
| 29 | + ], |
| 30 | + evidenceLog: 'live screenshot win-sidebar-light.png vs web-dash-real.png', |
| 31 | + }, |
| 32 | + { |
| 33 | + unitId: 'component:theme/BackendSync', |
| 34 | + title: 'Theme application (accent + colour mode)', |
| 35 | + parityChecklist: [ |
| 36 | + 'Accent theme + colour mode seeded from backend /settings at startup', |
| 37 | + 'Light/card-based look when the account is light mode', |
| 38 | + 'Token brushes recolour app-wide on theme change', |
| 39 | + 'Local default only stands in until backend resolves', |
| 40 | + ], |
| 41 | + status: 'todo', |
| 42 | + visualScore: 92, |
| 43 | + shotPath: `${SHOTS}/component-theme-BackendSync.png`, |
| 44 | + deltas: [ |
| 45 | + 'solar-amber light surface reads slightly warmer/cream vs the web near-white', |
| 46 | + ], |
| 47 | + evidenceLog: 'live screenshot win-dash-imperial.png (light + solar-amber) vs web-dash-real.png', |
| 48 | + }, |
| 49 | + { |
| 50 | + unitId: 'component:dashboard/Banners', |
| 51 | + title: 'Dashboard banners (web parity = none)', |
| 52 | + parityChecklist: [ |
| 53 | + 'No native-only "Personalize TeslaSync" first-run banner', |
| 54 | + 'No "Tesla account not connected" warning banner', |
| 55 | + 'No broken dark-on-light chrome', |
| 56 | + ], |
| 57 | + status: 'done', |
| 58 | + visualScore: 95, |
| 59 | + shotPath: `${SHOTS}/component-dashboard-Banners.png`, |
| 60 | + deltas: [], |
| 61 | + evidenceLog: 'live screenshot win-dash-nobanners2.png vs web-dash-real.png (web shows neither banner)', |
| 62 | + }, |
| 63 | + { |
| 64 | + unitId: 'component:units/BackendSync', |
| 65 | + title: 'Unit preference (metric/imperial) from backend', |
| 66 | + parityChecklist: [ |
| 67 | + 'Unit preference seeded from backend /settings', |
| 68 | + 'Distance/speed/temp render in the account units (mi/mph/°F when imperial)', |
| 69 | + 'List pages and dashboard widgets both honour the preference', |
| 70 | + ], |
| 71 | + status: 'done', |
| 72 | + visualScore: 95, |
| 73 | + shotPath: `${SHOTS}/component-units-BackendSync.png`, |
| 74 | + deltas: [], |
| 75 | + evidenceLog: 'live screenshot win-dash-imperial.png (mi / °F / Wh·mi) vs web-dash-real.png', |
| 76 | + }, |
| 77 | + { |
| 78 | + unitId: 'component:shell/TopToolbar', |
| 79 | + title: 'Top toolbar (refresh/export/kiosk/customize/live/print)', |
| 80 | + parityChecklist: [ |
| 81 | + 'Refresh, export, kiosk, customize, notifications, live indicator, print actions', |
| 82 | + 'Right-aligned in the header row', |
| 83 | + ], |
| 84 | + status: 'todo', |
| 85 | + visualScore: 0, |
| 86 | + shotPath: '', |
| 87 | + deltas: ['native header has a minimal action set vs the web toolbar cluster'], |
| 88 | + evidenceLog: '', |
| 89 | + }, |
| 90 | + { |
| 91 | + unitId: 'component:shell/StatusBar', |
| 92 | + title: 'Bottom status bar', |
| 93 | + parityChecklist: [ |
| 94 | + 'API latency + live indicator on the left', |
| 95 | + 'Active vehicle name / SoC / range on the right', |
| 96 | + 'Shortcuts, tour, report-bug, version links', |
| 97 | + ], |
| 98 | + status: 'todo', |
| 99 | + visualScore: 0, |
| 100 | + shotPath: '', |
| 101 | + deltas: ['native status bar shows only a "Secured" hint vs the web rich status bar'], |
| 102 | + evidenceLog: '', |
| 103 | + }, |
| 104 | + { |
| 105 | + unitId: 'component:dashboard/LayoutSystem', |
| 106 | + title: 'Customizable dashboard widget grid', |
| 107 | + parityChecklist: [ |
| 108 | + 'GET STARTED onboarding checklist', |
| 109 | + 'Recently Viewed strip', |
| 110 | + 'Layout picker (Default / New Layout / Save as / Edit)', |
| 111 | + 'Add-widget floating action + persistence', |
| 112 | + ], |
| 113 | + status: 'todo', |
| 114 | + visualScore: 0, |
| 115 | + shotPath: '', |
| 116 | + deltas: ['native dashboard is a fixed curated widget set; the web customizable layout grid is not ported'], |
| 117 | + evidenceLog: '', |
| 118 | + }, |
| 119 | + { |
| 120 | + unitId: 'component:data-display/DigitalTwin3D', |
| 121 | + title: 'Digital twin vehicle render', |
| 122 | + parityChecklist: [ |
| 123 | + 'Vehicle render with live lock/sentry/charging/closure state', |
| 124 | + 'Matches the web digital twin fidelity', |
| 125 | + ], |
| 126 | + status: 'todo', |
| 127 | + visualScore: 70, |
| 128 | + shotPath: `${SHOTS}/component-data-display-DigitalTwin3D.png`, |
| 129 | + deltas: ['native renders a 2D vehicle illustration; web renders a higher-fidelity twin'], |
| 130 | + evidenceLog: 'live screenshot win-dash-nobanners2.png', |
| 131 | + }, |
| 132 | +]; |
| 133 | + |
| 134 | +// 1) write the spec file (combined-list partner of parity-manifest.json) |
| 135 | +const specPath = path.join(dir, 'parity-chrome-units.json'); |
| 136 | +const spec = units.map(({ status, visualScore, shotPath, deltas, evidenceLog, ...rest }) => rest); |
| 137 | +fs.writeFileSync(specPath, JSON.stringify(spec, null, 2) + '\n'); |
| 138 | +console.log('wrote', path.relative(dir, specPath), '(' + spec.length + ' units)'); |
| 139 | + |
| 140 | +// 2) merge honest rows into the ledger |
| 141 | +const ledgerPath = path.join(dir, 'windows-ledger.json'); |
| 142 | +const ledger = JSON.parse(fs.readFileSync(ledgerPath, 'utf8')); |
| 143 | +const byId = new Map(ledger.map((r) => [r.unitId, r])); |
| 144 | +for (const u of units) { |
| 145 | + const row = { |
| 146 | + unitId: u.unitId, |
| 147 | + platform: 'windows', |
| 148 | + status: u.status, |
| 149 | + coveredCount: u.parityChecklist.length - u.deltas.length, |
| 150 | + requiredCount: u.parityChecklist.length, |
| 151 | + visualScore: u.visualScore, |
| 152 | + shotPath: u.shotPath, |
| 153 | + deltas: u.deltas, |
| 154 | + attempts: 1, |
| 155 | + promptId: 'live-visual/windows-parity-loop', |
| 156 | + evidenceLog: u.evidenceLog, |
| 157 | + }; |
| 158 | + byId.set(u.unitId, row); |
| 159 | +} |
| 160 | +const merged = Array.from(byId.values()); |
| 161 | +fs.writeFileSync(ledgerPath, JSON.stringify(merged, null, 2) + '\n'); |
| 162 | +const done = merged.filter((r) => r.status === 'done').length; |
| 163 | +const blocked = merged.filter((r) => r.status === 'blocked').length; |
| 164 | +const todo = merged.filter((r) => r.status === 'todo' || r.status === 'in_progress').length; |
| 165 | +console.log(`ledger rows: ${merged.length} (done ${done}, blocked ${blocked}, todo ${todo})`); |
0 commit comments