- weekend-deep-sync.ts: fire webhook to N8N_WEBHOOK_URL after each sync cycle
(N8N triggers tippecanoe PMTiles rebuild via SSH on host)
- nginx tile-cache: add stub_status at /status, custom log format with cache status
- Add tile-cache-stats.sh: shows HIT/MISS ratio, cache size, slow tiles
- docker-compose: add N8N_WEBHOOK_URL env var
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cache warming must run from host, not from Docker container.
Use scripts/warm-tile-cache.sh standalone instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ogr2ogr doesn't auto-detect geometry from ST_SimplifyPreserveTopology().
Export raw geometry instead, let tippecanoe handle simplification (--simplification=10).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extend PMTiles to include simplified terenuri (5m tolerance) and cladiri (3m)
- map-viewer: terenuri z13 from PMTiles, z14+ from Martin (live detail)
- map-viewer: cladiri z14 from PMTiles, z15+ from Martin
- Martin sources start at higher minzoom when PMTiles active (less DB load)
- Add warm-tile-cache.sh: pre-populate nginx cache for major cities
- Rebuild script now includes cache warming step after PMTiles upload
- Remove deprecated docker-compose version: "3.8"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Strip parcel number prefix from building labels — "291479-C1" now displays as "C1".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UAT + administrativ layers now served from pre-generated PMTiles (~5ms)
instead of Martin/PostGIS (~200-2000ms) for zoom levels 0-14.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ghcr.io/felt/tippecanoe:latest returns 403 — no public Docker image.
Build tippecanoe from GitHub source in a multi-stage Alpine build.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add nginx reverse proxy cache in front of Martin (2GB, 1h TTL, stale serving, CORS)
- Martin no longer exposes host port — all traffic routed through tile-cache on :3010
- Add PMTiles support in map-viewer.tsx (conditional: NEXT_PUBLIC_PMTILES_URL env var)
- When set: single PMTiles source for UAT + administrativ layers (z0-z14, ~5ms/tile)
- When empty: fallback to Martin tile sources (existing behavior, zero breaking change)
- Add tippecanoe Docker service (profiles: tools) for on-demand PMTiles generation
- Add rebuild-overview-tiles.sh: ogr2ogr export → tippecanoe → MinIO atomic upload
- Install pmtiles npm package for MapLibre protocol registration
Performance impact:
- nginx cache: 10-100x faster on repeat tile requests, zero PostGIS load on cache hit
- PMTiles: sub-10ms overview tiles, zero PostGIS load for z0-z14
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Portainer CE volume mount pitfall (silent empty directory creation)
- Martin Docker tag format change at v1.0 (v prefix dropped)
- UNKNOWN GEOMETRY TYPE log is normal for views
- Bake-into-image pattern for config files in Portainer deployments
- Updated all implementation prompts with Portainer-safe instructions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Portainer CE deploys only docker-compose.yml — ./martin.yaml not present on host,
so Docker creates an empty directory instead of mounting the file. Solution: COPY
martin.yaml into the image at build time, eliminating the volume dependency.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: martin.yaml was never mounted in docker-compose.yml — Martin ran
in auto-discovery mode which dropped cadastral_ref from gis_cladiri tiles.
Changes:
- docker-compose: mount martin.yaml, upgrade Martin v0.15→v1.4.0, use --config
- map-viewer: add cladiriLabel layer (cadastral_ref at z16+), wire into visibility
- martin.yaml: update version comment
- geoportal/: tile server evaluation doc + 3 skill files (vector tiles, PMTiles, MapLibre perf)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New fields extracted from already-fetched documentation/GIS data
(zero extra API calls, no performance impact):
- TIP_INSCRIERE: "Intabulare, drept de PROPRIETATE, dobandit prin..."
- ACT_PROPRIETATE: "hotarare judecatoreasca nr... / contract vanzare..."
- COTA_PROPRIETATE: "1/1" or fractional
- DATA_CERERE: date of registration application
- NR_CORPURI: number of building bodies on parcel
- CORPURI_DETALII: "C1:352mp, C2:248mp, C3:104mp"
- IS_CONDOMINIUM: condominium flag
- DATA_CREARE: parcel creation date in eTerra
Also fixed HAS_BUILDING: now also uses NR_CORPURI count as fallback
(was 0 for parcels where buildingMap cross-ref missed matches).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Building labels (C1/C2/C3) disabled — Martin MVT tiles don't include
cadastral_ref as a property despite the PostgreSQL view exposing it.
Root cause needs investigation (Martin config or alternative tile server).
Removed temporary debug endpoints:
- /api/eterra/debug-tile-props
- /api/eterra/debug-tile-sample
Kept /api/eterra/debug-fields (useful long-term diagnostic).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Removed wrapper function/setTimeout approach. Now uses exact same
inline addLayer pattern as terenuriLabel which is proven to work.
Same source, same font, same coalesce pattern.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Labels were hidden by MapLibre collision detection with terrain
labels. Now using text-allow-overlap + text-ignore-placement to
force visibility. Also added retry with setTimeout in case source
isn't ready when layer is first added.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previous index-of/slice expression wasn't rendering. Simplified to
just show the full cadastral_ref (e.g. "77102-C1") as-is. MapLibre
auto-hides overlapping labels. This is a diagnostic step to verify
the tile property is accessible.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Temporary diagnostic to verify what columns gis_cladiri view exposes
for Martin vector tiles. Needed to debug missing C1/C2 labels.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previous index-of expression wasn't rendering. Simplified to use
filter with index-of on dash + slice from dash position.
Also lowered minzoom from 17 to 16.
Added diagnostic log in enrichment for building cross-ref count
to debug HAS_BUILDING=0 cases.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shows all available eTerra fields for a parcel + buildings:
- GIS layer attributes (raw from ArcGIS)
- Immovable parcel details (intravilan, categories)
- Immovable list entry (address, areas)
- Documentation data (owners, registrations)
- Local DB state (enrichment, sync dates)
Usage: /api/eterra/debug-fields?siruta=161829&cadRef=77102
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extracts body suffix from cadastral_ref (e.g. "77102-C1" → "C1") and
displays as centered label on each building polygon. Only visible at
zoom 17+ to avoid clutter at lower zooms.
Applied to both geoportal map-viewer and parcel-sync map tab.
Uses siruta filter in parcel-sync tab.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The eTerra API returns street and locality as objects ({name: "..."})
not strings. formatAddress now extracts .name correctly.
Also added:
- streetNumber fallback (alongside buildingNo)
- String() safety on addressDescription
- Corruption check: any enrichment containing "[object Object]" is
automatically re-enriched on next cycle
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The container was running on UTC by default — the 1-5 AM window was
actually 4-8 AM Romania time, missing the intended night window.
- Add TZ=Europe/Bucharest + tzdata package to Dockerfile
- Add startup diagnostic logs: server time, timezone, ETERRA creds check
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Same search pattern as parcel-sync module: type name or SIRUTA code,
pick from dropdown, city is added instantly. Already-queued cities
are filtered out from results.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The /wds page was showing 0 cities because the KeyValueStore was empty
until the scheduler ran for the first time. Now the GET endpoint
initializes the queue with the 9 default cities on first access.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Completeness check with real values: features with all "-" values
are now re-enriched instead of being considered "complete"
2. Age-based re-enrichment: features older than 30 days are re-enriched
on next run (catches eTerra data updates)
3. Per-feature try-catch: one feature failing no longer aborts the
entire UAT enrichment — logs warning and continues
4. fetchParcelFolosinte wrapped in try-catch: was a hard failure that
killed the whole enrichment process
5. Workspace resolution logging: warns when immovable list is empty
(wrong workspace), warns on fallback to PK=65
These fixes should progressively improve enrichment coverage toward
100% with each weekend sync cycle.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All sync paths now include both admin layers (LIMITE_INTRAV_DYNAMIC +
LIMITE_UAT) as best-effort alongside terenuri + cladiri:
- export-bundle (hero buttons)
- sync-background (fire-and-forget)
- auto-refresh scheduler (weekday nights)
- weekend deep sync (weekend nights)
- freshness check (export tab badge)
LIMITE_UAT rarely changes so incremental sync will skip it almost
every time, but it stays fresh in the DB freshness check.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- LIMITE_INTRAV_DYNAMIC added to primary layers checked for freshness
- Auto-refresh scheduler and weekend sync now also sync intravilan
- "X vechi" badge shows tooltip with exact layer names and dates
- "Proaspete" badge also shows tooltip with layer details
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Secondary layers (LIMITE_INTRAV, LIMITE_UAT) are synced once and rarely
change. They were causing permanent "1 vechi" badge even after fresh
sync of terenuri+cladiri.
Now canExportLocal and the freshness badge only consider TERENURI_ACTIVE
and CLADIRI_ACTIVE — the layers that actually matter for export.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace \u00ce with actual Î character in JSX text (was rendering as literal \u00cenchide)
- Add onSyncRefresh + onDbRefresh calls when closing bg sync card
- Ensures DB freshness badge updates after background sync completes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sync Incremental:
- Add fetchObjectIds (returnIdsOnly) to eterra-client — fetches only OBJECTIDs in 1 request
- Add fetchFeaturesByObjectIds — downloads only delta features by OBJECTID IN (...)
- Rewrite syncLayer: compare remote IDs vs local, download only new features
- Fallback to full sync for first sync, forceFullSync, or delta > 50%
- Reduces sync time from ~10 min to ~5-10s for typical updates
Smart Export Tab:
- Hero buttons detect DB freshness — use export-local (instant) when data is fresh
- Dynamic subtitles: "Din DB (sync acum Xh)" / "Sync incremental" / "Sync complet"
- Re-sync link when data is fresh but user wants forced refresh
- Removed duplicate "Descarca din DB" buttons from background section
Auto-Refresh Scheduler:
- Self-contained timer via instrumentation.ts (Next.js startup hook)
- Weekday 1-5 AM: incremental refresh for existing UATs in DB
- Staggered processing with random delays between UATs
- Health check before processing, respects eTerra maintenance
Weekend Deep Sync:
- Full Magic processing for 9 large municipalities (Cluj, Bistrita, TgMures, etc.)
- Runs Fri/Sat/Sun 23:00-04:00, round-robin intercalated between cities
- 4 steps per city: sync terenuri, sync cladiri, import no-geom, enrichment
- State persisted in KeyValueStore — survives restarts, continues across nights
- Email status report at end of each session via Brevo SMTP
- Admin page at /wds: add/remove cities, view progress, reset
- Hint link on export tab pointing to /wds
API endpoints:
- POST /api/eterra/auto-refresh — N8N-compatible cron endpoint (Bearer token auth)
- GET/POST /api/eterra/weekend-sync — queue management for /wds page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Personal prompt library at /prompts with:
- 6 categories: Module Work, API & Backend, Quality & Security,
Session & Continue, Documentation & Meta, Quick Actions
- 22 optimized prompt templates for ArchiTools development
- Copy-to-clipboard on every prompt
- One-time prompts with checkbox persistence (localStorage)
- Search/filter across all prompts
- Best practices sidebar (10 tips from Claude Code research)
- Module name quick-copy badges
- Variable placeholders highlighted ({MODULE_NAME}, etc.)
- Deploy prep checklist, debug unknown errors, and more
Not registered as a module — accessible only via direct URL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
These env vars were previously hardcoded in source code and removed during
the production audit. Now properly configured in docker-compose.yml.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Export fix:
- Replaced DropdownMenu with direct DXF/GPKG buttons in SelectionToolbar.
Radix dropdown portals don't work inside fixed z-[110] containers.
Direct buttons work reliably on all platforms.
Mobile RGI cards:
- Single-row compact layout: icon + nr cerere + solicitant + termen + status
- Smaller icons (3.5), tighter spacing, shorter status labels
- No Card wrapper — lightweight border div for less visual weight
Mobile filters:
- Tighter spacing, smaller labels
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Selection toolbar:
- Moved OUTSIDE map container div to a fixed viewport position
(bottom-4, z-[110]). iOS Safari clips absolute elements inside
calc(100vh) containers — fixed positioning solves this.
- Only shown when UAT selected and has data.
Mobile top layout:
- UAT card takes full width (right-2 not right-[140px])
- Basemap switcher at top-[52px] left-2 on mobile (below UAT card)
- Desktop: unchanged (top-right offset from zoom controls)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ZIP download:
- Both portal and RGI test page now create a single ZIP archive
(Documente_eliberate_{appNo}.zip) instead of sequential downloads
- Uses JSZip (already in project dependencies)
Portal mobile:
- Basemap switcher drops below UAT card on mobile (top-14 sm:top-2)
- Selection toolbar at bottom-3 with z-30 (always visible)
- Click on feature centers map on that parcel (flyTo)
Tooltips:
- Green download icon: "Descarca arhiva ZIP cu documentele cererii X"
- Updated on both portal and RGI test page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Freehand drawing fix:
- Disable dragPan when in freehand mode (was only disabling dblclick
zoom). Without this, clicks were interpreted as pan gestures.
- Re-enable dragPan when exiting freehand mode.
Click highlight:
- Clicking a parcel in "off" mode now highlights it with the selection
layer (amber fill + orange outline). Clicking empty space clears it.
- Provides visual feedback for which parcel was clicked.
Mobile toolbar:
- Moved selection toolbar higher (bottom-12 on mobile) with z-20
to ensure it's above MapLibre attribution bar.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SelectionToolbar: new hideEnrichment prop hides the Enrichment button.
Portal uses it to show only Export + Clear in selection toolbar.
Portal feature panel: added disabled "Solicita extras CF" button with
tooltip "Sectiune platita — contacteaza administratorul".
Initial map zoom: starts at zoom 15 (close-up) instead of 7 (Romania
overview). Prevents the UAT boundaries flash before fitBounds runs.
Applied to both ParcelSync Harta tab and Portal.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
RGI (both pages):
- Default sort: Termen descrescator (cel mai in viitor sus)
Portal map:
- Basemap switcher moved left (right-12) to avoid zoom controls overlap
- Selection toolbar moved up (bottom-8) to avoid attribution overlap
- Download button has title tooltip on mobile cards
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixed:
- Added setLayoutProperty to MapLike type (was missing, broke build)
- Replaced FeatureInfoPanel with simple inline panel showing only:
SIRUTA, Nr. cadastral, Suprafata (no enrichment, no CF extract,
no "Actualizeaza" button)
- Fixed unknown type errors in JSX property access
- Hidden basemap boundaries + UAT layers for cleaner map
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The fitBounds effect was re-triggered every time mapReady toggled
(which happened frequently due to the source-checking polling interval).
Now uses boundsFittedForSirutaRef to ensure fitBounds runs only ONCE
per siruta selection — changing UAT still zooms correctly, but manual
zoom/pan is preserved afterwards.
Fixed in both ParcelSync Harta tab and Portal map.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>