Files
zeekayandHanzo Dev ebacc91ff7 work-board: migrate onto @luxfi/[email protected] (@hanzo/gui Tamagui engine) — Phase 1
Run the live 4-org Dework hub on the @luxfi/ui / @hanzo/gui (Tamagui) engine
without regressing the owner-approved look/feel.

- Vite+Tamagui weld (proven exchange recipe): react-native->react-native-web
  alias, dedupe + single-copy of @hanzo/gui/@hanzogui/core/@hanzogui/web,
  optimizeDeps pre-bundle, __DEV__/EXPO_OS/IS_WEB defines, commonjsOptions so
  the engine stays in React's chunk. guiPlugin extraction deferred (its 7.3.0
  peer pins [email protected]); app renders on the runtime style-injection path.
- Wrap in AppProvider (GuiProvider + TanStack Query); brand = VALUE: root theme
  row dark_${BRAND_KEY} + --brand sourced from @luxfi/ui LUX_BRAND (hex identical
  to brands.ts, zero cross-brand leak).
- tokens.css remap layer maps the @luxfi/ui --color-* contract onto the Dework
  palette + brand (no wholesale exchange-palette import).
- Text fields -> @luxfi/ui Input/Textarea (native inputs, onChangeText<->onChange
  bridge): CommandPalette ⌘K, Suggestions editor, TaskDetail comment, chrome
  Toolbar, Leaderboards search. Keystroke round-trip verified.
- Fix Leaderboards mobile clip: page container was a flex item whose mx-auto
  disabled cross-axis stretch, so min-w-[460px] tables forced cards to 460px and
  bled past 390px. w-full + min-w-0 chain + overflow-hidden cards -> tables now
  scroll inside rounded cards, no horizontal page scroll.

Build green (tsc + vite). Visual-regression: 19/21 golden views pixel-match
(<=2%); 2 mobile bottom-sheet overlays (task-detail, palette) diverge ~3% (under
review). Preview branch — do NOT merge to main until owner review.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-18 11:08:50 -07:00

47 lines
2.1 KiB
JavaScript

// Reusable capture: load a work-board hash route at a viewport and screenshot it,
// matching how the goldens were captured (dark, dsf 2, 1440x900 / 390x844).
// Usage: node scripts/capture.mjs <baseUrl> <outDir> [view:hash ...]
// node scripts/capture.mjs http://localhost:5188 /tmp/shots overview:#/ board:#/board
import pw from '/Users/z/work/luxfi/ui/node_modules/@playwright/test/index.js';
import { mkdirSync } from 'node:fs';
const { chromium } = pw;
const base = process.argv[2] || 'http://localhost:5188';
const outDir = process.argv[3] || '/private/tmp/claude-501/-Users-z-work-lux-private/a30ef7fe-e14c-464e-8783-67708506331d/scratchpad/shots';
const specs = process.argv.slice(4);
const DEFAULT = [
'overview:#/', 'board:#/board', 'open-tasks:#/space/engineering/tasks',
'leaderboards:#/leaderboards', 'suggestions:#/suggestions', 'space:#/space/engineering/board',
'task-detail:#/board?task=0', 'explore:#/explore',
];
const views = (specs.length ? specs : DEFAULT).map((s) => {
const i = s.indexOf(':');
return { view: s.slice(0, i), hash: s.slice(i + 1) };
});
const VIEWPORTS = [
{ name: 'desktop', width: 1440, height: 900 },
{ name: 'mobile', width: 390, height: 844 },
];
mkdirSync(outDir, { recursive: true });
const errors = [];
const browser = await chromium.launch();
for (const vp of VIEWPORTS) {
const ctx = await browser.newContext({
viewport: { width: vp.width, height: vp.height },
deviceScaleFactor: 2,
colorScheme: 'dark',
});
const page = await ctx.newPage();
page.on('pageerror', (e) => errors.push(`[${vp.name}] PAGEERROR ${e.message}`));
page.on('console', (m) => { if (m.type() === 'error') errors.push(`[${vp.name}] ${m.text()}`); });
for (const { view, hash } of views) {
await page.goto(`${base}/${hash}`, { waitUntil: 'networkidle', timeout: 60000 });
await page.waitForTimeout(700); // settle fixture render + fonts
await page.screenshot({ path: `${outDir}/${view}-${vp.name}.png` });
}
await ctx.close();
}
await browser.close();
console.log(JSON.stringify({ captured: views.map((v) => v.view), viewports: VIEWPORTS.map((v) => v.name), errors: errors.slice(0, 15) }, null, 2));