gendesign/.design-sync/NOTES.md

14 KiB
Raw Permalink Blame History

design-sync NOTES — tradein-mvp-frontend

Target: tradein-mvp/frontend (NOT the repo-root frontend/, which is the Site-Finder app). Project: gendesign-tradein.

Build gotchas

  • No dist / no Storybook → synth-entry mode. The package is a Next.js app, not a lib; package.json has no main/module/exports.
  • --entry must point at a NON-existent dist path (tradein-mvp/frontend/dist/index.js). Two jobs:
    1. PKG_DIR resolution walks up dirname(--entry) to the real named package.json → PKG_DIR=tradein-mvp/frontend (without --entry, PKG_DIR defaults to node_modules/<pkg> which doesn't self-install → ENOENT crash).
    2. The path being absent makes resolveDistEntry(soft) return null → synthEntry=truederiveComponentsFromSrc discovers components from src/. A path that EXISTS (e.g. package.json) suppresses synth mode → [ZERO_MATCH] tokens-only.
  • cfg.* path fields are PACKAGE-relative (relative to PKG_DIR), not repo-relative. srcDir=src, cssEntry=src/components/trade-in/trade-in.css, tokensGlob=src/app/globals.css.
  • --node-modules tradein-mvp/frontend/node_modules (react + @types/react live there).

Styling

  • Tokens in src/app/globals.css (:root{--bg-app,--accent,…}); component CSS in src/components/trade-in/trade-in.css (2277 lines). Manrope via remote Google Fonts @import (→ [FONT_REMOTE], no action).

Re-sync risks

  • Synth scan over-includes non-component PascalCase exports (51 found vs ~37 files) — prune with componentSrcMap: {Name: null} as identified.
  • Components are app-coupled (next/*, TanStack Query, data hooks); many need providers/mock props to render → expect floor cards / authored previews with composed props.

Authoring (38 components, all authored-good)

  • Import contract: preview imports {X} from 'tradein-mvp-frontend' (mapped to window.TradeInUI bundle) + data from ./_fixtures (re-exports the repo's offline app/ui-preview/estimate/fixture.ts). Type imports erased.
  • Provider = seeded PreviewProvider (.design-sync/preview-provider.tsx, wired via cfg.extraEntries + cfg.provider). Mirrors app/ui-preview/estimate/page.tsx's seeded QueryClient: pre-fills ["auth","me"] (fake admin user) + the estimate sub-queries (placement-history, house-analytics, sell-time-sensitivity, cian-price-changes=[], sales-vs-listings). This is what makes fetch-coupled cards (StreetDealsCard, HouseAnalyticsSection, PlacementHistoryCard) and auth-gated ones (RouteGuard, UserMenu) render offline.
    • CRITICAL: an extraEntries module must NOT import anything that reads process.env at module top-level (e.g. @/lib/useMe@/lib/api) — extraEntries evaluate BEFORE the synth-entry .pkg-shim.mjs, so process is undefined and the whole IIFE aborts (38/38 vanish from the global). The provider hardcodes ME_QUERY_KEY = ["auth","me"] instead of importing useMe.
    • extraEntries path is PACKAGE-relative: ../../.design-sync/preview-provider.tsx. Its @/ aliases don't resolve from outside the tsconfig root → use relative ../tradein-mvp/frontend/src/... for repo imports inside it.
  • Chart cards gate on analog count ≥8 (DistributionCard, ExposureCard): the shared fixture has 3 analogs → "not enough data" fallback. Those two previews extend analogs to 10 realistic ЕКБ lots INLINE in their own preview file (spread FIXTURE_ESTIMATE, override analogs+n_analogs) — never edit _fixtures.ts.
  • Admin/scraper panels (DataQualitySection, ProviderProxySection, RunsTable, PacingSection, SystemHealthSection) fetch their own data with no repo fixture → they render their real styled LOADING/header state. Graded good as honest states; richer data would need admin fixtures that don't exist in the repo.
  • MapCard / MapPicker: Leaflet from unpkg loads in the capture env; OSM raster tiles sometimes don't paint within the screenshot window (external tile fetch) — markers/popup/controls still render. Both graded good. MapPicker is a createPortal full-viewport overlay — rendered contained in capture; if a future capture viewport change makes it escape, add cfg.overrides.MapPicker.cardMode/viewport.

Re-sync risks

  • The process shim env defaults live in .design-sync/overrides/source-kit.mjs (synth entry). If the app reads NEW process.env.NEXT_PUBLIC_* vars, add them there or components throw.
  • PreviewProvider seed keys are pinned to the fixture's estimate_id and address/area/rooms — if fixture.ts changes those, update preview-provider.tsx to match (else fetch-coupled cards re-floor).
  • Synth scan re-includes Next route/layout/error files on every build → componentSrcMap nulls (13 entries) must persist. New app-router pages would need adding.
  • .d.ts props are weak ([key:string]:unknown) — synth mode has no built types. Real prop contracts live in each component's source interface Props. A real tsup/tsc lib build would fix this (recommend if the agent needs strong API contracts).
  • Grades clear on any cfg.provider/preview-affecting config change (expected) — re-grade from fresh sheets.

Re-sync 2026-07-02 — v2 dashboard sync (main was 223 commits ahead; harness authored on June components)

Two source-kit.mjs fork fixes were REQUIRED for the evolved v2 codebase (both committed in the override, declared in cfg.libOverrides):

  1. Exclude next/font importers from the synth-entry. src/app/v2/layout.tsx calls Manrope()/IBM_Plex_Mono() from next/font/google at MODULE TOP-LEVEL. esbuild can't resolve the Next-only loader → stubs it (void 0)undefined() aborts the whole browser IIFE → window.TradeInUI empty → ALL 58 components vanish ([RENDER] root empty everywhere, [BUNDLE_EXPORT]). Fix: comps filter drops any file whose content matches /from\s+['"]next\/font/. If a NEW file top-level-calls a Next build-time loader, same class of crash — extend the filter.
  2. Re-export default-exported components. export * from <path> does NOT re-export a module's default. The v2 views/nav/overlay/panel are authored export default function <Name> (AnalyticsView, HeroBar, HistoryView, ParamsPanel, SectionOverlay, SourcesView, TopNav) → absent from window.TradeInUI[BUNDLE_EXPORT] not a component. Fix: entry now also emits export { default as <Name> } from <path> for each export default function/class <Name>. Named-export components (export function X) were always fine.

componentSrcMap: added TradeInV2Layout / TradeInV2Page / SaleShareLayout / SaleSharePage = null (Next route files, never DS components — same as RootLayout et al.).

overrides (grid): MapPicker {cardMode:single, primaryStory:Default} (portal/fixed), StreetDealsCard + Topbar {cardMode:column} (wider than a grid cell).

Floor-card components (6, authorable on any future re-sync): SaleShareControls, SaleShareList, SaleShareMap, SectionOverlay, LocationDrawer, BuildingListingsDrawer — overlays/drawers whose props don't seed rich data via PreviewProvider, so they show the honest typographic floor. All the OTHER v2 components (views/nav/hero/panel) render richly because the provider seeds their data.

Known render warn: ResultPanel — variants identical (Default vs Error cells render near-identically; not broken, the Error cell just doesn't diverge visually enough). Recorded here so re-syncs don't read it as new.

Font note (re-sync risk): the v2 HUD's real typefaces are Manrope + IBM_Plex_Mono (loaded by the excluded v2/layout.tsx via next/font → CSS vars --font-manrope/--font-plex-mono). cfg.extraFonts ships Inter + JetBrains Mono (June brand fonts). v2 previews therefore render Manrope-slot text in the shipped fallback. If brand-exact v2 rendering is wanted, add Manrope + IBM Plex Mono woff2 to .design-sync/fonts/ + cfg.extraFonts and map the --font-manrope/--font-plex-mono vars.

ResultPanel NAME COLLISION (fixed 2026-07-02): two components named ResultPanel in src — app/scrapers/_components/ScraperPage.tsx (export function ResultPanel, scraper mut-panel) and components/trade-in/v2/ResultPanel.tsx (export default function ResultPanel, the v2 estimate result / honest hero). The default-re-export fix makes the v2 one win window.TradeInUI.ResultPanel (explicit export {default as ResultPanel} beats the star export). But the June authored preview previews/ResultPanel.tsx was the SCRAPER one (mut props) → the v2 component rendered fine (reads data from context) but its prompt.md documented the wrong (scraper) API. FIX: (1) cfg.componentSrcMap.ResultPanel = "src/components/trade-in/v2/ResultPanel.tsx" pins enrichment to v2; (2) re-authored previews/ResultPanel.tsx<ResultPanel onNavigate={()=>{}} /> (v2 API; data defaults to the component's built-in RESULT_FIXTURE = honest-hero). If a re-sync ever shows ResultPanel with scraper markup, the pin/preview regressed. Any NEW duplicate PascalCase component name will hit the same class of issue — the default-re-export makes the default-export win; pin + author the intended one.

Upload gotcha (this machine): after a follow-up single-component rebuild, resync-verdict.json upload.deletePaths came back with ALL other components (318 paths) — a stale-anchor diff artifact, NOT real deletions (ResultPanel + audit/uploads were absent from it). Do NOT feed that deletePaths to delete_files (would nuke the project). For a focused single-component re-upload: write just that component's components/<group>/<Name>/* + _preview/<Name>.js + _ds_sync.json (sentinel-fenced), deletes=[].

  • CORRECTION (2026-07-03): the 318 deletePaths were NOT a diff artifact — they were REAL local deletions caused by a source-kit fork bug (see below): the July-02 componentSrcMap.ResultPanel pin collapsed the synth build to 1 component, so the local ds-bundle genuinely lost the other 53. The "don't feed deletePaths blindly" advice stands (it saved the project), but the root cause is fixed now.

Re-sync 2026-07-03 — 6 floor cards authored + v2 brand fonts (#2267)

source-kit.mjs fork fix #3 (REQUIRED): componentSrcMap pin must AUGMENT synth discovery, not replace it. In synth mode there is no shipped .d.ts, so exportedNames() is empty and names contains ONLY the non-null componentSrcMap pins. The old guard if (!components.length && synthEntry) components = deriveComponentsFromSrc(...) therefore never ran once a single pin existed (ResultPanel, added 2026-07-02) → the whole bundle collapsed to 1 component ((stale preview: X — component no longer exported) for everything else; verdict shows all others as removed + bogus deletePaths). Fix in the fork: when synthEntry, always union deriveComponentsFromSrc(srcFiles) (minus null-excluded) with the pinned names. Any future pin would have re-triggered this. NB: the fork edit re-keys EVERY component's sourceKey → full re-grade pass (done: 47/54 renderHashes byte-identical to the 2026-07-02 anchor → carry-forward grades; the rest eyeballed).

6 floor cards → authored (all graded good):

  • SaleShareControls — inline SaleShareSummary (histogram 7 корзин, coverage 92.1%); порог 8% приглушает нижнюю корзину; все фильтры.
  • SaleShareList — 6 инлайн-домов ЕКБ (heat-бейджи, «аварийный», over_100 «возможно, несколько корпусов», selected-row) + Empty cell. cardMode: column.
  • SaleShareMap — 8 heat-маркеров + открытый popup выбранного дома; OSM-тайлы офлайн не красятся (известно, как MapCard).
  • SectionOverlay (v2) — рендерится contained в relative-«артборде» (absolute-позиционирование против ближайшего positioned ancestor; wrapper height 680 + v2-градиент). 2 cells: HistoryView (04) / AnalyticsView (06) на их встроенных fixtures (data-props не переданы). cardMode: column.
  • LocationDrawer (v2) — open=true в таком же contained-артборде (height 640). cardMode: column.
  • BuildingListingsDrawercreatePortal + .ss-drawer-overlay = position:fixed → cardMode: single (прецедент MapPicker). Шапка богатая (props), тело fetch-coupled (useBuildingListings, data-prop нет) → офлайн честный error-state «Не удалось загрузить объявления дома». Сидировать можно было бы ключом ["sale-share","listings",<house_id>] в PreviewProvider — сознательно НЕ сделано (кэш-ключ завязан на house_id фикстуры превью; хрупко).

v2 brand fonts shipped (Manrope + IBM Plex Mono). woff2 (latin+cyrillic; Manrope variable 200-800, Plex Mono static 300/400/500 — веса из app/v2/layout.tsx) скачаны с Google Fonts → .design-sync/fonts/, @font-face добавлены в brand-fonts.css. ГОЧА: extraFonts-пайплайн (css.mjs extractFonts) извлекает ТОЛЬКО @font-face-блоки — :root{} из brand-fonts.css молча выбрасывается. Маппинг --font-manrope/--font-plex-mono поэтому живёт в preview-provider.tsx (<style> в провайдере — капчер-путь) + задокументирован для design-консюмеров в conventions.md (сниппет :root{...}). После фикса v2-цифры реально в Plex Mono (проверено по sheets ResultPanel/SectionOverlay).

Upload 2026-07-03: НЕ выполнен из worker-сессии — DesignSync MCP-тулов в ней нет (ToolSearch пуст). Verdict готов и чист: ok=true, pendingGrade=0, deletePaths=0 (легитимно — remote-анкор полный, local снова 54 компонента), upload.any=true, components=54 (все re-key'нуты форк-фиксом), bundle+styling+aux=true. Main-сессия: залить ds-bundle по verdict'у (deletePaths пуст — ничего не удалять) и после успеха скопировать свежий ds-bundle/_ds_sync.json.design-sync/.cache/remote-sync.json (новый анкор).