feat(analysis): add DomainLanguageParser emitting cc.json 2.0 domain … - #4507
feat(analysis): add DomainLanguageParser emitting cc.json 2.0 domain …#4507ChristianHuehn wants to merge 52 commits into
Conversation
…lens
Port the DomainLanguageCharta analyzer into ccsh as a first-class parser
(`ccsh domainlanguageparser`) that extracts domain vocabulary (word
frequencies, optional n-grams, TF-IDF) from source code and writes it into
the reserved cc.json 2.0 `domain` lens, keyed by node id over the standard
files tree.
- New writer (DomainProjectGenerator) builds a Project + opaque `domain`
lens; keys computed with NodeId.fromSegments so they resolve against the
ids the 2.0 writer emits (File leaves, Folder dirs, empty-segment root).
tfidf omitted when null; deterministic, byte-stable output.
- picocli command (CommonAnalyserParameters + AnalyserInterface) with a
Dialog for interactive mode; registered in Ccsh.kt, ccsh build, settings,
and the ccsh analyser tests. Not in AttributeGeneratorRegistry (opaque
lens, not numeric metrics).
- Reuses the ported analysis engine verbatim; retires DLC's {tree, words}
JSON output and kotlinx-cli entrypoint.
- Dependency dedupe: drop jgit (-> AnalyserInterface GitignoreHandler),
mordant (-> model ProgressTracker), kotlinx-cli and the kotlin
serialization plugin; keep kasechange and kotlinx-serialization-json.
- Docs: analysis README parser table, module README, CHANGELOG, gh-pages
parser page + sidebar.
Verified equivalent to the original DLC tool: same word/frequency/TF-IDF
data on identical inputs; domain lens survives a mergefilter round-trip.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Important Review skippedToo many files! This PR contains 478 files, which is 378 over the limit of 100. To get a review, narrow the scope: Upgrade to a paid plan to raise the limit. Usage-priced reviews support at most 300 files. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (3)
📒 Files selected for processing (482)
You can disable this status message by setting the ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…in view The non-map race branch skipped the very file-state emission that raised the indicator, then waited for a subsequent one. A file-panel change is usually the last emission there is, so on /domain the spinner hung until the 60s deadline. Distinguish the two raisers: a file-panel change has already landed and settles on the current file set, while a load raises the indicator before the fetch and must wait for filesLoaded first. isPendingHeavyDispatch$ had the same shape of defect: only renderCodeMap$ cleared it, and the blacklist actions that set it are not in actionsRequiringRerender, so a dispatch that changed nothing latched it true for the rest of the session. Add a backstop deadline that cannot outlive its own dispatch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move axisCard, sliderNumberInput, settingsPopoverShell and the settingsInput helpers out of metricsBar into features/shared so a second settings bar can compose them, and replace the metricsBar host chrome with a reusable BarShellDirective. Pure structural change: the vertical offset stays with each bar via the BAR_BOTTOM_* constants. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Load the cc.json 2.0 domain lens into a domainLensSource state home and project it through a pure domain lens, alongside a domainBar home for the word-cloud presentation settings. Merge domain words per file on load, re-keying paths the way edges are handled so multi-file mode aggregates correctly, and persist the domainBar settings via saveCcState. Also teach the ValidationTool schema and simplecc.sh about the domain lens, and carry the lens in both sample1.cc.json copies. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lift the positioning and show/hide logic out of the codeMap tooltip service into a standalone hoverTooltip service so a non-3D view can reuse it. Behaviour is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Split the single page into routed metrics and domain views, with a nav bar view switcher and a keep-alive route reuse strategy so switching back does not rebuild the map. Routing uses hash location and preserves query params across switches. The domain view renders the domain lens as an echarts word cloud with a domainBar for shape, rotation and sizing settings, built on the shared bar UI kit. The sidebar explorer is generalized behind an explorerHost so both views drive it, and the loading spinner becomes per-view via a viewReadiness store. Adds a lint:styles check enforcing Tailwind utilities over component style files, wired into the visualization CI workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
HoverTooltipRow has no importers outside the service, so exporting it only widened the module's surface. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The nav bar sits above the router outlet and outlives every view switch, so it rendered one fixed set of trailing controls regardless of what was on screen — putting the map-only 3D Print and Explore/Compare on the domain view. Each view now declares the controls it wants in viewNavBarControls, and the nav bar renders from that declaration. The active view comes from the URL rather than view lifecycle, because the route-reuse strategy detaches views instead of destroying them. Each divider is the trailing separator of the control above it, so a hidden control takes its divider with it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The layout silently left out every word it could not place, so large maps
showed far fewer words than the Words slider promised, with no indication.
- Fit all words now defaults to on, so words are shrunk rather than dropped.
- A new Draw outside bounds toggle lets words render past the layout edge;
the size-range fit is skipped when it is on, since nothing gets skipped.
- The cloud reports any remaining shortfall ("212 of 300 words fit …"),
counted from the graphic elements the layout hands out — no public echarts
API reports it.
- The Words slider goes up to 1000 (previously 300).
Renders also got slower with every interaction: echarts-wordcloud dispatches
its abort CustomEvent without cancelable: true, so the preventDefault()-based
cancel was a no-op and every superseded layout kept placing words in the
background. Patched via patch-package, which also disposes the previous
layout per chart when a new one starts. A render after 15 interrupted
layouts drops from 19.7 s to 1.3 s.
Resizes no longer run their own debounce calling chart.resize() directly —
that laid out once and then again for the re-fitted font clamp, the visible
double render. Words, settings and resizes now funnel through one debounce,
which measures height as well as width and skips a detached 0x0 view.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Slim down the DomainLanguageParser (drop WordAnalyzer) and reorganize the visualization domain feature: move the word cloud into the renderer, consolidate domainBar segments, and relocate view-readiness/domain state into the routing and store layers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ttled Excluding a file extension (or otherwise changing a settled map) gated its render on a staleness flag that a sibling effect only raised after the changed data had already passed through, so the render was skipped and the metrics spinner stayed up forever. Split the trigger: a genuine data/setting change always rebuilds the visible map, while the staleness check now guards only the switch-back-to-metrics path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extract the sidebar explorer into shared row/selection/sort/context-menu ports so the domain and metrics views can host it independently. Move formatCompactNumber to util, add per-view explorer sort state, and wire domain sorting selectors. Document the enforced layering in TARGET-ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The save-trigger union registered only 7 of the 11 domain-state setters that are dispatched on restore, so drawOutOfBound, shrinkToFit, sortingOrder and sortingOrderAscending were restored on load but never written back — the per-view sort silently did not survive a reload. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the Language.keywordFilter field and its 17 ResourceKeywords(...) arguments: it had zero production read sites, since real language filtering runs through ConfigurationBuilder, which loads the keyword resources itself. The keyword classes (LanguageKeywords/ResourceKeywords/ResourceKeywordLoader) stay — ConfigurationBuilder and PathScopedKeywordProvider use them directly. Remove the three production StopWordFilter methods with no production caller (filter(words), filter(words, filePath), isExcluded(word)); only the path-aware isExcluded(word, filePath) is used (FilterStage). Drop the duplicate weight validation from ExtractionWeights so weight positivity is asserted once, at the CLI boundary where the message can name the offending --*-weight option. To avoid discarding real coverage, the affected tests are routed through the surviving APIs rather than deleted: LanguageTest loads each keyword resource directly (keeping every keyword file referenced and its contents validated), and the StopWordFilter exclusion-algorithm tests call the retained path-aware method via small test-only helpers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
V18 seeded the state root under the old "domainBar" key and V19 immediately renamed it to "domainState" — two migrations fighting over a key that never shipped in any released blob (main is at v16; v17-v19 are all new on this branch). Collapse them into a single V18 that seeds "domainState" directly, drop V19 and its registry entry, and set DB_VERSION = 18. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the hand-rolled router.url.split("?")[0] === routeLinks.domain in
RedirectAwayFromDomainViewEffect with the existing viewIdForLink(...), so
URL-to-view parsing lives only in routePaths. Extract pathToNodeName(path,
fallback) into nodePathHelper and call it from both the word-cloud read store
and the domain view, which had each hand-rolled the leaf-name idiom with
different fallbacks.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Route the navBar, fileExtensionBar and bottomBar imports of the shared PublishesHeightDirective through the shared feature's public-API barrel instead of reaching into its internal path, clearing three dependency-cruiser feature-cross-feature-only-via-public-api violations. Also apply biome's organizeImports fixes that had slipped past the pre-commit hook. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
findApplicableFrameworks returned the frameworks of the FIRST enclosing directory found while iterating an unordered HashMap, so for a file nested under more than one framework directory (e.g. a monorepo with React at the root and Angular in a sub-package) which keywords applied depended on hash iteration order — nondeterministic across runs and silently dropping the other directory's keywords. Union the frameworks of every enclosing directory instead; the result no longer depends on iteration order. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- FrameworkDetector: extract the shared find-files/parse/identify/store skeleton and a walkFiles helper across the JS and C# ecosystems; both now store results through the same per-directory merge, fixing the inconsistency where the C# path overwrote (existing + frameworks) while the JS path merged. - Extract parseWordLines, shared by ResourceKeywordLoader and DlcIgnoreParser, so the keyword-file format lives in one place. - Use Map.merge for the count-accumulation idiom (TfIdfCalculator, DirectoryWordAggregator). - TestFileDetector: fold the identical Kotlin/Java TypeName+Test patterns into one helper and share matchesAnyExtension with FileFilter. - ConfigurationBuilder: inject FrameworkDetector and DlcIgnoreParser via the constructor. - Delete the zero-assert SilentProgressReporterTest (a null object with nothing to assert). - Trim redundant comments (AnalysisConfiguration section dividers, WordFrequency KDoc) and rename a single-letter loop index. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- domainExplorerSelection.topWords now calls the exported selectTopWords instead of re-implementing the rank-by-sizing-value comparator, so the hover tooltip and the rendered cloud can no longer rank words differently. - Single-source the metrics-view explorer capabilities as DEFAULT_EXPLORER_CAPABILITIES, reused by the view provider and the port mock instead of two identical literals. - Extract seedRootIfAbsent from the near-identical v17/v18 IndexedDB seed migrations. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ivider The domain view's cloud container fell back to a 28px bottom-bar height while every other consumer used 32px, so the two disagreed until the bottom bar published its measured height. Align on 32px and name the bar-height fallbacks (DEFAULT_BOTTOM_BAR_HEIGHT_PX, DEFAULT_FILE_EXTENSION_BAR_HEIGHT_PX) so the TS-side calc() strings share one source. Extract the thrice-repeated nav-bar hairline into a cc-nav-divider component. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DomainViewComponent owned a clipboard-copy-with-transient-feedback concern (navigator.clipboard, a copied signal, a feedback timeout and its DestroyRef teardown) that had nothing to do with composing the view. Move it into a component-scoped CopyToClipboardService with its own coverage, leaving the component to compose and delegate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DEFAULT_BOTTOM_BAR_HEIGHT_PX and DEFAULT_FILE_EXTENSION_BAR_HEIGHT_PX are only referenced within barShell.directive.ts, so exporting them tripped knip's unused-export check (missed in the prior commit because only the dependency-cruiser line of the lint output was inspected). Make them module-private. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SourceAnalyzer and DirectoryWordAggregator both built a WordFrequency and looked its TF-IDF score out of the scores map by hand. Add WordFrequency.withScore and call it from both so the score-attachment lives in one place. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…spec - Extract withRangeMin/withRangeMax so the rotation and word-sizing popovers stop hand-indexing the [min,max] tuple (a transposition risk when only one is edited). - Cross-reference the word-cloud brand colors between color.util.ts and tailwind.css so the duplicated fallback literals cannot silently drift. - Reduce metricsExplorerRow.spec to the adapter's wiring; the projection branches it re-tested are already covered by explorerRow.projection.spec. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a tree-find bar to the domain explorer (reveals/expands/scrolls to matches), a reusable toast that explains the domain->map redirect, and publish the explorer width so the floating settings bars center clear of the sidebar. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Readable shape labels (Circle/Heart/...), Title-Case toggle labels, 10px smallest-word floor, horizontal-dominant default rotation, a 'Metrics' switcher label, and keeping the selection when a folder is collapsed in the domain view (metrics view unchanged). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The fill step enlarged fonts toward containerWidth*0.2, and echarts-wordcloud layout cost scales with word area (worse with shrinkToFit), so a single layout could take seconds on wide windows. Keep the width-based shrink (never drops the largest word) and the horizontal rotation default; drop only the enlargement. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DomainState gained sortingOrder/sortingOrderAscending, but several test mocks still built it from defaultWordCloudSettings, breaking tsc. Route them through defaultDomainState, preserving each test's intent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…an complete Ignore analysis-root .cc.json/.gz analyser output (and the internal brand asset), and correct the per-view-loading-indicator plan whose status was stale (the redesign is committed in 7b3ee03). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a logoM shape that lays the words out inside the M mark via an echarts-wordcloud mask image. The mask is the logo's two arch paths only (wordmark dropped, cropped to the M's bounding box) as an inline data URI, loaded by the component and threaded into the pure option builder, which keeps the mask's aspect ratio and falls back to a circle until the image resolves. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The domain view now offers the metrics view's screenshot capability: a camera button floating above the cloud, the Ctrl+Alt+S (file) and Ctrl+Alt+F (clipboard) hotkeys, and the existing screenshot-to-clipboard preference. The export is the word cloud alone, rendered by ECharts at the device pixel ratio on a transparent background and cropped to the words. Screenshot service, clipboard writer and button move out of viewCubeToolbox into a shared screenshot feature behind a SCREENSHOT_CAPTURE token, so each toolbox supplies what its view captures. Both views are kept alive across a switch, so each button guards its hotkeys on the active view and unbinds only its own handler. Canvas cropping is shared by both captures.
Replace every suppression with a real fix rather than a relocated escape hatch: - fileValidator.spec: checkErrors guards the load boundary, so its input is raw parsed JSON, not an already-valid ExportCCFile. Building the malformed node through that same parse boundary typechecks without a cast, dropping the @ts-expect-error. - gameObjectsValidator.spec: the validator takes a JSON string, so its input may legitimately hold schema-violating values. A local type widens exactly the two mutated fields to unknown, dropping both @ts-ignore. Split the test, which was asserting valid and invalid in one body. - version-manager: the file already declares ImportMeta.main via declare global, so the @ts-ignore suppressed an error that no longer existed. - EventEmitter: hoisting the listener array into a local preserves the narrowing a naive length check would break, dropping the biome-ignore left from the Biome 2 migration. - readFiles: FileList is iterable under DOM.Iterable, which the ES2022 target pulls in, so plain for-of compiles and the useForOf ignore is obsolete. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 6-lens branch review and the domain-language-parser code review, both still carrying open follow-ups. The generated map and the plans/ working notes are deliberately left untracked. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Only build ran 'npm run patch', so after a dependency reinstall 'npm run dev' served an unpatched echarts-wordcloud — its layout-abort fix missing, so interrupted layouts piled up and the word cloud got progressively slower. patch-package is idempotent, so this just costs ~1s of dev startup. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Analyser output dropped at the repo root (e.g. maibornwolff-domain.cc.json) cluttered git status and broke 'npm run format:check', since biome tried to reformat the minified map. Tracked fixtures all live in subdirectories and stay visible. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ktlint_official requires an expression body for single-expression functions, so the block-body-everywhere rule contradicted the gate that actually runs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Keeps the WHY notes; the renderCodeMap rationale is cut to the two lines that state the past bug rather than deleted. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Moves the slider min/max/step literals out of both domain-bar popover templates into named bounds on the components. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Names the two passes in DirectoryWordAggregator, folds the two corpus traversals in TfIdfCalculator into one, moves the substring reduction out of NgramsStage into its own class, drives FrameworkDetector from an Ecosystem value object, and drops the KDoc that restates the declarations. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces toContainText with toHaveText in the domain path bar, the inspector node name and the scenario apply dialog. The error-message assertions stay partial: those texts carry URLs and concatenated validation output. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The reporter was only built with --verbose, the inverse of every other parser, so a plain run showed nothing. The bar writes to stderr, so a piped cc.json is unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
An IndexedDB restore commits the loaded file twice in one task: first re-parsed from the persisted state, which loses the domain lens through the flat 1.x export shape, and only then the persisted file state that carries it. The redirect acted on the first of the two, so every refresh on #/domain bounced to the map view with the no-domain-data toast. The condition is now read once the file-store writes of a task have settled. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Anchors the domain toolbox where the metrics view's view-cube toolbox sits instead of centering it over the cloud. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Also correct the Code Smells scenario to use the sonar_code_smells metric. The Authors scenario shows the number of authors per file with an absolute color mode and an inverted color range from 2 to 3. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Default to no comment. Write one only when the code cannot be made to explain itself and a reader would otherwise draw a wrong conclusion. Naming is the tool: rename, extract a named helper, introduce a named constant. Replaces "group related functions with section comments" with top-down ordering, and aligns CODE_QUALITY.md's rule and its list of comment smells. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove the comments this branch added and, where a comment carried real information, move that information into a name instead: - dispatchAfterPaint: afterSpinnerHasBeenPainted, isRunningInTests, dispatchAll - renderCodeMap.effect: named streams instead of an annotated inline merge - explorerTreeLevel: EXPLORER_SCROLL_HOST_SELECTOR, closeContextMenuOnScroll - publishesHeight: publishHeightUnlessDetached - domain.selectors: selectedNodePath, which is what the parameter is - ProgressReporter: totalItems, completedItems, restartEtaBaseline isDimmed becomes isInactive: the flag renders both 50% opacity on the name and a grey icon, and comes from "no area in the current metric". Split three files along their responsibilities: - wordCloudOption.builder into the option model, top-word selection, the size-range fit, the tooltip and the per-word colour - wordCloud.component into wordCloudChartHost (the ECharts lifecycle) and wordCloudDescription (the accessible text) - domainWords.merger into the word-bank accumulator and the combiners Move localStorage out of the services: LocalStorageRepo is now the only code touching the API, with ExplorerWidthRepo, ExplorerCollapseRepo and VersionRepo on top. The services keep their state and rules. Drop the unused ScreenshotCapture re-export from the screenshot facade. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fold the domain view's own development churn (word sizing, settings bar areas, tooltip, spinner and layout regressions) into the feature entry it belongs to, since none of it ever reached a user. Record the Authors scenario and the Code Smells metric correction, which did have no entry, and describe the domain language parser's CLI surface. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…roles The toast region and the word cloud's fit notice become <output>, the view switcher a labelled <nav>, the settings popover a <dialog>, and the cloud's text alternative a <figcaption> inside a <figure>. JSDOM loads no author stylesheet, so it would hide a closed <dialog> that the browser shows; the unit setup mirrors the app's own display declaration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Raise the type library to ES2023 for it, and hoist the reveal service's default options out of the signature so each call shares one object. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
waitForFunction does not await a promise its predicate returns, and a promise object is truthy, so the IndexedDB read passed on the object alone — for a file name that could never exist it returned in 13ms. The restore test then reloaded while the debounced save was still pending and booted the sample files instead: 2 of 12 runs. Polling through page.evaluate, which does await, makes it 0 of 12. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The refresh test asked the persistence wait for "sample1 +1", the label the map selector shows for the boot pair, while the record keys files by "sample1.cc.json". The wait could never be satisfied — invisible until the wait started waiting for real. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
|



…lens
Port the DomainLanguageCharta analyzer into ccsh as a first-class parser (
ccsh domainlanguageparser) that extracts domain vocabulary (word frequencies, optional n-grams, TF-IDF) from source code and writes it into the reserved cc.json 2.0domainlens, keyed by node id over the standard files tree.domainlens; keys computed with NodeId.fromSegments so they resolve against the ids the 2.0 writer emits (File leaves, Folder dirs, empty-segment root). tfidf omitted when null; deterministic, byte-stable output.Verified equivalent to the original DLC tool: same word/frequency/TF-IDF data on identical inputs; domain lens survives a mergefilter round-trip.
{Meaningful title}
Please read the CONTRIBUTING.md before opening a PR.
Closes: #
Description
Descriptive pull request text, answering:
Definition of Done
A PR is only ready for merge once all the following acceptance criteria are fulfilled:
Screenshots or gifs