Commit Graph

74 Commits

Author SHA1 Message Date
Claude VM 8d5316dd1b feat(geoportal-v2): map styling parity with eterra.live
Marius didn't like the previous map palette + missing labels.
Re-paint to match eterra.live's V2 map:

UATs:
  Was: #9ca3af / #6b7280 (two-tone gray)
  Now: #7c3aed violet, width ramps 0.3 → 0.6 by zoom band
  Plus new v2-uats-z8-label symbol layer that prints the UAT name
  at z9–12 (Noto Sans 10px, violet text + white halo).

Parcele (terenuri):
  Was: #0ea5e9 sky-blue line, transparent 0.001 fill (hit-only)
  Now: #22c55e green fill at 0.15 opacity (so the click area is
  visible at the same time it acts as hit-target) + #15803d
  darker green outline at 0.8 / 0.9 opacity. Cadastral-ref label
  layer renders at z17+ (small enough zoom that the polygon can
  carry a 10px label without overlapping neighbours).

Cladiri (buildings):
  Was: #fb923c orange / #ea580c dark orange
  Now: #3b82f6 blue fill at 0.5 / #1e3a5f navy line — same blue
  eterra.live uses, distinct from the green parcel underlay and
  from the violet UATs.

Cladiri „fără acte" (build_legal == 0):
  NEW. Two amber overlay layers, filtered on the tile's
  build_legal property:
    v2-cladiri-unreg-fill — #f59e0b at 0.6 opacity
    v2-cladiri-unreg-line — #b45309 at width 1
  Renders on top of the default cladiri layers so illegal builds
  visibly pop. If a PMTiles tile doesn't carry build_legal yet
  the layer is empty — no regression on legal buildings.

Building suffix label (C1, C2, C3…):
  NEW. v2-cladiri-label symbol at z16+. Mirrors eterra.live's
  expression: extract the slice after the last "-" of
  cadastral_ref ("354686-C1" → "C1"). Noto Sans 9px, navy text +
  white halo so it reads on both light and dark basemaps.

Click handler unchanged — v2-cladiri-fill covers ALL buildings
(no filter), so legal vs unreg both route through the same query.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:08:41 +03:00
Claude VM 52e16e7807 fix(cf-modal): portal to body + auto-close on parcel switch
Two related issues with the modal when the user kept clicking
around the map while in CF order mode:

1. LAYOUT BREAK (Marius screenshot — modal header clipped above
   viewport): The V2 panel wrapper uses `backdrop-blur-md`. Per CSS
   spec, an element with non-none backdrop-filter establishes a
   containing block for `fixed`-positioned descendants. So
   `fixed inset-0` on the modal was relative to the panel
   (top-right, ~50px tall at min) instead of the viewport — the
   modal anchored to the panel and overflowed up. Fix: render via
   React's createPortal to document.body. The modal now escapes the
   panel's stacking context entirely and centers in the viewport.

   Also bumped z-index from 50 to 100 so the modal stays above the
   MapLibre canvas + panel itself.

2. STATE CARRY-OVER: clicking a different parcel while the modal
   was open silently re-targeted the modal at the new parcel — same
   modal showing different cadref/sold mid-flow could mislead the
   user about which parcel they were buying CF for. Fix:
   FeatureInfoPanel now has a useEffect that closes the modal when
   feature.cadastralRef / siruta / layerId changes. Modal stays
   scoped to a single decision.

SSR guard: if (typeof document === "undefined") return null; before
the portal call so the modal doesn't blow up during server-side
render.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 08:30:26 +03:00
Claude VM ad89da690f fix(cf-modal): inline auto-connect + credential form — no parcel-sync hop
Marius: don't kick the user out to /parcel-sync just to connect ePay,
do everything inside the modal. The parcel-sync page also wasn't
helpful when reached (UAT selector empty), so the redirect was a
dead-end anyway.

State machine rewrite:

  loading-status → GET /api/ancpi/session
  not-connected  → DELETED (replaced by transparent flow)
  connecting     → POST /api/ancpi/session with {} — server picks up
                   ANCPI_USERNAME/ANCPI_PASSWORD from env (Infisical
                   has them in /architools), connects silently
  need-credentials → only if env creds are missing OR invalid: shows
                   an inline form (username / password / Conectează
                   button + privacy note "nu sunt păstrate la noi
                   după sfârșitul sesiunii")
  no-credits / ready / placing / processing / done / error — as before

Flow for the happy path (Marius's case): user clicks "Comandă CF" →
modal shows "Conectare la ePay ANCPI…" for ~1s → "Verificare credite"
done → "Ești sigur? 1 credit, mai ai X" → confirm → animated steps
→ done. Zero page navigations.

Flow for the no-env case (other tenants or first-run): user sees
inline form, types credentials, presses Conectează → server stores
them in the in-memory session for the lifetime of the request,
modal continues straight to "ready".

Removed:
- goToParcelSync() handler + "Conectează ePay" deep-link button
- "not-connected" UI panel
- Phase value "not-connected" (no longer reachable)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 08:10:33 +03:00
Claude VM 5e4618b309 feat(geoportal-v2): inline CF order modal — confirmation + animated steps
Marius: click "Comandă CF" from the card itself, no new-tab to
parcel-sync. Show "Ești sigur? Costă 1 credit, mai ai X" first.
Animate the order through its phases until done.

New component cf-order-modal.tsx — a 7-state machine over a single
shadcn-style dialog:

  loading-status — checks /api/ancpi/session for connection + credits
  not-connected  — ePay session offline → prompt to connect via
                   parcel-sync (the only place credentials live)
  no-credits     — 0 credits, can't proceed
  ready          — confirmation: 1 credit cost, current balance,
                   projected balance after the order, all in
                   rounded chips with Coins icon
  placing        — POST /api/ancpi/order, spinner on step 1
  processing     — poll /api/ancpi/orders every 3s until status
                   becomes completed/done/minioPath populated.
                   Shows live elapsed seconds; 90s timeout falls
                   through to error with "verifică din nou peste
                   câteva minute".
  done           — checkmark anim + "Descarcă PDF" if document URL
                   came back
  error          — destructive panel + Reîncearcă button

Animations (tailwindcss-animate utilities):
  - Modal backdrop: fade-in 200ms
  - Modal card: zoom-in-95 + slide-in-from-bottom 200ms
  - Step rows: active row gets primary-tinted bg + Loader2 spin,
    done rows turn emerald + Check icon zooms in 300ms
  - Success/error final state: rounded badge + icon zooms in 500ms

Footer adapts per phase: Anulează+Confirmă (ready), Conectează ePay
(not-connected), Închide (loading/no-credits), Închide fereastra
(placing/processing — order continues in bg), Gata (done), Închide+
Reîncearcă (error).

Wires into feature-info-panel by replacing the "open /parcel-sync"
click handler with setCfModalOpen(true). Modal mounts at the
panel's root with fixed positioning + z-50 so it overlays the map.
Backdrop click dismisses except during placing/processing.

Uses the legacy /api/ancpi/* endpoints (not /api/cf/* gis-ac route)
per Marius's earlier decision to keep credit tracking on his own
ePay session.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 07:53:15 +03:00
Claude VM 8f86bab337 fix(geoportal-v2): remove eterra.live links + fix Actualizează wrap
Per Marius's feedback: eterra.live is a separate product, ArchiTools
shouldn't link out to it. Removed both touch-points:

1. The "eterra.live" button I'd added beside "Actualizează" in the
   Date eTerra header — gone. This was also breaking layout (the
   second button forced "acum câteva secunde" to wrap into "acum /
   câteva / secunde" stacked above the button text).

2. The "Export GPKG" action in the toolbar — gone. It used to deep-
   link to eterra.live/harta?…&autoexport=geopackage. Toolbar now
   holds just "Comandă CF" which stays internal (/parcel-sync).

While in there:
- Actualizează button: `whitespace-nowrap` + `shrink-0` so it stops
  wrapping when the panel is at min-width.
- Dropped the inline " · acum X min" beside the button label —
  cleaner button, less truncation risk.
- Resurfaced the relative time as a small line at the bottom of the
  Date eTerra body ("Actualizat din ANCPI · acum 3 min"). Same info,
  no layout pressure.
- Cleaned unused lucide imports (Download, ExternalLink, Hash,
  Layers, CalendarDays) that were leftover from the removed
  eterra.live button + Cladire icon experiments.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 07:44:12 +03:00
Claude VM 3004790ad2 feat(geoportal-v2): cladire characteristics + eterra.live link + collapsible Înscriere
Per Marius's iteration on the panel:

1. AUTO-ENRICH DISABLED — the panel no longer fires /parcel/enrich on
   sparse-data load. Refresh only via the explicit "Actualizează"
   button in the Date eTerra header. Keeps the eTerra account pool
   safe from browse-spam (was draining 500/h on rapid clicks).

2. eterra.live link button — sits beside "Actualizează" in the Date
   eTerra header. Opens https://eterra.live/harta?siruta=...&cad=...
   in a new tab so the user can cross-check the full eterra.live
   panel.

3. ÎNSCRIERE collapsible — Tip înscriere / Data cererii / Act
   proprietate now hide inside a <details> closed by default (per
   the highlighted-block screenshot). Keeps the "above the fold"
   info trimmed to what matters at a glance.

4. CARACTERISTICI CORP — new section, only rendered for
   CLADIRI_ACTIVE clicks. Shows the cladire-specific enrichment
   fields the orchestrator populates after a deep-enrich:
     - Chip row: tip / destinație / subtype (chips) + Condominium
       chip with unit count + Cu/Fără acte status pill
     - 3-col metric strip: Regim înălțime / Niveluri / An construire
     - Suprafață CF, CF IE, Clasă energetică, Părți comune (rows)
     - Observații (multi-line InfoBlock)

   Fields wired: CLADIRE_TYPE, CLADIRE_DESTINATIE, CLADIRE_SUBTYPE,
   CLADIRE_REGIM, CLADIRE_NIVELURI, CLADIRE_AN_CONSTRUIRE,
   CLADIRE_AREA_CF, CLADIRE_OBSERVATII, CLADIRE_LANDBOOK_IE,
   CLADIRE_COMMON_PARTS, CLADIRE_UNITS_NO_ANCPI,
   CLADIRE_ENERGETIC_CLASS, IS_LEGAL_BUILDING, IS_CONDOMINIUM.
   All show only when populated (no empty "-" rows). LABEL map
   extended with Romanian translations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 07:36:01 +03:00
Claude VM 4f38fd1070 feat(geoportal-v2): compact eterra.live-style layout + buildings list
Significant restructure of the parcel info panel based on Marius's
side-by-side comparison with eterra.live. Same data + same workflow,
much denser layout.

Layout changes:

1. HEADER — status dot + cadref + small uat/area/SIRUTA line. Removed
   the redundant "Activ"/"Inactiv" chip (the dot is the signal).
   Building suffixes (C1, C2…) still resolve via the search-by-cadref
   path; header shows the full cadref ("354686-C1") so the user sees
   both parent and suffix.

2. CARACTERISTICI — chips row only; tighter padding (px-2 py-1.5
   vs px-3 py-2.5). Same intravilan / categorie / corpuri.

3. METRIC STRIP — new. Three cells in a single divided pill: GIS /
   2D eTerra / Legală. Same pattern eterra.live uses. Saves a whole
   section worth of vertical space.

4. DATE ETERRA CARD — wrapped in a bordered subtle-bg container with
   the refresh button INLINE in the section header (vs at the bottom
   of the panel). Shows "acum X min" relative time when enriched.
   Two-column NR. CF + Nr. topo. Adresă with pin icon. Solicitant
   with user icon. Proprietari as InfoBlock (multi-line preserved).
   Foști proprietari as <details> collapsible (closed by default).
   Înscriere group (tip / data / act) as a small subsection.

5. CONSTRUCȚII LIST — new. For TERENURI parcels, fetches the
   building siblings via gisApi.search(parentCadref) + filter on
   "<parent>-" prefix. Renders BuildingRow per cladire:
     - Icon (Home / Building2 / Factory / Warehouse from destinatie)
     - C1/C2/C3… suffix (mono, font-semibold)
     - Area
     - "Cu acte" (green) / "Fără acte" (amber) / "Necunoscut"
       pill from BUILD_LEGAL / PARCEL_HAS_LANDBOOK enrichment
   Click row → onSelectFeature switches panel to that building.
   Lazy isLegal hydration: row first shows "Necunoscut", then
   parallel parcela.get for each building fills the pill (5ms per
   cache hit, no blocking).

6. APARTAMENTE — same content as before (for CLADIRI clicks), now
   sits beside Construcții in the same flow. Header consistent with
   the other section labels.

7. LOCALIZARE — moved to a single tight strip (lat/lng + Google
   Maps link). Removed the SIRUTA repetition since it's already in
   the header.

8. ACTIONS toolbar — compressed. Removed the in-toolbar "Citește din
   ANCPI" button since the refresh button is now inside the Date
   eTerra card header (where it belongs contextually). Kept Export
   GPKG + Comandă CF.

GeoportalV2 wires onSelectFeature={setClicked} so building rows
propagate. Building clicks reuse the same panel + same auto-enrich
flow — the second feature.layerId === CLADIRI_ACTIVE branch in the
condo-owners useEffect kicks in for buildings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 06:57:27 +03:00
Claude VM a4f61bf3d8 feat(geoportal-v2): manual fetch flag + friendlier pool-exhausted error
Two operational gaps observed after PR3 deep-enrich rollout:

1. Raw \"Eroare: no_available_account\" surfaced when the eTerra
   account pool hit its hourly quota. Replace with a plain-language
   note ("Pool-ul ANCPI e temporar epuizat — încearcă peste câteva
   minute"). Same friendly treatment for the other common
   orchestrator errors: no_immovable_match, parcel_not_found,
   eterra_fetch_failed.

2. Marius wants the auto-trigger (fires on sparse-data load) and the
   explicit "Citește din ANCPI" button to be separable on the
   orchestrator side. Casual map browsing burns through the 500/h
   quota with auto-triggers; a working session that needs 20-30
   specific parcels shouldn't be starved.

   refreshFromAncpi now takes { manual?: boolean }. The button passes
   manual: true → request body includes manualOverride: true. The
   auto-trigger useEffect calls it with no argument (manual defaults
   to false). gis-api / orchestrator can later route manualOverride
   to a separate-quota bucket or skip the per-hour check entirely.
   Until then the flag is harmless (orchestrator ignores unknown
   fields).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 22:44:25 +03:00
Claude VM 02a466ccaa feat(geoportal-v2): swap refresh path to /parcel/enrich (deep-enrich)
gis-api session shipped PR3 (gis-api 09f1ab8 + gis-sync-orchestrator
0371d81): new POST /api/v1/parcel/enrich does the full eTerra
round-trip (searchImmovableByIdentifier → fetchDocumentationData
→ fetchImmovableParcelDetails) and merges NR_CF / ADRESA / PROPRIETARI
+ 20-plus fields into gis_core.GisFeature.enrichment with a 30-day
cache. Verified on 266888 + 328607 → 27 keys with full PII.

Wired in three places:

1. src/lib/gis-api-client.ts — gisApi.parcel.enrich({siruta,
   cadastralRef, force?}) thin wrapper.

2. src/app/api/gis/parcel/enrich/route.ts — architots-side proxy,
   matches the parcel/tech pattern (auth check → forward → bubble up
   GisApiError status codes).

3. src/modules/geoportal/v2/feature-info-panel.tsx — refreshFromAncpi
   now POSTs to /api/gis/parcel/enrich instead of /api/gis/parcel/tech.
   After the orchestrator returns, the panel re-fetches the canonical
   record via parcela.get (when uuid known) or parcela.find (when
   not), so it sees exactly what gis_core stores rather than the
   orchestrator response shape.

The existing auto-trigger (fires when detail has no NR_CF/ADRESA/
PROPRIETARI) now actually fills those fields. Subsequent clicks on the
same parcel hit gis-api's 30-day cache (5ms vs 1-2s live fetch).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 22:24:02 +03:00
Claude VM 87f9d72e4f feat(geoportal-v2): auto-fetch enrichment when DB only has tech keys
Parcel 328607 in Cluj-Napoca (and many others) is in gis_core with
only 10 enrichment keys, all tech-level (PARCEL_HAS_LANDBOOK,
PARCEL_IS_CONDOMINIUM, etc.) — no NR_CF, no ADRESA, no PROPRIETARI.
The panel renders correctly but with nothing of substance shown.
User had to manually click "Citește din ANCPI" to backfill.

Now auto-fires the fetch when:
  - Panel mounts a fresh detail (or after parcel switch)
  - AND enrichment lacks ALL three of NR_CF / ADRESA / PROPRIETARI
  - AND we haven't already auto-fetched for this parcel this tab
    session (sessionStorage dedupe keyed by uuid OR siruta+cad+layerId)

Visible feedback while it runs: a quiet "Se preiau date suplimentare
din ANCPI…" strip below the loading area. The user can keep reading
whatever is already on screen.

Side fixes:
- refreshFromAncpi → useCallback (stable deps) so it can sit in the
  auto-trigger useEffect's dep array without infinite loops.
- Refresh path now uses /api/gis/parcela/find as the fallback when no
  uuid is available, matching the initial-load logic. The old
  orchestrator-shape projection still exists as a last-resort fallback
  but is rarely hit.

Note: orchestrator's parcel/tech only re-populates PARCEL_* tech
fields. Truly enriching rich PII (NR_CF/ADRESA/PROPRIETARI) needs the
"deep enrich" orchestrator path which the gis-api proxy contract
doesn't expose yet — separate gis-api task. So parcels that only ever
got tech-level enrichment will stay at tech-level even after this
auto-fetch. The visible improvement is: parcels that DO have rich
data load it in the first second instead of needing a manual click.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 19:52:00 +03:00
Claude VM 342bdca648 fix(geoportal-v2): structured panel sections + readable labels (back to basics)
Marius's feedback: "datele arată foarte ciudat" — the flat dl renderer
showed enrichment keys in whatever order gis-api returned them, with
raw key names (PARCEL_POSTAL_NO instead of "Nr. poștal") and no visual
hierarchy. With 20+ keys the important things (NR_CF, ADRESA,
PROPRIETARI) ended up below the fold under PARCEL_* tech metadata.

Restructure to mirror eterra.live's parcel-info layout:

1. HERO BLOCK at the top of the data area
   - "Nr. Carte Funciară" big mono number (NR_CF)
   - NR_CF_VECHI shown below when different
   - Adresă with pin icon and proper text wrapping

2. NAMED SECTIONS (rendered in fixed order, only when populated)
   - Proprietari — splits comma/semicolon-separated names into a list
   - Cadastru — NR_CAD, NR_TOPO, PARCEL_TOPO_NO
   - Suprafețe — SUPRAFATA_R, SUPRAFATA_2D, PARCEL_LEGAL_AREA, SUPRAFATA
     (values parsed + " m²" suffix)
   - Înscriere — SOLICITANT, TIP_INSCRIERE, DATA_CERERE, DOC
   - (CF/Adresă removed from the list because they're in the hero)

3. CARACTERISTICI CHIPS (existing) — Intravilan / Categorie / nr corpuri /
   status stay at the top. CARACTERISTICI_KEYS excludes them from the
   sections below so nothing is shown twice.

4. DETALII TEHNICE (collapsed by default) — anything not in a named
   section: PARCEL_HAS_LANDBOOK, PARCEL_IS_CONDOMINIUM,
   PARCEL_TECH_ENRICHED_AT, etc. Renders with friendly labels (Da/Nu
   for flags, dd/mm/yyyy hh:mm for dates) instead of "1" / ISO strings.

5. Value renderers per key type:
   - formatAreaValue("456" | "456.06" | "456 mp") → "456 m²"
   - formatFlag(0/1, da/nu) → "Da" / "Nu"
   - formatDate(ISO) → "19.05.2026, 04:40" (Romanian locale)
   - parseOwners("MATHBOUT MOHAMED MAHER, DR.") → list items

6. enrichedAt moved to a single small line at the bottom of the data
   area instead of a per-section caption.

LABEL map covers all 25 enrichment keys observed in DB. Anything new
falls back to raw key name (visible — easy to spot and add).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 17:17:48 +03:00
Claude VM a23ba1957f fix(geoportal-v2): silent auto re-grant on scope-missing 403
Removes the "Re-loghează-te" button + scope-mismatch warning prose.
On 403 from /api/gis/parcela/find the panel now:

1. Checks sessionStorage flag — false on first 403 of the tab
2. Sets the flag, fires signIn("authentik", { callbackUrl: current
   URL }) silently. For an SSO'd user this is a sub-second Authentik
   redirect cycle that mints a fresh access_token with the right
   scope claims, lands the user back on the same panel, and the
   re-mount fetches successfully — no visible message, no prompt.
3. If another 403 happens after the retry (i.e., Authentik genuinely
   can't grant the scope — config issue, not a stale-token issue),
   falls through to a discreet "Datele detaliate nu pot fi încărcate
   momentan." note. No call-to-action, no jargon.
4. On any successful 200 fetch, clears the sessionStorage flag so a
   future 403 in the same tab can re-trigger the silent retry.

Per Marius: "vreau doar să meargă, safe și fix" — no auth-flow
chrome shown to the user. The recovery is part of the system's
correctness contract, not a feature for the user to manage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 16:57:42 +03:00
Claude VM 71df1ee9ec fix(geoportal-v2): surface scope-insufficient instead of silent 404
The other-session's gis-api investigation found that gis-api is
working correctly — full/basic/none scopes all behave per spec.
The bug was in our /api/gis/parcela/find proxy: when EVERY candidate
returned 403 from gis-api (because the caller's JWT carried no
enrichment_scope claim), the proxy swallowed the 403s and returned
silent 404. The panel then rendered the "not in central DB" empty
state instead of prompting re-login.

This was the case for Marius today — his pre-refresh-fix session
held a token without the enrichment claim. After the auth self-heal
fix (commit 8ff67d1) the next gis-api call would have re-authed
correctly, but the panel never gave him that signal because find
hid the 403.

Fix in two places:

1. /api/gis/parcela/find:
   - Count 403s seen during candidate iteration
   - If forbiddenCount > 0 && forbiddenCount === candidates.length,
     return 403 { error: "scope_insufficient", ... } with a log line
     [gis-parcela-find] all_candidates_forbidden siruta=X cad=Y N
   - Otherwise log [gis-parcela-find] no_match (so we never go silent)

2. feature-info-panel: when fetch returns 403, the existing
   "forbidden" UI was a passive warning. Now it shows an actionable
   "Re-loghează-te" button that fires signIn("authentik", {
   callbackUrl: current }) — same path SessionErrorWatcher uses for
   RefreshAccessTokenError.

Reference: gis-api session report 2026-05-19 (Marius forwarded
analysis); the gis-api repo is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 16:52:47 +03:00
Claude VM 7afba6e1a9 fix(geoportal-v2): siruta-aware parcela lookup (B1 round 2)
Previous fix searched by cadastralRef and picked the first
layerId-matching result. But cadastral refs collide across UATs:
"354686" exists in multiple counties. The Cluj-Napoca f9bf2ca4-...
parcel with full enrichment got passed over for a same-cad parcel
in another UAT that has no enrichment → panel rendered header +
"Caracteristici" with empty Intravilan, no "Date eTerra" section.

New server-side /api/gis/parcela/find?siruta&cad&layerId proxy:
- gisApi.search(cad) → filter by layerId → up to ~20 candidates
- For each candidate, parcela.get and check stored siruta
- Return the siruta-matching detail
- Fallback: first readable candidate (so the panel still has data
  even if siruta mismatch — better than empty)

Panel useEffect simplified: fast path = parcela.get by uuid when the
tile has one, slow path = parcela/find when not. 404 from find sets
the "not in central DB yet" empty state (user can hit Citește din
ANCPI to trigger orchestrator live-fetch).

Diagnostic logs: [gis-parcela-find] siruta=… cad=… layerId=…
candidates=N + per-hit "has_enrich=true keys=N" so we can tell from
container logs whether the right parcel resolved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 15:26:49 +03:00
Claude VM b5eff5acc1 fix(geoportal-v2): rewrite info panel — auto-fetch + sections + condo + basic mode
Root cause of B1 (panel showed "Apasă din ANCPI" even with full enrichment
in DB): PMTiles overview tiles don't carry the GisFeature uuid, only
siruta/cadastral_ref/object_id. The panel's useEffect bailed out at
`!feature.id` and never fetched. So the data was there, the UI just
refused to ask for it.

Fix: when the click feature has no uuid, the panel now calls
`/api/gis/search?q=<cadref>`, filters by layerId match, and uses the
returned id to do `parcela.get(id)`. One extra round trip (~50ms with
the trigram-idx fix from 2026-05-18). For features arriving from the
search dropdown the uuid is already known — that path is unchanged.

Panel redesign — same data shape as eterra.live, ArchiTools styling
(shadcn instead of HeroUI), single-file:
  - Header: cadref + layer + area + status chip + close
  - Caracteristici: intravilan + categorie folosință + nr corpuri (chips)
  - Date eTerra: all enrichment fields, PII passes through gis-api scope
    redaction (scope=basic → PROPRIETARI/NR_CF/DOC already null)
  - Apartamente (condominium): for CLADIRI_ACTIVE clicks, fetches
    /api/gis/building/condo-owners and renders units with owners + cf + area
  - Localizare: click lat/lng + Google Maps link + SIRUTA echo

Two new proxy routes (thin wrappers over gis-api):
  - POST /api/gis/parcel/units-fetch
  - POST /api/gis/building/condo-owners

Basic-panel mode for restricted users (per Marius: "for users I don't
want to give full access to"):
  - New env BASIC_PANEL_USERS (csv emails) → session.basicPanel flag
  - Optional PANEL_BASIC_GLOBAL=1 to force-basic everyone
  - When true, panel renders only header + cadref + suprafață + a
    restriction notice; all sections + condo fetch are skipped
  - Defaults to off; pilot user Marius gets full panel as before

map-viewer now forwards lngLat on click so the Localizare section has
coordinates without a second lookup.

Type-check clean. Production build (NODE_ENV=production npx next build)
passes. The dev-mode prerender error on / page is pre-existing (Next 16
useContext-null on client component during static export, unrelated).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 12:35:09 +03:00
Claude VM 77da69e29f fix(geoportal-v2): CF button → deep-link to parcel-sync ePay tab
User pushback on the pool-based CF flow: he wants his own ePay account
(per-user creds, visible credit balance, decrement per order) — not the
shared orchestrator pool which hides cost attribution.

V2 panel "Comandă CF" now opens /parcel-sync?tab=epay&cad=<ref> in a
new tab where the legacy UI handles ordering with credits. The
/api/cf/* gis-api routes stay (used elsewhere + future SaaS consumers)
but the V2 button doesn't hit them.

Plus diagnostic on /api/gis/parcela showing enrichment presence + key
count to debug "data nu raman" — should reveal whether Marius's session
is getting full enrichment back from gis-api.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 08:16:55 +03:00
Claude VM b85e074e3a feat(geoportal-v2): wire Comanda CF button to /api/cf/order
Was a disabled placeholder ("Va fi disponibil la Faza F"). Now
POSTs to /api/cf/order with nrCadastral + siruta + gisFeatureId.
Forwards 409 catalog_hit as "Extras CF valid deja există".
Spinner during request, result text shown below the toolbar.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 07:53:11 +03:00
Claude VM e6432b13f0 fix(geoportal-v2): hydrate siruta when refresh fires before parcela.get
Audit-flagged blocker: search-dropdown feature select → setClicked
with siruta:"" because gis-api /search response doesn't include
siruta per feature. If user clicks "Citește din ANCPI" before the
panel's auto-fetch of parcela.get completes, refresh fails with
missing_siruta_or_cad.

Fix: refresh handler now awaits parcela.get inline when siruta is
empty + feature.id is present, then hydrates detail before posting
to /api/gis/parcel/tech.

Proper followup: extend gis-api /search response with siruta per
feature so the race goes away entirely.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 00:15:07 +03:00
Claude VM 68355efbba fix(geoportal-v2): UAT click deep-links to eterra.live + revert debug
UAT click previously console.logged only. gis-api search response
doesn't include bbox/centroid, so ArchiTools can't fitBounds locally.
Reuse the deep-link pattern (already used by Export GeoPackage) →
open eterra.live/harta?siruta=X in a new tab. eterra.live has its own
/api/geoportal/uat-bounds + flyTo wired.

Future: add GET /api/v1/uat/:siruta/bounds to gis-api so ArchiTools
can fitBounds inline without leaving the page.

Also reverts the session.debug diagnostic (Marius confirmed
hasRefreshToken=true + expiresIn=293 after attaching offline_access
scope mapping to Authentik provider pk=6 — root cause fixed,
diagnostic no longer needed).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 23:59:47 +03:00
Claude VM e0610b0573 fix(geoportal-v2): handle PMTiles features without uuid id
PMTiles overview tiles may carry only object_id + siruta + cadastral_ref
(not the gis_core.GisFeature uuid). Old click handler required `id` and
silently dropped clicks. Now:

- map-viewer click handler: extract objectId AND id; require only
  siruta+cadastral_ref to dispatch. Logs the props when fields are
  missing for further diagnosis.
- feature-info-panel: skip auto-fetch when feature.id is empty; show
  basic info from tile properties + nudge user to "Citește din ANCPI"
  to populate enrichment.
- Refresh button: project orchestrator response straight into the
  panel when no gis-uuid available (no second parcela.get roundtrip).
  Falls back to detail.siruta when click came from search (which only
  returns id + cadastralRef, no siruta).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 21:42:40 +03:00
Claude VM 99a673de3d feat(geoportal): Faza E v2 thin client (PMTiles + gis.ac)
New geoportal module flag-gated by session.useGisAc. Legacy code path
preserved as GeoportalV1Legacy (rename only — same logic). When
session.useGisAc=true (Infisical USE_GIS_AC=1 OR email in
GIS_AC_PILOT_USERS), the page renders GeoportalV2 instead.

V2 layout (851 LOC across 5 files):
- map-viewer.tsx (~295 LOC): MapLibre + PMTiles src `pmtiles://pmtiles.gis.ac/overview.pmtiles`. Layers: UAT boundaries (z5, z8), parcels (gis_terenuri line + invisible hit-test fill), buildings (gis_cladiri fill+line). Click → resolves layerId from sourceLayer, emits ClickedFeatureLite (id, siruta, cadastralRef, layerId).
- search-bar.tsx (~160 LOC): debounced 300ms, calls /api/gis/search, dropdown grouped by UATs / Parcele.
- feature-info-panel.tsx (~270 LOC): fetches /api/gis/parcela/[id], renders enrichment block (scope-aware — 403 shown explicitly as "permisiuni insuficiente"). Buttons: "Citește din ANCPI" (POST /api/gis/parcel/tech force:true), "Export GeoPackage" (deep-link to eterra.live/harta?…&autoexport=geopackage), "Comandă CF" placeholder pending Faza F.
- basemap-switcher.tsx (~40 LOC): liberty / dark / satellite / google. Dropped orto + topo50/25 (require ANCPI session — orto/topo via raster.gis.ac is TBD Sprint 2).
- geoportal-v2.tsx (~85 LOC): wraps MapViewer + SearchBar + BasemapSwitcher + FeatureInfoPanel.

API routes (90 LOC across 3 files):
- GET /api/gis/search → gisApi.search
- GET /api/gis/parcela/[id] → gisApi.parcela.get
- POST /api/gis/parcel/tech → gisApi.parcel.tech (refresh ANCPI)

All routes 401 if no NextAuth session, forward GisApiError status+code,
hit api.gis.ac with the Authentik access_token from session. Per
project_audit_correlation_echo memory: no correlationId set on client
side (gis-api overwrites server-side).

Cutover-bottom-right badge "gis.ac · v2" visible until full rollout for
ops visibility.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 08:32:36 +03:00
Claude VM 8222be2f0e fix(geoportal): search input text invisible in dark mode
Changed bg-background/95 to bg-background (opaque) and added
text-foreground to ensure input text is visible on dark theme.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 22:11:46 +03:00
Claude VM 177f2104c1 fix(geoportal): show UAT name in search results + fix map snap-back
Search results now JOIN GisUat to display UAT name prominently instead
of just SIRUTA codes. Map flyTo uses imperative handle instead of
stateful props that re-triggered on re-renders.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 21:21:09 +03:00
AI Assistant 9bf79a15ed fix(geoportal): proxy PMTiles through HTTPS + fix click/selection + optimize rebuild
PMTiles was loaded via HTTP from MinIO (10.10.10.166:9002) on an HTTPS page,
causing browser mixed-content blocking — parcels invisible on geoportal.

Fixes:
- tile-cache nginx proxies /pmtiles/ → MinIO with Range header support
- PMTILES_URL changed to relative path (resolves to HTTPS automatically)
- clickableLayers includes PMTiles fill layers (click on parcels works)
- Selection highlight uses PMTiles source at z13+ (was Martin z17+ only)
- tippecanoe per-layer zoom ranges (terenuri z13-z18, cladiri z14-z18)
  skips processing millions of features at z0-z12 — faster rebuild

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 14:56:49 +03:00
AI Assistant 870e1bd4c2 perf(geoportal): extend PMTiles to z18 — eliminate Martin for terenuri/cladiri entirely
PMTiles now covers z0-z18 (full zoom range). Martin sources kept only for
selection highlight and fallback when PMTiles not configured.
All terenuri/cladiri fill/line/label layers served from PMTiles when active.
Zero PostGIS load for tile rendering at any zoom level.

File will be ~1-2 GB but eliminates all cold-load latency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 17:23:04 +02:00
AI Assistant f5c8cf5fdc perf(geoportal): extend PMTiles to z16 — near-zero PostGIS load for tile serving
- PMTiles now covers z0-z16 (was z0-z14) for terenuri + cladiri
- Martin only serves z17-z18 (very close zoom, few features per tile)
- map-viewer: PMTiles layers serve z13-z16 for terenuri, z14-z16 for cladiri
- Labels at z16 now from PMTiles (cadastral_ref included in tiles)
- Remove failed compound index from postgis-setup.sql

This eliminates PostgreSQL as bottleneck for 99% of tile requests.
PMTiles file will be ~300-500MB (vs 104MB at z14).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 15:40:03 +02:00
AI Assistant 0d5fcf909c feat(geoportal): PMTiles for terenuri/cladiri overview + cache warming + cleanup
- 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>
2026-03-27 21:46:47 +02:00
AI Assistant 236635fbf4 fix(geoportal): show only building body suffix (C1, C2) instead of full cadastral_ref
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>
2026-03-27 21:32:38 +02:00
AI Assistant 536b3659bb feat(geoportal): nginx tile cache + PMTiles overview layers + tippecanoe pipeline
- 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>
2026-03-27 20:28:49 +02:00
AI Assistant a75d0e1adc fix(geoportal): mount Martin config + upgrade v1.4 + enable building labels
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>
2026-03-27 10:28:20 +02:00
AI Assistant 9d45799900 revert: disable building labels + remove debug endpoints
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>
2026-03-27 08:43:20 +02:00
AI Assistant 946723197e debug: red building labels with template string syntax
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 08:30:13 +02:00
AI Assistant 3ea57f00b6 debug: try cladiri labels at minzoom 15
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 08:23:48 +02:00
AI Assistant 1d233fdc19 fix(geoportal): building labels — inline addLayer like terenuriLabel
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>
2026-03-27 08:13:40 +02:00
AI Assistant c6eb1a9450 fix(geoportal): building labels — force overlap + delayed init
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>
2026-03-27 08:07:38 +02:00
AI Assistant 49a239006d fix(geoportal): simplify building labels — show full cadastral_ref
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>
2026-03-27 07:57:45 +02:00
AI Assistant 4c1ffe3d01 fix(geoportal): building labels C1/C2 — simpler expression + minzoom 16
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>
2026-03-27 07:11:56 +02:00
AI Assistant acb9be8345 feat(geoportal): building body labels (C1, C2, C3...) on map at zoom 17+
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>
2026-03-27 06:53:45 +02:00
AI Assistant c012adaa77 fix: export buttons direct (no dropdown), compact mobile cards
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>
2026-03-25 12:03:24 +02:00
AI Assistant 8acafe958b fix: freehand drawing, click highlight, mobile toolbar visibility
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>
2026-03-25 07:11:16 +02:00
AI Assistant 45d4d1bf40 fix: hide enrichment in portal, CF disabled button, no UAT flash, fix overlaps
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>
2026-03-25 06:58:01 +02:00
AI Assistant 3fcf7e2a67 fix(geoportal): Google satellite, ESC/right-click exit, no UAT fill, ANCPI bbox fix
Basemaps: added Google Satellite option
ANCPI ortofoto: fixed bbox conversion (all 4 corners, not just SW/NE)
Selection: ESC key and right-click exit selection mode, tooltips updated
UAT layers: removed fill (only lines + labels), less visual clutter
Proprietari vechi: greyed out (opacity-50) so current owners stand out

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 14:19:02 +02:00
AI Assistant 024ee0f21a fix(geoportal): layer toggle + enrichment update + refresh button
1. Layer toggle fix: removed isStyleLoaded() check that silently blocked
   visibility changes when OpenFreeMap style has pending sprite/font loads
2. Enrichment: "Actualizeaza" button always visible (re-fetch from eTerra)
   replaces "Enrichment" button when data already exists
3. Panel updates with returned enrichment data immediately

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 13:53:18 +02:00
AI Assistant 19bed6724b fix(geoportal): enrichment panel update + force-hide all layers + boundary filter
1. Enrichment: panel now updates immediately with returned data (was only showing message)
2. Layers: ALL data layers set to visibility:none immediately after creation,
   then only enabled ones are shown. Fixes cladiri appearing when only terenuri toggled.
3. OpenFreeMap boundaries: also filter by source-layer="boundary" (more reliable)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 13:45:29 +02:00
AI Assistant 5ff7d4cdd7 fix(geoportal): hide oneway arrows from OpenFreeMap basemap
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 13:24:41 +02:00
AI Assistant 91034c41ee fix(geoportal): background enrichment using proven enrichFeatures()
Previous single-parcel enrichment wrote empty data (couldn't match in eTerra).
Now uses the original enrichFeatures() which properly fetches owners, CF, etc.

Changes:
- Enrichment runs in BACKGROUND (returns immediately with message)
- Clears bad enrichment data before re-running
- Tracks running enrichments to avoid duplicates
- GET /api/geoportal/enrich?siruta=... checks if enrichment is running
- Panel: hasRealEnrichment checks for CF/PROPRIETARI/CATEGORIE (not just NR_CAD)
- Enrichment button stays visible until real data exists
- Message: "Enrichment pornit in background. Datele vor aparea in 1-3 minute."

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 13:23:44 +02:00
AI Assistant d9c247fee2 fix(geoportal): force all layers hidden on map load (fixes terenuri/cladiri showing when toggled off)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 13:20:13 +02:00
AI Assistant 7ae23aebf4 fix(geoportal): hide OpenFreeMap built-in boundary layers on load
OpenFreeMap Liberty/Dark styles include admin boundary layers that show
even when our UAT toggle is off. Now hides all boundary/admin/border
layers from the basemap style on map load.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 13:18:44 +02:00
AI Assistant d2b69d5ec6 fix(geoportal): all layers OFF by default + full enrichment display
Layers:
- ALL layers OFF by default (just basemap on load)
- User activates what they need

Feature panel:
- Shows ALL enrichment fields: proprietari (full text, wrapping),
  CF vechi, nr topo, adresa, solicitant, intravilan, categorie
- Building info with icon (cu acte / fara acte warning)
- hasEnrichment check relaxed (any non-empty field counts)
- Panel scrollable (max-h 60vh) for long data
- WrapRow for multi-line text (proprietari, adresa)
- Enrichment button visible when no enrichment data
- Enrichment auto-updates panel on success

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 13:09:41 +02:00
AI Assistant dfa4815d75 fix(geoportal): layers off by default + bulk enrichment feedback
- UAT + Intravilan layers OFF by default (user activates when needed)
- Terenuri/Cladiri listed first in panel (most used)
- Bulk enrichment: per-feature with progress counter (3/10), success summary
- Progress text shown in toolbar during enrichment

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 12:58:24 +02:00