Fix/explorer sort dropdown auto close (#4481)
* fix(visualization): auto-close File Explorer sort dropdown
Migrate the daisyUI sort dropdown from pure-CSS focus-within to a
controlled signal pattern (isOpen + document:click HostListener),
matching MapSelector / DeltaSelector. The menu now closes after the
user picks a sort key, toggles the order, or clicks outside.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(visualization): replace ribbon bar with floating metrics bar
Replace the top ribbonBar, metricChooser, scenariosPanel and slider stack
with a new DaisyUI-based floating metrics bar (features/metricsBar/): per-axis
distribution histograms, color/edge segments, and native-popover search/settings.
Removes the @angular/material dependency from these surfaces and deletes the
superseded components.
Includes fixes from a multi-agent review of the change:
- visibleNodeMetricValues selector uses boundary-safe path matching so sibling
folders sharing a path prefix are no longer pulled into the histogram
- nodeSelection.findTopLevelNode guards the empty-visible-nodes case
- metricColorRangeDiagram percentile calcs always return a finite value (no NaN)
- screenshot capture again subtracts the bottom bar height
- color range slider thumbs get slider ARIA roles + keyboard control; link
Color/Height button gets an accessible label
- colorSettingsPopover cancels its pending debounce on destroy
- axisColorRamp normalises inverted color ranges
- shared settingsInput util (debounce constant + parse/validate helper)
Backfills unit specs across the new metrics bar components and the
visibleNodeMetricValues selector. Full suite: 363 suites / 2094 tests green.
* fix(visualization): rank top labels by height-metric value
Top metric labels were selected by rendered building height, but getHeightValue
inverts the rendered height when the height metric has inverted attribute
direction or 'invert height' is enabled (maxHeight - value). As a result the
lowest-value buildings were labeled instead of the highest (e.g. a file with
complexity 4 labeled while one with 223 was not).
Rank the height-mode label candidates by the height-metric value itself, so the
highest-value buildings are always labeled regardless of how height is rendered.
Behavior is unchanged for non-inverted metrics (rendered height already tracks
the value). Adds a regression test.
* fix(visualization): show grey background on hovered metric option
The active/hovered metric option in the select dropdown showed a primary-colored
ring and no fill, because the static bg-transparent class tied with bg-base-200 at
equal specificity and won. Drop the ring and move bg-transparent into the inactive
branch only, so the hovered/active option paints a solid grey (bg-base-200).
* refactor(visualization): dedup metrics-bar segments and settings popovers
- Extract MetricSegmentBase shared by the area/height segments (#44).
- Extract a reusable cc-settings-popover-shell container used by the area,
height, color and edge settings popovers (#47); all popover ids, anchors,
testids and width behavior preserved.
- parseNumberInput now uses parseFloat so numeric settings inputs accept decimals.
- Fix the Color 'Reset thresholds' button resetting delta map colors as a
side effect; it now resets only dynamicSettings.colorRange.
* test(visualization): metrics-bar dispatch/selector specs and metric-select e2e
Strengthen dispatch and popover-wiring coverage across the metric segments,
toggles and settings popovers; add the settings-popover-shell spec, parseFloat
decimal cases, visibleNodeMetricValues selector edge cases, and a Playwright e2e
for the metric-select flow (unverified locally — needs a build + Chrome).
* perf(visualization): select top labels without sorting all buildings
setLabels ranked the top-N labels by sorting every leaf building on each render
(O(n log n)). Replace the full sort with a partial top-N selection
(selectTopNByValue) that keeps an n-sized window in one pass — identical result
and ordering, but avoids sorting thousands of buildings to pick a handful of
labels. Behaviour (rank by height-metric value) is unchanged.
* fix(visualization): clear previous building highlight when selecting another
selectBuilding overwrote the selected reference and colored the new building but
never reset the previously selected one, so its highlight lingered. Two parts:
- Reset the previously selected building's color when switching selection.
- Invalidate the mesh's incremental highlight cache (_prevHighlightedIds) on any
selection change, so the next highlight pass does a full recompute. Without this,
the just-deselected building kept its bright default color against the dimmed
focus state (the selected building is always skipped by the incremental diff),
which is why it only cleared after repeatedly hovering elsewhere.
* refactor(visualization): move metrics bar onto feature store/service architecture
The metrics bar lived under features/ but accessed the ngrx Store directly from
components and imported the scenarios feature internals, failing the
dependency-cruiser architecture lint (21 errors) that CI gates on.
Mirror the labelSettings architecture: add per-slice stores/ (the only layer that
imports @ngrx/store) and delegating services/, move feature-local selectors into
selectors/, and route every component through the services instead of injecting
Store. Access the scenarios feature via its facade. Behavior is unchanged.
Architecture lint: 0 errors (was 21). Full suite: 365 suites / 2128 tests green.
* docs(visualization): add changelog entries for hovered-option color and top-label perf
* refactor(visualization): align blacklist matching with NodeDecorator and improve metrics-bar a11y
Reuse returnIgnore in createBlacklistMatcher and the explorer rule counts so
flatten/exclude matching mirrors NodeDecorator, including negated !-rules.
Only line-through flattened files (not folders) in the explorer tree.
Replace the color range slider's role=slider divs with native range inputs.
Extract helpers in histogramBins and visibleNodeMetricValues selectors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(visualization): harden settings number inputs against fractional and mid-typing values
- parseNumberInput no longer overwrites the input element while typing, so
intermediate out-of-range values (the "4" while typing "45") survive; adds
an optional round flag for integer-only settings
- extract shared cc-slider-number-input control used by the area, height and
edge settings popovers: debounced commits, step rounding, display
normalization on change, and no stale intermediate commits when retyping
the current value
- selectTopNByValue floors fractional n and ranks NaN below real values
instead of crashing or blocking the top-N window
- edgePreviewNodes selector floors fractional amountOfEdgePreviews
- debounce util gains flush() and cancel()
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(visualization): repair color settings popover slider, inversion state and ramp colors
- measure the color-range slider track width instead of assuming a fixed
395px, so thumbs stay on the track on narrow viewports
- abort thumb drags when the popover is light-dismissed mid-drag and clean
up document listeners on destroy instead of dispatching a garbage range
- step arrow-key and typed threshold changes from the pending value so
repeated presses within the debounce window accumulate
- track color inversion with an explicit flag in mapColors state so the
Invert Colors checkbox stays meaningful after colors are customized
- flush the pending color range on popover destroy instead of discarding
it, and use the shared debounce util instead of a hand-rolled timer
- extract getColorByMetricValue as the single classification used by the
3D map and the axis color ramp: bins at the thresholds now match their
buildings, and the ramp respects the selected color mode
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(visualization): unify blacklist matching in one shared matcher
createBlacklistMatcher is now the single engine for blacklist rules, used by
NodeDecorator, both layouts, the metric calculators, the file explorer counts
and selection cleanup, so they can never disagree on what a rule affects:
- isExcludedLeaf applies NodeDecorator's leaf-only exclude semantics,
including negated !-rules
- isExcludedSubtree is the only folder-level check; it ignores negated rules,
so the street layout no longer prunes folder subtrees that still contain
non-excluded leaves
- isPathBlacklisted/isPathHiddenOrExcluded use the shared matcher instead of
raw gitignore semantics, so selection cleanup agrees with what
NodeDecorator removes from the map
- NodeDecorator's private engine copy is deleted; the explorer's per-rule
counts get back the combined-engine prefilter for positive rules
- delete unused isNodeExcludedOrFlattened
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(visualization): restore metrics-bar behaviors lost in the ribbon-bar migration
- keep the edge metric chooser usable while edges are hidden (dim instead of
disable) and restore the sum/median aggregation indicator next to hovered
edge values
- initialize keyboard navigation in the metric select popover on the
currently selected metric so Enter right after opening is a no-op
- guard METRIC_ALIASES lookups with Object.hasOwn so metrics named like
Object prototype members cannot break the metric list
- position settings popovers via JS next to their anchor in browsers
without CSS Anchor Positioning (Firefox) instead of viewport-centered
- wrap metrics-bar segments in real boxes so divide-x separators paint
(segment hosts are display:contents)
- render one full histogram bin for metrics where min equals max so a
populated metric is distinguishable from no data
- metricChooserType uses the tested createAttributeTypeSelector instead of
re-implementing the aggregation logic inline
- metricsBar spec: histogram test now feeds visibleNodeMetricValuesSelector
(what the component actually reads) and asserts rendered bars
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(visualization): correct selection, top-label and histogram rendering state
- clearSelection nulls the selection before the highlight repaint so the
just-deselected building cannot keep its stale selection color
- remap the selected building onto the new mesh on mesh swap (or drop it
when the building is gone) instead of keeping a stale old-mesh reference
- top labels rank by rendered building height again, and the color-label
branch reuses selectTopNByValue instead of sorting all matching leaves
- hovering a folder outside the focused subtree no longer rebuilds the
metrics-bar histograms from buildings that are not rendered
- screenshots measure the bottom bar via its inner footer (the host box is
always zero-height), so the capture actually crops the bottom bar
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf(visualization): cut metrics-bar and highlight hot paths
- visibleNodeMetricValues keys on the derived hovered-folder path, so leaf
hovers (10-30/s while sweeping) no longer re-walk the whole map; it also
collects only the displayed area/height/color metrics and drops the
unused sum
- topLevelNode depends on area/height metric instead of the whole
dynamicSettings slice, so search keystrokes and margin drags no longer
re-run the full layout for a fallback display value
- color range diagram computes percentiles with one sort instead of an
O(unique x N) scan, redraws only the colored areas for thumb-only
changes, and defers all work while its popover is closed
- metric select popovers render their option list lazily on open instead
of keeping ~300 buttons per popover in the DOM
- selection clicks update the highlight diff incrementally (repaint the
formerly selected building) instead of invalidating the cache and
re-uploading the full color buffer
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(visualization): remove dead metrics-bar code and unify popover shells
- delete the unused AccumulatedData/DynamicSettings/HoveredNode/SelectedNode
store+service mirror pairs (zero consumers)
- rename the duplicate root singletons: sidebarExplorer's AreaMetricStore to
ExplorerAreaMetricStore and 3dPrint's ColorModeStore (which forces
ColorMode.absolute) to Export3DColorModeStore, so auto-imports cannot mix
them up with the metricsBar stores
- remove never-called setters setIsEdgeMetricVisible,
setIsHeightAndColorMetricLinked and setMapColors from store and service
- metric select popover and the labels/scenarios segment use
SettingsPopoverShellComponent instead of hand-rolled popover markup (which
had already drifted), gaining the Firefox positioning fallback
- visibleNodeMetricValues uses the shared isLeaf helper
- fix the two remaining spec type errors so tsc --noEmit is clean
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(visualization): move fake-timer cleanup into afterEach
A failing assertion in the settings popover specs could previously skip the
inline jest.useRealTimers() call and leak fake timers into the rest of the
test file.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(visualization): add changelog entries for the review-findings round
Also check in the max-effort review plan and findings plan; all findings are
fixed except F48 (store/service layer collapse), which is deferred as a team
decision.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(visualization): exclude inversion flags from the color picker key type
The new boolean inversion flags on MapColors widened the template-indexed
access in cc-color-picker-for-map-color to string | boolean, which only the
AOT template check catches. Restrict mapColorFor to color-valued keys.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf(visualization): tighten per-node loops in blacklist matching and top-N selection
Benchmarked against the pre-refactor implementations with 200k nodes:
- BlacklistMatcher gains classify(path, isLeaf) that transforms the path once
and uses plain loops instead of Array.some closures; NodeDecorator and the
explorer counts use it, removing the ~10% decoration-pass regression the
shared-matcher refactor had introduced (now at parity, ~500ms for 200k
nodes x 6 rules, with folders still skipping negated exclude engines)
- selectTopNByValue caches the window's boundary value so the common case
(item not in the top n) costs one comparison instead of re-reading the
boundary item (2.2ms -> 1.4ms for 200k items)
Verified unchanged results and confirmed the earlier wins: percentile ranks
2928ms -> 14ms (100k values), per-rule explorer counts 985ms -> 466ms.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(visualization): restart the settings debounce on every keystroke
Retyping the value that was already pending skipped re-arming the timer, so
the commit could fire mid-typing. The number inputs now push the debounce
window out on each keystroke; the label settings panel also drops a pending
intermediate value when typing returns to the committed one.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* style(visualization): remove the vertical dividers between metrics-bar segments
Design decision after seeing them render; also reverts the wrapper divs that
only existed to give divide-x real boxes to paint between.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(visualization): pin metrics-bar histograms to the selected folder
The displayed metric values follow hovered ?? selected ?? top level, but the
histograms only knew hover and focus: after selecting a folder floor the
value stayed pinned while the histograms fell back to the whole map. The
histogram prefix now mirrors the same precedence (selected leaves are
ignored, and a selection outside the focused subtree cannot widen the view).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(config): add mise configuration for toolchain pinning and bootstrap tasks
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(visualization)!: remove the metrics-bar distribution histograms
The per-axis histograms and the color ramp did not add enough value to
justify their surface: delete the axisDistribution and axisColorRamp
components and the histogramBins util with their specs. The segments keep
the hovered/selected metric value and the min/max range of the rendered
buildings (still narrowing to the focused, hovered or selected subtree).
- rename DistributionSegmentComponent to MetricSegmentComponent, since the
distribution was the only thing the name referred to
- visibleNodeMetricValues collects only min/max per metric, no longer the
full value arrays the histograms binned
- the color segment derives its min/max labels from the global color metric
data alone and drops the ramp's colorMode/mapColors/colorRange wiring
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(visualization): route the attribute-type label through the store layer
metricChooserType injected the ngrx Store directly, which violates the
feature-only-stores-can-import-ngrx-store dependency-cruiser rule that
failed CI. The createAttributeTypeSelector is now exposed via
AttributeTypesStore/-Service like every other selector in the feature.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(visualization): remove the min/max value row from the metric segments
The row was the histograms' axis labeling and is not needed without them.
With it gone the whole visibleNodeMetricValues selector/store/service chain
and MetricSegmentBase have no consumers left and are deleted; the segments
show only the hovered/selected metric value.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(visualization): npm audit fix for dev dependency advisories
Bumps transitive dev dependencies (fast-xml-parser family, postcss, nanoid,
qs, ws, xmldom and others) to patched versions. The three newly added
transitive packages (@nodable/entities, xml-naming, anynum) are part of the
fast-xml-parser author's package family and were verified against the
registry and their tarball contents (no install scripts, no suspicious
code). The remaining high advisory is electron itself, which only an
across-major bump (audit fix --force) would address.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(config): disable npm install scripts repository-wide
Dependency lifecycle scripts (preinstall/install/postinstall) are the main
npm supply-chain attack vector; with ignore-scripts=true in .npmrc they can
no longer execute code during npm install. Audit of both lockfiles: no root
dependency has an install script, and all native visualization dependencies
(esbuild, lmdb, @parcel/watcher, msgpackr-extract, unrs-resolver, fsevents)
ship prebuilt platform packages whose install scripts are only fallbacks.
The two real exceptions are handled explicitly:
- electron: script/ensureElectron.js downloads the binary on demand before
npm run start / npm run package (this also repairs installs where
node_modules/electron/path.txt is missing)
- husky: git hooks are set up via npm run prepare, wired into the mise
install task
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* style(visualization): resolve Sonar findings on the PR
- extract the per-rule counting loops out of buildRulesWithCount to get its
cognitive complexity under the threshold (S3776)
- make the NaN guards on floored limits explicit (Number.isNaN || <= 0)
instead of the negated comparison Sonar flags (S1940) — plain <= 0 alone
would let NaN through
- use entries.at(-1) for the last resize observer entry (S7755)
- drop a type assertion that querySelector("footer") already implies (S4325)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(visualization): replace explanatory comments with expressive code
- selectTopNByValue/parseNumberInput: named helpers (rankNaNBelowEveryValue,
collectTopWindow, clamp) instead of comment blocks
- codeMapMesh: extract invalidateDiffCacheWhenPresentationModeChanged and
repaintFormerlySelectedWhenSelectionChanged
- metric alias lookup uses a Map, removing the hasOwn guard and its comment
- metricMetaValue builds one display view model from small pure functions;
the template shrinks to a single state switch
- explorer counts read the decorated isFlattened/isExcluded flags directly
(they are re-decorated on every blacklist change, so the matcher round
trip was redundant); the rule-count prefilter comment became
buildRuleEnginesMatchingNodeDecorator
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(visualization): split axis card and metric select popover
- extract cc-axis-card-header (label + settings cog) and share the card
content between the clickable and static variants via one template
instead of duplicating it per branch
- extract cc-metric-select-option so the popover template is the search
field plus a list, with the option row markup in its own component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(visualization): split blacklist matching out of codeMapHelper
codeMapHelper had grown into a grab-bag. The gitignore-based blacklist
machinery (transformPath, returnIgnore, addRulePatternsToEngine, the
BlacklistMatcher and the isPathBlacklisted/isPathHiddenOrExcluded helpers)
now lives in util/blacklist/blacklistMatcher.ts next to the other blacklist
utilities; codeMapHelper keeps only the generic code-map node helpers.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* style(visualization): keep the option list item inside the popover's ul
The li now lives in the parent list, the option component renders only the
button — fixes the Sonar item-tag warning and the invalid ul nesting.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Christian Huehn <christian.huehn@maibornwolff.de>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> C
Christian Hühn committed
5652c677fc3ff37c9a9e61e13165a1b8072b83a8
Parent: a97bb47
Committed by GitHub <noreply@github.com>
on 6/10/2026, 1:24:41 PM