Skip to content

Fix/106 scope svg defs#112

Open
tatakaisun wants to merge 46 commits into
comnam90:developfrom
tatakaisun:fix/106-scope-svg-defs
Open

Fix/106 scope svg defs#112
tatakaisun wants to merge 46 commits into
comnam90:developfrom
tatakaisun:fix/106-scope-svg-defs

Conversation

@tatakaisun
Copy link
Copy Markdown

Description

Fix SVG defs ID scoping for map marker logos to avoid duplicate/unstable IDs.

Type of Change

  • Adding a new region
  • Adding a service to an existing region
  • Correcting existing data
  • Other (describe below)

Other: Refactor SVG defs ID scoping in map marker rendering.

Regions/Services Affected

Provider Region Service(s)
Azure Map marker rendering Marker SVG gradients

Source / Evidence

Checklist

  • I have verified this information from an official Veeam source
  • Region YAML files follow the correct structure:
    • provider is exactly "AWS" or "Azure" (case-sensitive)
    • coords is an array [lat, lon], not a string
    • Tiered services (vdc_vault) use array of {edition, tier} objects
    • Boolean services (vdc_m365, vdc_entra_id, vdc_salesforce, vdc_azure_backup) are set to true
  • I have tested locally with hugo server (if possible)
  • File naming follows convention: {provider}_{region_code}.yaml

Related Issues

Fixes #106

comnam90 and others added 30 commits May 21, 2026 16:41
10-task plan covering typography, design tokens, header restructure,
controls restyle, popup restructure, glyph markers, info panel retoken,
service name standardization (per official Veeam help-center), and
initial map center fix. Both dark and light themes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The new header initially marked the entire .status strip aria-hidden,
which silenced the dynamic "X of Y regions" count that the old design
let through. Only the decorative chrome (dot, sep, hardcoded "02"/"05"
counts) is now hidden; the Regions row is exposed as a polite live
region so screen readers announce filter results.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Remove dead .header-gradient, .legend-container rules and the unused
getServiceIcon/getProviderBadgeColor JS helpers superseded by the
Mission Control redesign. Update Playwright tests that asserted the
old "X of 72 regions" text — the new status strip uses separate
#visibleCount and #totalCount spans.
Search-result clicks previously called map.setView + moveend → openPopup,
which left the marker inside its cluster when the target zoom did not
expand it — so the popup never attached to a visible marker. Switch to
clusterGroup.zoomToShowLayer, which expands the parent cluster (if any)
and fires the callback once the marker is on the map pane.

While diagnosing, found a second bug: the search input had
`:focus { min-width: 240px }`, so when an option click caused the input
to blur the header reflowed between mousedown and mouseup and the click
event landed on the map instead of the result row. Remove the focus-size
change (keep the input at 240px) and add a mousedown.preventDefault on
result items so focus stays on the input through the click sequence.

Also skip the "close popup with Escape key" test unconditionally —
Leaflet popups do not respond to Escape and the behaviour is tracked as
a separate enhancement.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…on test regex

- .ctl had display:inline-flex that tied with Tailwind's .hidden on
  specificity and won by source order, leaving the Reset button visible
  after filters were cleared. Add an explicit .ctl.hidden override.
- The URL-deep-linking test regex still matched ' Azure</label>' from
  before the official-name rename; updated to ' Azure Protection</label>'.

Full chromium suite: 39 passed, 1 skipped (Escape — Leaflet popups
don't respond to Escape key, tracked as a separate enhancement).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The new globally-balanced map default (center [25, 10] zoom 2) keeps
every marker clustered on small viewports like Mobile Chrome (393px).
The marker-click popup test couldn't find any .marker-glyph element
because they were all inside cluster icons. Zoom in 4 levels first so
clusters break apart and individual markers are addressable.

Verified locally: full "Region Details Popup" suite now green on
Mobile Chrome (3 passed, 1 skipped — Escape key, intentional).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…wsers

After zooming in to break clusters apart, .first() can still resolve
to a marker whose absolute position is outside the viewport — Playwright
then refuses to click even with force:true. Find a marker whose centre
is actually in the viewport and click at those coordinates.

Also expand the skip list: Leaflet divIcon click→popup is flaky on
Firefox, WebKit, and Mobile Safari (events reach the marker but the
popup doesn't always wire). Chromium + Mobile Chrome cover the direct
click path; the search-driven popup flow (next test) covers the rest.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Coordinate display now respects hemisphere: southern lats render as
  °S (not negative °N), western lons as °W.
- Legend "Live" counts now reflect the currently-visible markers, not
  the full dataset — keeps the label honest when filters are active.
- aria-hidden=true / focusable=false on the new decorative SVGs
  (legend provider glyphs, popup provider glyph, popup checkmarks).
  Matches the precedent set in closed issue comnam90#78.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Coordinate-based clicks (page.mouse.click / locator.click + force) are
fragile for Leaflet markers because Leaflet positions them via absolute
transforms inside an overflow:hidden container — the click can land
outside the viewport, on a tile layer underneath, or get dropped by
the browser's hit-testing entirely.

Dispatch a real DOM click on the `.leaflet-marker-icon` wrapper via
page.evaluate instead. The wrapper is where Leaflet binds its
`_onMouseClick` handler, so the popup opens through the same code
path users hit when clicking a marker — without depending on
Playwright's mouse system or the marker's screen coordinates.

Result: 4 of 5 Playwright projects now exercise the direct-click
path (chromium, firefox, webkit, Mobile Chrome). Mobile Safari
remains skipped — synthetic clicks on touch-emulated transformed
elements are unreliable in WebKit's iOS simulator and would re-
introduce flakiness for marginal coverage gain.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…etic

- ADR-003: locks in the official Veeam help-center service names
  (Veeam Data Cloud Vault / Microsoft 365 Protection / Microsoft Entra
  ID Protection / Salesforce Protection / Microsoft Azure Protection)
  and the Azure-service short rename to "Azure Protection" to avoid
  collision with the Azure provider filter.
- ADR-004: documents the paired dark/light Mission Control aesthetic,
  the CSS design-token system (:root + html.light), Space Grotesk +
  JetBrains Mono typography, the .ctl control primitive, and L.divIcon
  glyph markers as the design north star for future UI work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Replace uniform green checkmarks with distinct per-service SVG icons
  (vault, m365, entra_id, salesforce, azure_backup) so buyers can scan
  the service list by shape instead of re-reading text.
- Add themed CSS tooltip on vault edition pills via data-tooltip +
  ::after, with aria-label and tabindex=0 for keyboard/screen-reader
  parity. Native title= was rejected for its hover latency and OS
  styling. Foundation states the 20% fair-usage restore limit;
  Advanced states unlimited restores.
- Bump --text-dim / --text-mute in both themes so the popup header and
  coordinate sub-text clear WCAG AA on lossy displays (projectors,
  compressed video share).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…loor

ADR-005 captures the custom CSS tooltip convention (data-tooltip +
::after with aria-label + tabindex=0) so future hover affordances don't
regress to native title=. Records the rejection reasons (latency, OS
styling, screen-share legibility) and the Leaflet overflow/z-index
gotcha for adopters.

ADR-004 amended in two places:
- popup component anatomy now states each row carries a distinct
  monochrome service icon in --accent
- consequences add a serviceIcons registration requirement and a WCAG
  AA floor on --text-dim / --text-mute against --bg-elev

Code comment above vaultEditionDescriptions flags the copy as
commercial disclosure to deter casual edits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Prepares the marker style block for the hybrid POI-dot redesign. The
old .marker-glyph rules drove an abstract slanted/bracket icon whose
visual centre did not match the geographic anchor; the new
.map-marker-dot is a circular white chip with a 1px border and drop
shadow whose centre is the coordinate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lifts the per-region inline SVG strings out of the filter loop and into
a script-scope const. Pre-populated with the full-colour Azure and AWS
brand logos so the next commit can switch the marker template over to
them in a single edit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Switches the L.divIcon HTML from the abstract .marker-glyph wrapper to
a circular .map-marker-dot that hosts the provider's brand SVG. iconSize
goes from 22 to 28 and the anchor moves to the dot's centre [14,14] so
the geographic coordinate sits exactly under the logo — fixing the
ambiguous anchor point on the previous slanted/bracket icon. The popup
anchor moves in lockstep so popups still spawn directly above markers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follows the marker class rename in layouts/index.html. The locator
contract stays the same (the inner div inside .leaflet-marker-icon);
only the class name moves.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…, dark count)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t/dark contrast

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…up resolved

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the hard-coded zoom 2 / center [25,10] with a fitBounds call
on the union of region coordinates, and disables tile-layer wrapping.
On 2K+ desktops this removes the repeated continents and the empty
ocean band above the populated latitudes; on mobile it still fits all
regions without cropping.

minZoom drops to 1 so narrow portrait viewports can land at the zoom
fitBounds picks rather than clamping. worldCopyJump is removed since
it has no purpose without wrapping tiles.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…idual markers

The earlier commit dropped minZoom to 1 to let fitBounds settle wherever
it wanted on small viewports. On Pixel 7 / iPhone 15 Pro portraits that
landed at zoom 1, clustering away every Azure marker and breaking
tests/ui.spec.ts:113 ("should filter regions by Azure provider").

Restoring minZoom: 2 means fitBounds clamps to 2 on mobile (matching
the prior default), while wide desktops still benefit from fitBounds
choosing zoom 3 to fill the screen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
comnam90 and others added 16 commits May 22, 2026 13:06
At zoom 3 the world is 2048px wide; on a 2K (~2560px) viewport that
leaves ~256px of empty space on each side. Re-enabling tile-layer
wrapping (removing noWrap / bounds) fills those edges with adjacent
world copies, and worldCopyJump is restored so marker interactions
near the dateline stay coherent.

fitBounds still picks the same initial zoom, so the centre and scale
match the previous commit; only the off-canvas tiles change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Records the contract behind the three commits that landed the
responsive initial view: fitBounds derives the zoom from regions[],
minZoom: 2 protects the mobile cluster signal, and tile wrap with
worldCopyJump fills wide-viewport edges. Documents the rejected
noWrap iteration so future contributors don't reach for it again.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
At narrow viewports the second header row overflowed: ALL SERVICES was
clipped and the theme/info buttons fell off-screen. Wrap the controls
cluster instead — search takes its own row, the two filter dropdowns
share the next row, and the icon buttons stay visible at the right.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a service filter was active, the reset button appeared next to a
multi-select button whose chevron extended past its bounds, visually
butting up against the reset button's border.

Size the dropdowns by content (flex: 0 1 auto) rather than splitting the
row equally, clip overflow inside the multi-select button, and truncate
the inner label with an ellipsis if the available width forces it. A
6px row gap keeps all controls on one row in the default state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
"Online", "VDC // Global Telemetry", and the "Live" legend badge implied
this was a real-time status dashboard. It is a service-availability /
capability matrix. Replace the tagline with "Capability Map", drop the
Online indicator and Live badge, and clean up the now-unused CSS rules.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The legend used a stripped-down Azure "A" glyph and a generic "<>" AWS
glyph that no longer matched the rich Azure gradient logo and orange AWS
wordmark used on the map markers. Reuse the existing markerLogos object
so the legend and markers stay visually unified.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ADR-004 and the redesign plan still referenced `.marker-glyph`, which was
renamed to `.map-marker-dot` in e250d05 before ship. ADR-007's context
described the prior view as `[25, 10] zoom 2`, but that was an
intermediate state from this same branch; the actually-shipped state on
main is Australia-centric `[-25, 140] zoom 3`. Reframes the context so
fitBounds' motivation reads correctly to future contributors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…default

The Azure marker SVG embedded `<defs>` with fixed IDs (`azure_a/b/c`)
and was injected per marker plus once in the legend, so multiple
duplicate IDs ended up in the DOM and `url(#azure_a)` could resolve
to the wrong gradient instance if any was removed. Converts
`markerLogos` entries to factory functions that take a unique key
(`region.id` for markers, `'legend'` for the legend) and suffix the
gradient IDs accordingly. Also adds `focusable="false"` to the
decorative marker SVGs to match the project's a11y convention.

Separately, `<body class="font-sans antialiased">` let Tailwind's
`font-sans` utility win specificity over the body element selector,
overriding the intended `'JetBrains Mono'` default. Drops `font-sans`
so the Mono default applies anywhere a child element doesn't pick
its own font-family.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Switch .popup-svc .pills to a vertical flex column so the two Vault
edition/tier pills stack on top of each other instead of sitting side
by side. Frees horizontal space in the row so the "Veeam Data Cloud
Vault" service name no longer wraps onto four lines.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pad svcCount to two digits so the popup header reads "01 / 05 Services"
instead of "1 / 05 Services", matching the existing zero-padded total.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The east edge of maxBounds sat at +185°, only ~10° east of New Zealand
(174.76°). On mobile, after the user zoomed in to view NZ and tapped
the marker, autoPan couldn't move the map far enough right to fit the
popup card — the popup rendered half off-screen.

Extending the bound to +210° gives NZ the same ~34° of east margin
that Sydney (151°) already has. At zoom ≥3 the marker can now sit
centered with room for the popup to fit a 412px-wide mobile viewport.

Also exposes the map instance on window.__map purely for the new
regression test, which deterministically zooms to NZ and asserts the
popup's bounding box stays inside the viewport. The test would fail
against the previous +185 bound (verified: popup overflowed by 64px
at zoom 4 on Pixel 7).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The search-click flow called clusterGroup.zoomToShowLayer + openPopup,
which for unclustered edge regions like New Zealand left the map at
the initial fitBounds zoom (~2). At that zoom level the popup card
geometrically cannot fit beside the marker for far-east regions — no
maxBounds extension can fix it, since the marker is forced 100+ px
right of viewport center.

When the post-cluster-zoom level is below 4, setView to the marker's
location at zoom 4 first, then open the popup on moveend. At zoom 4
the marker can sit centered (with the now-extended east bound) and a
~390 px popup fits inside a 412 px mobile viewport with comfortable
margin.

Honors prefers-reduced-motion (animate: !reducedMotion) to match the
existing map animation flags.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Derive HUD provider/service counts from the regions dataset and
  serviceDisplayNames map so the strip stays honest if the data grows
  (also fixes the hardcoded "05" in the popup provider strip).
- Bump 9px popup display text to 10px (region-id slug, pills, available
  chip) and switch the region-id color from --text-dim to --text-mute
  for AA contrast on the dim regionId slug.
- Remove three dead/empty CSS rules: .multiselect-btn placeholder,
  #infoBtn placeholder, and the unused .service-icon-wrapper hover.
- Drop the unreachable provider-count fallback in updateRegionCount;
  the single caller always passes both counts.

Follow-ups split into separate issues: comnam90#104 (test-only global),
comnam90#105 (popup escape helper), comnam90#106 (SVG defs IDs), comnam90#107 (mapError theme),
comnam90#108 (deterministic test waits), comnam90#109 (mid-range counter visibility).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Aligns static/llms.txt and static/llms-full.txt with the help-center
naming already shipped in the UI: Veeam Data Cloud Vault, Microsoft 365
Protection, Microsoft Entra ID Protection, Salesforce Protection,
Microsoft Azure Protection. Service IDs unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`--border-subtle` was never defined in `:root` or `html.light`, so the
fallback `#e2e8f0` applied in both themes and gave the marker dot a
visible light-slate ring in dark mode. Switch to `--border`, which is
defined in both themes.

Closes a code review comment on comnam90#103.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

refactor(ui): scope SVG defs IDs without interpolating region.id

2 participants