14 KiB
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.jsonhas nomain/module/exports. --entrymust point at a NON-existent dist path (tradein-mvp/frontend/dist/index.js). Two jobs:- PKG_DIR resolution walks up
dirname(--entry)to the real namedpackage.json→ PKG_DIR=tradein-mvp/frontend(without--entry, PKG_DIR defaults tonode_modules/<pkg>which doesn't self-install → ENOENT crash). - The path being absent makes
resolveDistEntry(soft)return null →synthEntry=true→deriveComponentsFromSrcdiscovers components fromsrc/. A path that EXISTS (e.g. package.json) suppresses synth mode →[ZERO_MATCH]tokens-only.
- PKG_DIR resolution walks up
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 insrc/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 offlineapp/ui-preview/estimate/fixture.ts). Type imports erased. - Provider = seeded
PreviewProvider(.design-sync/preview-provider.tsx, wired viacfg.extraEntries+cfg.provider). Mirrorsapp/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.envat module top-level (e.g.@/lib/useMe→@/lib/api) — extraEntries evaluate BEFORE the synth-entry.pkg-shim.mjs, soprocessis undefined and the whole IIFE aborts (38/38 vanish from the global). The provider hardcodesME_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.
- CRITICAL: an extraEntries module must NOT import anything that reads
- Chart cards gate on analog count ≥8 (DistributionCard, ExposureCard): the shared fixture has 3 analogs → "not enough data" fallback. Those two previews extend
analogsto 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
createPortalfull-viewport overlay — rendered contained in capture; if a future capture viewport change makes it escape, addcfg.overrides.MapPicker.cardMode/viewport.
Re-sync risks
- The
processshim env defaults live in.design-sync/overrides/source-kit.mjs(synth entry). If the app reads NEWprocess.env.NEXT_PUBLIC_*vars, add them there or components throw. PreviewProviderseed keys are pinned to the fixture'sestimate_idand address/area/rooms — iffixture.tschanges those, updatepreview-provider.tsxto match (else fetch-coupled cards re-floor).- Synth scan re-includes Next route/layout/error files on every build →
componentSrcMapnulls (13 entries) must persist. New app-router pages would need adding. .d.tsprops are weak ([key:string]:unknown) — synth mode has no built types. Real prop contracts live in each component's sourceinterface Props. A realtsup/tsclib 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):
- Exclude
next/fontimporters from the synth-entry.src/app/v2/layout.tsxcallsManrope()/IBM_Plex_Mono()fromnext/font/googleat MODULE TOP-LEVEL. esbuild can't resolve the Next-only loader → stubs it(void 0)→undefined()aborts the whole browser IIFE →window.TradeInUIempty → ALL 58 components vanish ([RENDER] root emptyeverywhere,[BUNDLE_EXPORT]). Fix:compsfilter 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. - Re-export default-exported components.
export * from <path>does NOT re-export a module'sdefault. The v2 views/nav/overlay/panel are authoredexport default function <Name>(AnalyticsView, HeroBar, HistoryView, ParamsPanel, SectionOverlay, SourcesView, TopNav) → absent fromwindow.TradeInUI→[BUNDLE_EXPORT] not a component. Fix: entry now also emitsexport { default as <Name> } from <path>for eachexport 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.ResultPanelpin 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— inlineSaleShareSummary(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.BuildingListingsDrawer—createPortal+.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 (новый анкор).