Squash-recovery of fusion/fn-188 onto dev — original branch was based on
main (dccd4fa) due to baseBranch=None drift in Fusion settings; rebase
onto dev surfaced unrelated main-only commits (EMEX/PL24/Corgi) as false
conflicts. This commit applies only FN-188's 23-file changelog patch.
- apps/api: ChangelogModule (controller, service, DTO, spec, schema)
- apps/web: ChangelogTab (timeline + accordion), useChangelog hook, i18n
- packages/shared: changelog Zod schemas + types
- packages/ui: Accordion component + Badge stage variant
- docs/INDEX.md: changelog feature documented
Lint, typecheck, all 169 api tests + 2 web tests pass.
Production build was failing because tsc compiled src/**/__tests__/*.test.tsx files
which reference vitest globals (test/expect/beforeEach) and react-router-dom — neither
imported nor in production deps. Resulted in 8 TS errors at `pnpm build` step inside
Coolify's Docker stage, blocking dev.sase.tr deploys since 2026-05-11 09:12.
Tests still run via vitest (its include pattern is unchanged).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire the autonomous engineering pipeline to operate on dev branch with
no human deploy gate. Pushes to git.semih.ai/root/sase.tr.git dev
trigger the Coolify webhook that redeploys https://dev.sase.tr — the
operator pulls signal from the live URL instead of approving every
merge.
Production (main → sase.tr) stays human-driven: agents never push to
main directly; promotion is an explicit operator decision.
Critical-path policy on dev relaxes for auth/payments/billing/
subscription so the loop can ship fixes there with a regression test;
migrations + dep/config changes still need human ack on dev because
they desync persisted/shared state.
Adds team-charter.md (dev/main topology, severity matrix, routing
rules, council protocol, loop detection) and MEMORY.md updates that
agents consult on every triage. Also gitignore'd fusion's runtime
SQLite/task state so .fusion/agents and .fusion/memory remain the
only authored docs in the tree.
- CATALOG_MAP'e ~40 yeni WMI eklendi (Mitsubishi, Nissan, Volvo, vb.)
- brandFromCatalogCode listesi 55 DB kataloğuna genişletildi
- brand="UNKNOWN" olsa bile catalogCode varsa EMEX sonucu kabul edilir
- emex-catalog: linkPath'li kategorilerde parçalar DB'den yüklenir (scraper bug fix)
- VIN cache: emex_vehicle_vins tablosu ile tekrar VIN'lerde emexdwc.ae atlanır
- getCategoryTree Step 0: VIN cache lookup (~10ms)
- Match sonrası otomatik VIN cache doldurma
- Proaktif cache: ilk decode sonrası matchByName + saveVinCache
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Old queries cached empty arrays for groups and schemas. Bumping cache
key prefix from "pcat:" to "pcat2:" so new correct queries bypass stale
entries without needing manual Redis flush.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
car_groups junction table only has data for 2K of 68K cars. Derive groups
from schema_images.category_id which covers 92.6% of records. Also fix
getSchemaImages to filter by category_id instead of empty group_id.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Expand pcat API with getCars, getCarGroups, getSchemaImages, getSchemaDetail
endpoints. Add frontend routes for the complete flow:
catalog → model → car → groups → schema+parts with hotspots.
Reuses existing SchemaViewer component for interactive part diagrams.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Import parts-catalogs.com dump into pc schema (72 catalogs, 2788 models,
134K groups). Add pcat-catalog service/controller with Redis caching.
Create frontend routes for catalog → model → group navigation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Expand catalog tabs from 2 to 5. Rename existing PL24/Emex labels,
add Sase.Tr, Pcat, Tecdoc as coming-soon placeholders.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When EMEX DB has no vehicle match (Ford EU, Mercedes etc.), instead of
falling back to full on-demand VIN decode (~8s), use the VIN-decode SSD
directly with QuickGroups.aspx?c={catalog}&vid=0&ssd={vinSsd} (~2-3s).
Tested: Ford Focus CB4 VIN SSD returns 376 categories via QuickGroups.
New step 3.5 in the cascade:
1. pathData → matchByName (DB-only, ~50ms)
2. emexVehicleName → matchByName (DB-only, ~50ms)
3. matchBySsd wizard walk (DB, ~2-3s)
3.5. VIN SSD → QuickGroups shortcut (~2-3s) ← NEW
4. Fallback → full on-demand scrape (~8s)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
VIN-decode SSDs are too long/specific — EMEX returns HTML instead of
JSON when passed to GetWizard2. New approach:
1. Start from root SSD ("")
2. Advance through wizard steps (Europe → Passenger → ...)
3. Find pathData name (e.g. "Focus CB4 2008-2011") in model options
4. Use getWizardVehicles for DB matching
For Ford Focus: wizard finds it at step 3, but Ford DB has no EU
models so it still falls back to on-demand scrape. BMW/Renault with
good DB coverage will benefit from this flow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The VIN-decode SSD is model-level, not variant-level. matchBySsd now:
1. Calls GetWizard2 with VIN SSD → gets sales designation options
2. Finds option matching pathData (e.g. "Focus CB4 2008-2011")
3. Uses that option's key SSD for Vehicles.aspx?ft=findByWizard2
4. Matches motor/variant names against DB
Note: Ford Focus still falls back to on-demand scrape because Ford
DB coverage is only 55 US models (no EU models like Focus/Fiesta).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Temporarily disable PartsCatalogs and PL24 decode steps to test
EMEX-only VIN resolution speed and reliability.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When EMEX returns candidates, show VehicleSelectModal for selection.
When EMEX returns a single vehicle, show vehicle info card and
auto-navigate to the category page via /vehicles/decode.
Failed results still show JSON output as before.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ConfigService returns empty string (not the default) when env var
exists but is empty. Add || fallbacks in code and set proper defaults
in docker-compose to prevent invalid proxy URL like http://user:pass@:10000.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Node's global fetch doesn't support the dispatcher option. Use
undici's exported fetch when proxy is configured, which properly
handles ProxyAgent as a dispatcher.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add EMEX_USE_PROXY, EMEX_PROXY_HOST, EMEX_PROXY_USER, EMEX_PROXY_PASS
to both api and worker services so the proxy config reaches the
containers at runtime.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
undici was in devDependencies, causing MODULE_NOT_FOUND crash in
production where only prod deps are installed. Move to dependencies
since emex.service.ts imports ProxyAgent at the top level.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
EMEX server blocks direct server IP requests (geo/bot protection),
causing all HTTP-based VIN decodes to return empty results. Add proxy
support to fetchEmexHtml() using undici ProxyAgent, reusing the same
EMEX_USE_PROXY / EMEX_PROXY_* env vars as the browser service.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
decodeVinOrCandidates returns { type: "notFound" } or { type: "error" }
instead of null, so data !== null was incorrectly marking these as
success. Now explicitly checks emex result type.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
testService() was swallowing errors — services like parts-catalogs catch
internally and return null, so the outer try/catch never fired. Now each
service call has its own try/catch, and null responses get an explicit
error message.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The api client unwraps response.data automatically, which conflicts
with our testService response having its own 'data' field.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Admin-only page at /dashboard/service-test with dropdown to test
each VIN decode service (Corgi, PartsCatalogs, PL24, EMEX, VIN API)
individually or the full cascade. Results shown as JSON on page,
no DB writes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Backend: add `model` query param to wizard-vehicles endpoint
- Backend: 3-tier matching: exact name → name prefix → model fuzzy
- Frontend: simplified wizard UI, show determined params as tags
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace static DB-based dropdown chain with live wizard from emexdwc.ae.
The wizard API (GetWizard2) works without auth and shows all models/options
for each catalog (e.g. AU1587 now shows 103 Audi models vs 11 before).
Backend: proxy GetWizard2 + search DB for matching vehicles after wizard completes.
Frontend: step-by-step wizard with breadcrumb navigation, search, and reset.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Detect available filter fields from actual data per catalog
- Skip fields whose values are substrings of optionsRaw (e.g. driveType
redundant when optionsRaw="LEFT (GSX)")
- Skip fields that duplicate already-added filters (e.g. engine=LHD same
as driveType=LHD in Land Rover)
- Use optionsRaw as primary variant text in vehicle rows
- Works for all 54 catalogs: Renault (structured), Fiat, BMW, Volvo, etc.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dropdowns now auto-detect which fields have data (engine, body type,
transmission, drive type, fuel type, variant) and show only relevant
filters. Each dropdown cascades — selecting a value narrows options
for subsequent filters. Handles both structured options_raw (Renault)
and standard DB fields (Fiat, BMW, Volvo, etc).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Model → Engine → Gearbox dropdown chain narrows 4000+ vehicles down
to a manageable selection. Parses options_raw key:value pairs for
catalogs that have structured data (Renault). Falls back to engine/
transmission fields for others (BMW, Volvo, etc).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Parse emexdwc.ae QuickGroups hierarchy and create a static gid→path mapping
(384 groups). Groups page now shows collapsible tree (Body > Vehicle Front >
Bumper/Parts) instead of flat list. Unmapped groups fall back to flat display.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace stacked images + plain table with interactive SchemaViewer (same as PL24).
Backend transforms response to SchemaViewer-compatible format (Part/SchemaPic types).
Add prev/next pagination for groups with multiple schema images.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Redesign emex_* tables from vehicle-centric to catalog-centric structure with
junction tables (emex_vehicle_group_links, emex_vehicle_part_links). Migrate
92M+ rows from emex DB via postgres_fdw. Add EmexCatalogService for browse
and VIN pre-scraped matching, with Redis caching. Frontend catalog page now
has PL24/Emex tabs with full drill-down routes (brand → vehicle → group → parts).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Helmet's default CSP blocked images from storage.sase.tr (different
origin). Add it to img-src and connect-src directives.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move SPA fallback from main.ts catch-all (didn't work - NestJS 404
handler runs first) to HttpExceptionFilter. Now non-API GET 404s
serve index.html so client-side routing works for /dashboard/* etc.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
NestJS was returning 404 JSON for SPA routes like /dashboard/search
because ServeStaticModule couldn't find a matching file and the request
fell through to NestJS router. Add explicit catch-all GET handler after
app.init() to serve index.html for non-API routes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Traefik was picking the wrong Docker network (_default) for the staging
api container, causing requests to hang. Force it to use the coolify
network which is shared with the Traefik proxy.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Coolify adds its own Traefik labels when FQDN is set on the service.
This allows different domains per environment (sase.tr vs dev.sase.tr).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The 'redis' hostname resolved to coolify-redis (Coolify's internal Redis)
instead of our compose Redis service due to shared coolify network.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add multi-stage Dockerfile (node:22-alpine, pnpm, Chromium for Playwright)
- Add docker-compose.coolify.yml with api, worker, redis services
- Add @nestjs/serve-static for serving Vite frontend from NestJS
- Add .dockerignore
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace Playwright browser with pure HTTP for EMEX VIN decode:
- Vehicles.aspx, QuickGroups.aspx, QuickDetails.aspx all work
without authentication — no session/browser needed
- VIN decode drops from ~10-20s (Playwright) to ~1-5s (HTTP)
- Playwright browser kept as fallback if HTTP fails
Add multi-candidate selection for EMEX (mirrors PartsCatalogs flow):
- EmexCandidate interface matches PcatCar shape so VehicleSelectModal
renders without changes
- decodeVinOrCandidates(): single HTTP fetch returns vehicle OR
candidates (avoids double Vehicles.aspx request)
- decodeVinByIndex(): resolves user-selected candidate by index
- vehicles.service: emexCandidates in VinResolveResult, emexCarIndex
param in decodeVin/resolveVin, resolveEmexCarByIndex() method
- vehicles.controller: @Body("emexCarIndex") accepted
- search.tsx: candidateSource state, handleCandidateSelect sends
emexCarIndex or pcatCarId based on source
Test VIN NM417800006410193 (Fiat SIENA) now shows 2-option modal.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remotion Player + compositions (SchemaDemo, DashboardDemo, EcommerceDemo)
replaced with React.lazy wrappers in routes/index.tsx — now split into
separate chunks loaded only when the landing page sections are visible
- posthog-js static import replaced with a dynamic load() wrapper in
lib/posthog.ts; all exported functions remain fire-and-forget with the
same API signature
- subscription/index.tsx updated to use new setPeopleProperties() helper
instead of the raw posthog instance
Initial chunk: 1,082 KB → 727 KB (-33%), gzip 324 KB → 212 KB
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>