Merge pull request 'chore(design-sync): durable-инпуты DS-панели tradein — 6 floor-card превью, шрифты Manrope/Plex, source-kit fork-фикс (#2267)' (#2274) from chore/design-sync-tradein into main
This commit is contained in:
commit
4a9ddb2a9a
26 changed files with 469 additions and 42 deletions
|
|
@ -32,3 +32,40 @@ Target: `tradein-mvp/frontend` (NOT the repo-root `frontend/`, which is the Site
|
|||
- 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`.
|
||||
- `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` (новый анкор).
|
||||
|
|
|
|||
|
|
@ -24,7 +24,21 @@
|
|||
"ScraperPage": null,
|
||||
"ScrapersUnifiedPage": null,
|
||||
"TradeInPage": null,
|
||||
"YandexScraperPage": null
|
||||
"YandexScraperPage": null,
|
||||
"TradeInV2Layout": null,
|
||||
"TradeInV2Page": null,
|
||||
"SaleShareLayout": null,
|
||||
"SaleSharePage": null,
|
||||
"ResultPanel": "src/components/trade-in/v2/ResultPanel.tsx"
|
||||
},
|
||||
"overrides": {
|
||||
"MapPicker": { "cardMode": "single", "primaryStory": "Default" },
|
||||
"StreetDealsCard": { "cardMode": "column" },
|
||||
"Topbar": { "cardMode": "column" },
|
||||
"SaleShareList": { "cardMode": "column" },
|
||||
"SectionOverlay": { "cardMode": "column" },
|
||||
"LocationDrawer": { "cardMode": "column" },
|
||||
"BuildingListingsDrawer": { "cardMode": "single", "primaryStory": "Default" }
|
||||
},
|
||||
"provider": {
|
||||
"component": "PreviewProvider"
|
||||
|
|
@ -32,5 +46,8 @@
|
|||
"extraEntries": [
|
||||
"../../.design-sync/preview-provider.tsx"
|
||||
],
|
||||
"readmeHeader": ".design-sync/conventions.md"
|
||||
"readmeHeader": ".design-sync/conventions.md",
|
||||
"extraFonts": [
|
||||
"../../.design-sync/fonts/brand-fonts.css"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,12 @@ This is **not Tailwind and not CSS-in-JS**. Styling is a **global stylesheet** o
|
|||
| Border | `--border-soft` `--border-card` `--border-strong` |
|
||||
| Shape/type | `--radius` `--radius-sm` `--radius-lg` `--shadow-md` `--font-sans` (Manrope) `--font-mono` `--container` |
|
||||
|
||||
**v2 (МЕРА HUD) fonts:** the `v2/*` components read `var(--font-manrope)` / `var(--font-plex-mono)` (in the app these come from `next/font`). The woff2 for both families ships in `fonts/fonts.css`; define the vars once at your design root:
|
||||
|
||||
```css
|
||||
:root { --font-manrope: 'Manrope'; --font-plex-mono: 'IBM Plex Mono'; }
|
||||
```
|
||||
|
||||
```tsx
|
||||
<div style={{ background: "var(--bg-card)", border: "1px solid var(--border-card)",
|
||||
borderRadius: "var(--radius)", padding: 16, color: "var(--fg-primary)",
|
||||
|
|
|
|||
BIN
.design-sync/fonts/IBMPlexMono-300-cyrillic.woff2
Normal file
BIN
.design-sync/fonts/IBMPlexMono-300-cyrillic.woff2
Normal file
Binary file not shown.
BIN
.design-sync/fonts/IBMPlexMono-300-latin.woff2
Normal file
BIN
.design-sync/fonts/IBMPlexMono-300-latin.woff2
Normal file
Binary file not shown.
BIN
.design-sync/fonts/IBMPlexMono-400-cyrillic.woff2
Normal file
BIN
.design-sync/fonts/IBMPlexMono-400-cyrillic.woff2
Normal file
Binary file not shown.
BIN
.design-sync/fonts/IBMPlexMono-400-latin.woff2
Normal file
BIN
.design-sync/fonts/IBMPlexMono-400-latin.woff2
Normal file
Binary file not shown.
BIN
.design-sync/fonts/IBMPlexMono-500-cyrillic.woff2
Normal file
BIN
.design-sync/fonts/IBMPlexMono-500-cyrillic.woff2
Normal file
Binary file not shown.
BIN
.design-sync/fonts/IBMPlexMono-500-latin.woff2
Normal file
BIN
.design-sync/fonts/IBMPlexMono-500-latin.woff2
Normal file
Binary file not shown.
BIN
.design-sync/fonts/Inter-cyrillic.woff2
Normal file
BIN
.design-sync/fonts/Inter-cyrillic.woff2
Normal file
Binary file not shown.
BIN
.design-sync/fonts/Inter-latin.woff2
Normal file
BIN
.design-sync/fonts/Inter-latin.woff2
Normal file
Binary file not shown.
BIN
.design-sync/fonts/JetBrainsMono-cyrillic.woff2
Normal file
BIN
.design-sync/fonts/JetBrainsMono-cyrillic.woff2
Normal file
Binary file not shown.
BIN
.design-sync/fonts/JetBrainsMono-latin.woff2
Normal file
BIN
.design-sync/fonts/JetBrainsMono-latin.woff2
Normal file
Binary file not shown.
BIN
.design-sync/fonts/Manrope-cyrillic.woff2
Normal file
BIN
.design-sync/fonts/Manrope-cyrillic.woff2
Normal file
Binary file not shown.
BIN
.design-sync/fonts/Manrope-latin.woff2
Normal file
BIN
.design-sync/fonts/Manrope-latin.woff2
Normal file
Binary file not shown.
112
.design-sync/fonts/brand-fonts.css
Normal file
112
.design-sync/fonts/brand-fonts.css
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/* Brand fonts shipped with the trade-in DS bundle.
|
||||
* June set: Inter + JetBrains Mono (variable woff2).
|
||||
* v2 HUD set (2026-07-03): Manrope + IBM Plex Mono — the МЕРА v2 typefaces,
|
||||
* loaded in the app via next/font in app/v2/layout.tsx (CSS vars
|
||||
* --font-manrope / --font-plex-mono). next/font is excluded from the synth
|
||||
* bundle, so we ship the woff2 here and map the vars in :root below.
|
||||
* All fonts: latin + cyrillic subsets (ЕКБ addresses are Cyrillic). */
|
||||
|
||||
:root {
|
||||
--font-manrope: 'Manrope';
|
||||
--font-plex-mono: 'IBM Plex Mono';
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 200 800;
|
||||
font-display: swap;
|
||||
src: url('./Manrope-latin.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Manrope';
|
||||
font-style: normal;
|
||||
font-weight: 200 800;
|
||||
font-display: swap;
|
||||
src: url('./Manrope-cyrillic.woff2') format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'IBM Plex Mono';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('./IBMPlexMono-300-latin.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'IBM Plex Mono';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('./IBMPlexMono-300-cyrillic.woff2') format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'IBM Plex Mono';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('./IBMPlexMono-400-latin.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'IBM Plex Mono';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('./IBMPlexMono-400-cyrillic.woff2') format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'IBM Plex Mono';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url('./IBMPlexMono-500-latin.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'IBM Plex Mono';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url('./IBMPlexMono-500-cyrillic.woff2') format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url('./Inter-latin.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
src: url('./Inter-cyrillic.woff2') format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: normal;
|
||||
font-weight: 100 800;
|
||||
font-display: swap;
|
||||
src: url('./JetBrainsMono-latin.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: normal;
|
||||
font-weight: 100 800;
|
||||
font-display: swap;
|
||||
src: url('./JetBrainsMono-cyrillic.woff2') format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
// knob for component inclusion: non-null value = add/pin src path, null =
|
||||
// exclude a .d.ts-exported internal.
|
||||
|
||||
import { existsSync, writeFileSync } from 'node:fs';
|
||||
import { existsSync, writeFileSync, readFileSync } from 'node:fs';
|
||||
import { dirname, join, relative, resolve } from 'node:path';
|
||||
import { Project, Node, ts } from 'ts-morph';
|
||||
// forked from design-sync lib/source-kit.mjs — synth-entry process shim (Next.js app reads process.env.NEXT_PUBLIC_*)
|
||||
|
|
@ -70,7 +70,18 @@ export async function resolvePackage(ctx) {
|
|||
console.error(`[NO_DIST] ${PKG} has no built entry and no src/ to synthesize from — run its build.`);
|
||||
process.exit(1);
|
||||
}
|
||||
const comps = srcFiles.filter((p) => SRC_IMPL_RX.test(p) && !NON_IMPL_RX.test(p));
|
||||
// Next route files (app/**/layout.tsx, page.tsx) that call `next/font`
|
||||
// loaders (Manrope(), IBM_Plex_Mono()) at module top-level crash the browser
|
||||
// IIFE: esbuild can't resolve the Next-only loader → stubs it to `undefined`
|
||||
// → `undefined()` aborts the whole bundle → window.<global> stays empty and
|
||||
// every component vanishes. These files are never DS components anyway, so
|
||||
// drop any `next/font`-importing module from the synth-entry set.
|
||||
const comps = srcFiles.filter(
|
||||
(p) =>
|
||||
SRC_IMPL_RX.test(p) &&
|
||||
!NON_IMPL_RX.test(p) &&
|
||||
!/from\s+['"]next\/font/.test(readFileSync(p, 'utf8')),
|
||||
);
|
||||
// Next.js app code reads process.env.NEXT_PUBLIC_* at module top-level; in
|
||||
// the browser IIFE `process` is undefined → every component throws. Emit a
|
||||
// shim module and import it FIRST (ESM evaluates the first import's body
|
||||
|
|
@ -89,10 +100,25 @@ export async function resolvePackage(ctx) {
|
|||
"__e.NEXT_PUBLIC_TRADEIN_CONTACT_EMAIL ??= 'trade-in@example.com';\n",
|
||||
);
|
||||
entry = join(OUT, '.pkg-entry.mjs');
|
||||
// `export *` does NOT re-export a module's default. Components authored as
|
||||
// `export default function <Name>` (the v2 views/nav/overlay/panel) would be
|
||||
// absent from window.<global> → [BUNDLE_EXPORT] "not a component". Re-export
|
||||
// each named default under its declared name so default-exported components
|
||||
// reach the global alongside the named ones.
|
||||
const defaultReexports = comps
|
||||
.map((p) => {
|
||||
const m = readFileSync(p, 'utf8').match(
|
||||
/export\s+default\s+(?:async\s+)?(?:function|class)\s+([A-Z][A-Za-z0-9]*)/,
|
||||
);
|
||||
return m ? `export { default as ${m[1]} } from ${JSON.stringify(p)};` : null;
|
||||
})
|
||||
.filter(Boolean);
|
||||
writeFileSync(
|
||||
entry,
|
||||
`import ${JSON.stringify(slash(shim))};\n` +
|
||||
comps.map((p) => `export * from ${JSON.stringify(p)};`).join('\n') +
|
||||
'\n' +
|
||||
defaultReexports.join('\n') +
|
||||
'\n',
|
||||
);
|
||||
synthEntry = true;
|
||||
|
|
@ -117,8 +143,17 @@ export async function resolvePackage(ctx) {
|
|||
names.add(k);
|
||||
}
|
||||
let components = [...names].sort().map((name) => ({ name, group: 'general' }));
|
||||
if (!components.length && synthEntry) {
|
||||
components = deriveComponentsFromSrc(srcFiles).filter((c) => srcMap[c.name] !== null);
|
||||
if (synthEntry) {
|
||||
// Synth mode has no shipped .d.ts → `names` holds only componentSrcMap
|
||||
// pins. A non-null pin must AUGMENT src discovery (it exists to pin
|
||||
// enrichment to a specific file), not REPLACE it — with the old
|
||||
// `!components.length` guard a single pin (ResultPanel, 2026-07-02)
|
||||
// collapsed the whole synth bundle to just the pinned component.
|
||||
const derived = deriveComponentsFromSrc(srcFiles).filter((c) => srcMap[c.name] !== null);
|
||||
const have = new Set(components.map((c) => c.name));
|
||||
components = components
|
||||
.concat(derived.filter((c) => !have.has(c.name)))
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
if (!components.length) {
|
||||
if (cfg.cssEntry || existsSync(join(PKG_DIR, 'styles.css'))) {
|
||||
|
|
|
|||
|
|
@ -57,5 +57,17 @@ export function PreviewProvider({ children }: { children: React.ReactNode }) {
|
|||
);
|
||||
return qc;
|
||||
});
|
||||
return <QueryClientProvider client={client}>{children}</QueryClientProvider>;
|
||||
return (
|
||||
<QueryClientProvider client={client}>
|
||||
{/* v2 HUD font vars. The app defines --font-manrope/--font-plex-mono via
|
||||
next/font in app/v2/layout.tsx, which is excluded from the synth
|
||||
bundle (next/font loader crash) — so the vars never exist in capture
|
||||
and tokens.font.* fell back. The @font-face for both families ships
|
||||
in .design-sync/fonts/brand-fonts.css (cfg.extraFonts), but that
|
||||
pipeline extracts @font-face blocks ONLY (a :root{} there is
|
||||
dropped) — map the vars here instead. */}
|
||||
<style>{":root{--font-manrope:'Manrope';--font-plex-mono:'IBM Plex Mono'}"}</style>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
36
.design-sync/previews/BuildingListingsDrawer.tsx
Normal file
36
.design-sync/previews/BuildingListingsDrawer.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { BuildingListingsDrawer } from 'tradein-mvp-frontend';
|
||||
|
||||
// Правый drawer с активными объявлениями выбранного дома (/trade-in/sale-share):
|
||||
// createPortal в document.body, .ss-drawer-overlay = position:fixed по всему
|
||||
// вьюпорту (как MapPicker) → cardMode:single. Шапка — адрес + тепловой бейдж
|
||||
// доли + «N из M квартир» (из props). Тело FETCH-COUPLED: useBuildingListings
|
||||
// (GET /buildings/{id}/listings) — data-prop нет, глобальный query-cache пуст →
|
||||
// в офлайн-capture честный pending/error-state («Загрузка объявлений…» /
|
||||
// «Не удалось загрузить объявления дома»). На реальной странице — список
|
||||
// объявлений с ценой, ₽/м², этажом и ссылкой на оригинал.
|
||||
const building = {
|
||||
house_id: 3101,
|
||||
address: 'ул. Викулова, 46',
|
||||
lat: 56.8412,
|
||||
lon: 60.5556,
|
||||
sale_share_pct: 34.6,
|
||||
sale_share_pct_45d: 41.2,
|
||||
listings_45d: 33,
|
||||
over_100: false,
|
||||
active_secondary: 27,
|
||||
flat_count_effective: 78,
|
||||
gar_match_method: 'cadastre',
|
||||
median_price_rub: 4_950_000,
|
||||
median_price_per_m2: 158_500,
|
||||
avg_days_on_market: 74,
|
||||
year_built: 1972,
|
||||
house_type: 'panel',
|
||||
total_floors: 9,
|
||||
series_name: '1-468',
|
||||
is_emergency: false,
|
||||
};
|
||||
|
||||
/** Открытый drawer дома «ул. Викулова, 46» (34.6% в продаже, 27 из 78 квартир). */
|
||||
export const Default = () => (
|
||||
<BuildingListingsDrawer building={building} onClose={() => {}} />
|
||||
);
|
||||
25
.design-sync/previews/LocationDrawer.tsx
Normal file
25
.design-sync/previews/LocationDrawer.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { LocationDrawer } from 'tradein-mvp-frontend';
|
||||
|
||||
// «ПОЯСНЕНИЕ К РАСЧЁТУ» — правый drawer HUD «МЕРА Оценка» (/trade-in/v2),
|
||||
// открывается с «?» у «КОЭФ. ЛОКАЦИИ» в HeroBar. Честная методика: как
|
||||
// агрегируются источники (Циан/Я.Недвижимость/Авито/Домклик + Росреестр) и
|
||||
// явная плашка «коэффициент локации в разработке». Контент статичный —
|
||||
// данных не принимает, только open/onClose. Drawer absolute-позиционирован
|
||||
// (width 452 + scrim) — рендерим contained внутри relative-обёртки.
|
||||
|
||||
/** Открытое состояние: scrim + выдвинутая панель с методикой и info-плашкой
|
||||
* о локации. */
|
||||
export const Default = () => (
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
height: 640,
|
||||
borderRadius: 10,
|
||||
overflow: 'hidden',
|
||||
background:
|
||||
'radial-gradient(1100px 520px at 30% -10%, #f7fbff 0%, #eef4fa 55%, #e6eef7 100%)',
|
||||
}}
|
||||
>
|
||||
<LocationDrawer open={true} onClose={() => {}} />
|
||||
</div>
|
||||
);
|
||||
|
|
@ -1,36 +1,10 @@
|
|||
import { ResultPanel } from 'tradein-mvp-frontend';
|
||||
|
||||
/** Переиспользуемая панель результата мутации скрапера. Успех → JSON-дамп
|
||||
* ответа в `.scraper-result`. */
|
||||
export const Default = () => (
|
||||
<ResultPanel
|
||||
mut={{
|
||||
isSuccess: true,
|
||||
isPending: false,
|
||||
error: null,
|
||||
data: {
|
||||
run_id: 4821,
|
||||
source: 'avito',
|
||||
status: 'completed',
|
||||
anchors_seen: 64,
|
||||
lots_scraped: 312,
|
||||
houses_matched: 47,
|
||||
detail_fetched: 298,
|
||||
errors: 0,
|
||||
started_at: '2026-05-30T09:12:00Z',
|
||||
finished_at: '2026-05-30T09:48:21Z',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
/** Ветка ошибки — красная панель `.scraper-result--error` с текстом сбоя. */
|
||||
export const Error_ = () => (
|
||||
<ResultPanel
|
||||
mut={{
|
||||
isSuccess: false,
|
||||
isPending: false,
|
||||
error: new Error('502 Bad Gateway: upstream avito proxy timeout'),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
/**
|
||||
* ResultPanel (v2) — центральная панель результата оценки на `/trade-in/v2`:
|
||||
* одна цифра-герой (ожидаемая цена продажи / «оценка»), тиры median + ДКП,
|
||||
* доверительные диапазоны, источники и мини-гистограмма распределения.
|
||||
* Данные по умолчанию берутся из встроенной `RESULT_FIXTURE` компонента,
|
||||
* поэтому карточке достаточно передать `onNavigate` (навигация по секциям).
|
||||
*/
|
||||
export const Default = () => <ResultPanel onNavigate={() => {}} />;
|
||||
|
|
|
|||
54
.design-sync/previews/SaleShareControls.tsx
Normal file
54
.design-sync/previews/SaleShareControls.tsx
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import { SaleShareControls } from 'tradein-mvp-frontend';
|
||||
|
||||
// Панель «Порог и фильтры» страницы /trade-in/sale-share. Controlled-компонент:
|
||||
// состояние живёт в page.tsx и приходит через props. Сводка (гистограмма
|
||||
// распределения sale_share_pct) — реалистичные значения по покрытию ЕКБ
|
||||
// (view v_building_sale_share ≈ 6.7k домов вторички, знаменатель ГАР у ~92%).
|
||||
const summary = {
|
||||
total_secondary_buildings: 6667,
|
||||
buildings_with_denominator: 6143,
|
||||
coverage_pct: 92.1,
|
||||
max_pct: 42.0,
|
||||
p95_pct: 11.3,
|
||||
histogram: [
|
||||
{ bucket: '0-5', count: 4980 },
|
||||
{ bucket: '5-10', count: 642 },
|
||||
{ bucket: '10-20', count: 298 },
|
||||
{ bucket: '20-30', count: 121 },
|
||||
{ bucket: '30-50', count: 57 },
|
||||
{ bucket: '50-100', count: 18 },
|
||||
{ bucket: '100+', count: 6 },
|
||||
],
|
||||
};
|
||||
|
||||
const HOUSE_TYPES = ['panel', 'brick', 'monolith', 'monolith_brick', 'block', 'stalin'];
|
||||
|
||||
/** Порог 8% на слайдере: корзины ниже порога приглушены (opacity), тепловая
|
||||
* окраска столбцов green→red повторяет цвет маркеров карты. Фильтры: город,
|
||||
* цена, год постройки, тип дома, мин. объявлений, сортировка. */
|
||||
export const Default = () => (
|
||||
<SaleShareControls
|
||||
summary={summary}
|
||||
minPct={8}
|
||||
onMinPct={() => {}}
|
||||
city="Екатеринбург"
|
||||
onCity={() => {}}
|
||||
priceMin=""
|
||||
onPriceMin={() => {}}
|
||||
priceMax=""
|
||||
onPriceMax={() => {}}
|
||||
yearMin=""
|
||||
onYearMin={() => {}}
|
||||
yearMax=""
|
||||
onYearMax={() => {}}
|
||||
houseType=""
|
||||
onHouseType={() => {}}
|
||||
houseTypeOptions={HOUSE_TYPES}
|
||||
sort="share_desc"
|
||||
onSort={() => {}}
|
||||
window="now"
|
||||
onWindow={() => {}}
|
||||
minCount={3}
|
||||
onMinCount={() => {}}
|
||||
/>
|
||||
);
|
||||
50
.design-sync/previews/SaleShareList.tsx
Normal file
50
.design-sync/previews/SaleShareList.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { SaleShareList } from 'tradein-mvp-frontend';
|
||||
|
||||
// Список домов /trade-in/sale-share, отсортирован по доле в продаже (server-side
|
||||
// share_desc). Реалистичные дома ЕКБ (view v_building_sale_share): адрес,
|
||||
// тепловой бейдж доли, «N из M квартир», медиана + ₽/м², экспозиция,
|
||||
// год/тип/этажность/серия. Включены оба edge-бейджа: «аварийный» и
|
||||
// «возможно, несколько корпусов» (over_100 — ГАР-коллизия адреса).
|
||||
const buildings = [
|
||||
{ house_id: 4212, address: 'ул. Космонавтов, 52', lat: 56.8890, lon: 60.6132, sale_share_pct: 128.0, sale_share_pct_45d: 131.5, listings_45d: 46, over_100: true, active_secondary: 32, flat_count_effective: 25, gar_match_method: 'address', median_price_rub: 3_650_000, median_price_per_m2: 121_700, avg_days_on_market: 96, year_built: 1961, house_type: 'brick', total_floors: 5, series_name: null, is_emergency: false },
|
||||
{ house_id: 3387, address: 'пер. Сапёров, 5', lat: 56.8271, lon: 60.6198, sale_share_pct: 48.9, sale_share_pct_45d: 52.4, listings_45d: 24, over_100: false, active_secondary: 22, flat_count_effective: 45, gar_match_method: 'cadastre', median_price_rub: 2_990_000, median_price_per_m2: 98_400, avg_days_on_market: 148, year_built: 1957, house_type: 'brick', total_floors: 3, series_name: null, is_emergency: true },
|
||||
{ house_id: 3101, address: 'ул. Викулова, 46', lat: 56.8412, lon: 60.5556, sale_share_pct: 34.6, sale_share_pct_45d: 41.2, listings_45d: 33, over_100: false, active_secondary: 27, flat_count_effective: 78, gar_match_method: 'cadastre', median_price_rub: 4_950_000, median_price_per_m2: 158_500, avg_days_on_market: 74, year_built: 1972, house_type: 'panel', total_floors: 9, series_name: '1-468', is_emergency: false },
|
||||
{ house_id: 2874, address: 'ул. Бебеля, 138', lat: 56.8664, lon: 60.5721, sale_share_pct: 22.4, sale_share_pct_45d: 25.0, listings_45d: 20, over_100: false, active_secondary: 17, flat_count_effective: 76, gar_match_method: 'cadastre', median_price_rub: 5_400_000, median_price_per_m2: 149_200, avg_days_on_market: 61, year_built: 1978, house_type: 'panel', total_floors: 9, series_name: '141', is_emergency: false },
|
||||
{ house_id: 5530, address: 'ул. Малышева, 84', lat: 56.8380, lon: 60.6203, sale_share_pct: 12.1, sale_share_pct_45d: 13.8, listings_45d: 16, over_100: false, active_secondary: 14, flat_count_effective: 116, gar_match_method: 'cadastre', median_price_rub: 7_850_000, median_price_per_m2: 172_300, avg_days_on_market: 47, year_built: 1954, house_type: 'stalin', total_floors: 6, series_name: null, is_emergency: false },
|
||||
{ house_id: 6119, address: 'ул. Щербакова, 20', lat: 56.7791, lon: 60.6120, sale_share_pct: 6.8, sale_share_pct_45d: 8.1, listings_45d: 22, over_100: false, active_secondary: 18, flat_count_effective: 264, gar_match_method: 'cadastre', median_price_rub: 8_900_000, median_price_per_m2: 164_800, avg_days_on_market: 38, year_built: 2016, house_type: 'monolith', total_floors: 25, series_name: null, is_emergency: false },
|
||||
];
|
||||
|
||||
/** Полная таблица (6 домов, выбран «ул. Викулова, 46» — строка is-selected),
|
||||
* сортировка по доле, кнопка «Объявления →» ведёт в drawer. */
|
||||
export const Default = () => (
|
||||
<SaleShareList
|
||||
buildings={buildings}
|
||||
sort="share_desc"
|
||||
onSort={() => {}}
|
||||
selectedHouseId={3101}
|
||||
hoveredHouseId={null}
|
||||
onSelect={() => {}}
|
||||
onHover={() => {}}
|
||||
isLoading={false}
|
||||
isError={false}
|
||||
minPct={5}
|
||||
window="now"
|
||||
/>
|
||||
);
|
||||
|
||||
/** Пустой результат — фильтры отсекли все дома (честный empty-state). */
|
||||
export const Empty = () => (
|
||||
<SaleShareList
|
||||
buildings={[]}
|
||||
sort="share_desc"
|
||||
onSort={() => {}}
|
||||
selectedHouseId={null}
|
||||
hoveredHouseId={null}
|
||||
onSelect={() => {}}
|
||||
onHover={() => {}}
|
||||
isLoading={false}
|
||||
isError={false}
|
||||
minPct={25}
|
||||
window="now"
|
||||
/>
|
||||
);
|
||||
30
.design-sync/previews/SaleShareMap.tsx
Normal file
30
.design-sync/previews/SaleShareMap.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { SaleShareMap } from 'tradein-mvp-frontend';
|
||||
|
||||
// Тепловая карта домов /trade-in/sale-share (Leaflet + OSM с CDN, как
|
||||
// MapCard/MapPicker). Цвет и радиус circleMarker растут с sale_share_pct
|
||||
// (green → red, over_100 → тёмно-красный). 8 домов ЕКБ с координатами;
|
||||
// выбранный дом (selectedHouseId) получает открытый popup. OSM-тайлы —
|
||||
// внешний fetch: в офлайн-capture подложка может не прогрузиться,
|
||||
// маркеры/popup/контролы рендерятся всегда.
|
||||
const buildings = [
|
||||
{ house_id: 4212, address: 'ул. Космонавтов, 52', lat: 56.8890, lon: 60.6132, sale_share_pct: 128.0, sale_share_pct_45d: 131.5, listings_45d: 46, over_100: true, active_secondary: 32, flat_count_effective: 25, gar_match_method: 'address', median_price_rub: 3_650_000, median_price_per_m2: 121_700, avg_days_on_market: 96, year_built: 1961, house_type: 'brick', total_floors: 5, series_name: null, is_emergency: false },
|
||||
{ house_id: 3387, address: 'пер. Сапёров, 5', lat: 56.8271, lon: 60.6198, sale_share_pct: 48.9, sale_share_pct_45d: 52.4, listings_45d: 24, over_100: false, active_secondary: 22, flat_count_effective: 45, gar_match_method: 'cadastre', median_price_rub: 2_990_000, median_price_per_m2: 98_400, avg_days_on_market: 148, year_built: 1957, house_type: 'brick', total_floors: 3, series_name: null, is_emergency: true },
|
||||
{ house_id: 3101, address: 'ул. Викулова, 46', lat: 56.8412, lon: 60.5556, sale_share_pct: 34.6, sale_share_pct_45d: 41.2, listings_45d: 33, over_100: false, active_secondary: 27, flat_count_effective: 78, gar_match_method: 'cadastre', median_price_rub: 4_950_000, median_price_per_m2: 158_500, avg_days_on_market: 74, year_built: 1972, house_type: 'panel', total_floors: 9, series_name: '1-468', is_emergency: false },
|
||||
{ house_id: 2874, address: 'ул. Бебеля, 138', lat: 56.8664, lon: 60.5721, sale_share_pct: 22.4, sale_share_pct_45d: 25.0, listings_45d: 20, over_100: false, active_secondary: 17, flat_count_effective: 76, gar_match_method: 'cadastre', median_price_rub: 5_400_000, median_price_per_m2: 149_200, avg_days_on_market: 61, year_built: 1978, house_type: 'panel', total_floors: 9, series_name: '141', is_emergency: false },
|
||||
{ house_id: 5530, address: 'ул. Малышева, 84', lat: 56.8380, lon: 60.6203, sale_share_pct: 12.1, sale_share_pct_45d: 13.8, listings_45d: 16, over_100: false, active_secondary: 14, flat_count_effective: 116, gar_match_method: 'cadastre', median_price_rub: 7_850_000, median_price_per_m2: 172_300, avg_days_on_market: 47, year_built: 1954, house_type: 'stalin', total_floors: 6, series_name: null, is_emergency: false },
|
||||
{ house_id: 6119, address: 'ул. Щербакова, 20', lat: 56.7791, lon: 60.6120, sale_share_pct: 6.8, sale_share_pct_45d: 8.1, listings_45d: 22, over_100: false, active_secondary: 18, flat_count_effective: 264, gar_match_method: 'cadastre', median_price_rub: 8_900_000, median_price_per_m2: 164_800, avg_days_on_market: 38, year_built: 2016, house_type: 'monolith', total_floors: 25, series_name: null, is_emergency: false },
|
||||
{ house_id: 5871, address: 'ул. Крауля, 44', lat: 56.8443, lon: 60.5610, sale_share_pct: 17.9, sale_share_pct_45d: 19.6, listings_45d: 14, over_100: false, active_secondary: 12, flat_count_effective: 67, gar_match_method: 'cadastre', median_price_rub: 5_150_000, median_price_per_m2: 152_900, avg_days_on_market: 58, year_built: 1980, house_type: 'panel', total_floors: 9, series_name: '141', is_emergency: false },
|
||||
{ house_id: 6402, address: 'ул. 8 Марта, 190', lat: 56.8043, lon: 60.6094, sale_share_pct: 3.4, sale_share_pct_45d: 4.0, listings_45d: 11, over_100: false, active_secondary: 9, flat_count_effective: 262, gar_match_method: 'cadastre', median_price_rub: 9_300_000, median_price_per_m2: 176_400, avg_days_on_market: 33, year_built: 2019, house_type: 'monolith', total_floors: 26, series_name: null, is_emergency: false },
|
||||
];
|
||||
|
||||
/** Карта ЕКБ: 8 тепловых маркеров, выбран «ул. Викулова, 46» — открыт popup
|
||||
* (адрес, % в продаже, N из M квартир). */
|
||||
export const Default = () => (
|
||||
<SaleShareMap
|
||||
buildings={buildings}
|
||||
selectedHouseId={3101}
|
||||
hoveredHouseId={null}
|
||||
onSelect={() => {}}
|
||||
onHover={() => {}}
|
||||
/>
|
||||
);
|
||||
39
.design-sync/previews/SectionOverlay.tsx
Normal file
39
.design-sync/previews/SectionOverlay.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { SectionOverlay } from 'tradein-mvp-frontend';
|
||||
|
||||
// Glass/blur оверлей секций HUD «МЕРА Оценка» (/trade-in/v2): скобки-уголки,
|
||||
// шапка «номер секции + заголовок + ← К ОЦЕНКЕ», скроллируемое тело со
|
||||
// сменной view. Позиционируется absolute относительно артборда — в preview
|
||||
// рендерим contained внутри relative-обёртки с фоном v2-страницы.
|
||||
// Данные не передаём: каждая view падает на свой встроенный fixture-набор
|
||||
// (HISTORY_FIXTURE / ANALYTICS_FIXTURE …) — как storybook/unwired usage.
|
||||
|
||||
const Artboard = ({ children }: { children?: unknown }) => (
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
height: 680,
|
||||
borderRadius: 10,
|
||||
overflow: 'hidden',
|
||||
background:
|
||||
'radial-gradient(1100px 520px at 30% -10%, #f7fbff 0%, #eef4fa 55%, #e6eef7 100%)',
|
||||
}}
|
||||
>
|
||||
{children as React.ReactNode}
|
||||
</div>
|
||||
);
|
||||
|
||||
/** Секция 04 «ПРОДАЖИ В ДОМЕ» (active=1 → HistoryView на fixture-данных):
|
||||
* таблица ДКП-продаж дома с ценами и датами. */
|
||||
export const HistorySection = () => (
|
||||
<Artboard>
|
||||
<SectionOverlay active={1} onClose={() => {}} onNavigate={() => {}} />
|
||||
</Artboard>
|
||||
);
|
||||
|
||||
/** Секция 06 «АНАЛИТИКА ДОМА» (active=3 → AnalyticsView): KPI дома,
|
||||
* динамика цены, недавние продажи. */
|
||||
export const AnalyticsSection = () => (
|
||||
<Artboard>
|
||||
<SectionOverlay active={3} onClose={() => {}} onNavigate={() => {}} />
|
||||
</Artboard>
|
||||
);
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,4 +1,4 @@
|
|||
# Memory graph (hardlink to C:\mcp\.aim\memory-gendesign.jsonl)
|
||||
# Memory graph (hardlink to C:\mcp\.aim\memory-gendesign.jsonl)
|
||||
*.jsonl
|
||||
!memory/memory-gendesign.jsonl
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue