SIGN IN SIGN UP

Feature/2/migrate UI feature architecture (#4503)

* refactor(visualization): migrate shared leaf components to features/shared

Story #2, PR 1 of 5. Move the framework-agnostic leaf UI out of ui/ into
the feature-slice architecture:

- actionIcon, errorDialog (component+service+model+po),
  loadingFileProgressSpinner, resetSettingsButton
  -> features/shared/components/
- getPartialDefaultState(+spec) -> state/store/util/ (pure store helper)

Keep the slices dep-cruiser compliant:
- Replace direct @ngrx/store injection in loadingFileProgressSpinner and
  resetSettingsButton with shared stores/ + services/ (component -> service
  -> store), so only stores/ touch @ngrx/store.
- Convert actionIcon.component.scss to daisyUI/Tailwind host classes
  (cc-primary -> bg-primary, cc-emphasized -> hover:bg-secondary); drop the
  dead .disabled rule.
- Break the errorDialog component<->service cycle by extracting
  ErrorDialogData into errorDialog.model.ts and typing the service host via
  a local interface (features may not contain internal cycles).
- errorDialog.service lives under components/ so external (non-feature)
  callers can still import it through the slice's public surface.

Behavior unchanged. tsc, full Jest suite, and dep-cruiser (0 errors) pass.

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

* refactor(visualization): migrate fileExtensionBar to a feature slice

Story #2, PR 2 of 5. Move ui/fileExtensionBar into the feature-slice
architecture (components/, services/, selectors/, stores/, facade.ts) and
expose the public component via features/fileExtensionBar/facade.

Keep the slice dep-cruiser compliant:
- Add stores/ (MetricDistributionStore, BlackListExtensionStore,
  DistributionMetricStore) so only stores/ touch @ngrx/store; the three
  services and the distributionMetric component now go component -> service
  -> store. BlackListExtensionStore wraps dispatchAfterPaint.
- Convert the 3 SCSS files (fileExtensionBar, distributionMetric,
  fileExtensionBarSegment) to daisyUI/Tailwind host + utility classes;
  keep cc-bar-section(-text) as semantic anchors and switch the segment
  spec to containment selectors.
- Extract the pure FileExtensionCalculator (+spec) out of the slice into
  util/fileExtension/. It is shared by codeMap's threeSceneService, so
  leaving it inside the feature created a codeMap -> fileExtensionBar
  -> facade -> component cycle. Moving it to util/ breaks that and
  pre-empts the same cycle once codeMap becomes a feature (PR 3).
- Repoint threeSceneService and codeCharta.component accordingly.

Behavior unchanged. tsc, full Jest suite (378 suites), and dep-cruiser
(0 errors) pass.

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

* refactor(visualization): migrate codeMap to a feature slice

Story #2, PR 3 of 5. Move ui/codeMap into features/codeMap, expose its
public surface via a facade, and bring it into the feature architecture.

- Move the whole codeMap tree (component, render/tooltip/mouseEvent/arrow
  services, threeViewer/, rendering/) to features/codeMap; add facade.ts
  re-exporting the component + the render/tooltip/mouseEvent/three* services
  + CodeMapBuilding/CodeMapMesh/indicesPerNode/CodeMapPageObject. Repoint all
  66 external importers (incl. ui/viewCube) to the facade.
- Full ngrx stores refactor: add stores/ (codeMap, codeMapTooltip,
  codeMapArrow, codeMapMouseEvent, codeMapRender, threeScene) so only stores/
  touch @ngrx/store; the 6 service/component files now read/dispatch through
  purpose-specific store accessors. Move the two pure createSelector files
  (+ setEdgeVisibility helper) into selectors/.
- Convert codeMap.component.scss to Tailwind (Tailwind `hidden` + #codeMap
  utility classes; keep the id for querySelector).
- Downgrade feature-no-circular-dependencies-between-features to 'warn':
  making codeMap a feature surfaced pre-existing bidirectional couplings
  codeMap<->labelSettings and codeMap<->sidebarInspector (mouse/render events
  drive labels; labels draw into the scene). These need dependency inversion
  and are tracked as a follow-up; the rule is documented to be raised back to
  'error' once cleared.

Behavior unchanged. tsc, full Jest suite (378 suites), and dep-cruiser
(0 errors) pass.

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

* refactor(visualization): migrate viewCube to a feature slice

Story #2, PR 4 of 5. Move ui/viewCube into features/viewCube (the last ui/
folder) and finish the feature-architecture migration of the UI.

- Move the cube component + meshGenerator/materials/mouseEvents service +
  zoomSlider into features/viewCube; add facade.ts exposing ViewCubeComponent
  and ViewCubeMouseEventsService. viewCube has no @ngrx/store usage, so no
  stores are needed. Repoint codeMap's three viewCube imports to the facade
  (codeMap <-> viewCube is a cross-feature cycle, allowed as a warning).
- Convert the 2 SCSS files to Tailwind: viewCube host (absolute/right/
  transition + [&.sideBarVisible]:right-[var(--cc-inspector-width)]) and the
  zoomSlider (vertical range input incl. ::-webkit/-moz thumb via arbitrary
  variants); tests key off data-testid, not classes. ui/ is now empty.

Fix a production-build regression introduced in PR 3: the codeMap facade
re-exported CodeMapPageObject from codeMap.po, which esbuild does not
tree-shake, pulling codeMap.po -> playwright.helper -> node "path" into the
browser bundle (ng build failed with `Could not resolve "path"`). Drop the
page-object re-export, import it directly in the one cross-feature e2e, and
exempt .e2e.ts/.po.ts from feature-cross-feature-only-via-public-api.

Behavior unchanged. tsc, full Jest suite (378 suites), dep-cruiser (0 errors)
and `ng build` (exit 0) pass.

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

* build(visualization): finish ui/ teardown and tighten the SCSS rule

Story #2, PR 5 of 5. The ui/ -> features/ migration is complete (the ui/
directory was emptied and removed across PRs 1-4), so tighten the guard rails:

- Broaden the dependency-cruiser SCSS rule from features/ to all of
  app/codeCharta/ (renamed no-component-scss-files): component SCSS is now
  forbidden app-wide; only the global app/app.scss + app/mixins.scss remain.
- Add a CHANGELOG Chore entry summarizing the migration.

Note: feature-no-circular-dependencies-between-features stays 'warn' (not
raised to 'error' as originally envisioned) because making codeMap a feature
surfaced real codeMap<->labelSettings and codeMap<->sidebarInspector cycles
that still need dependency inversion — tracked as a follow-up.

dep-cruiser (0 errors) and `ng build` (exit 0) pass.

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

* build(visualization): enforce feature-cycle rule as error, grandfather codeMap/viewCube

Raise feature-no-circular-dependencies-between-features back to 'error'
instead of leaving it 'warn'. To keep the known rendering-cluster cycles
from blocking it:

- Scope the rule to cross-feature cycles only (add the same-feature `$1`
  exclusion the cross-feature-public-api rule uses); intra-feature cycles
  stay covered by the app-wide 'no-circular' warn. This stops dep-cruiser
  from reporting the cycles on intra-feature first-hop edges.
- Grandfather the codeMap/viewCube rendering pair via pathNot. Every current
  cross-feature cycle edge (codeMap<->viewCube, codeMap<->labelSettings,
  codeMap<->sidebarInspector, viewCube->viewCubeToolbox->codeMap) touches
  codeMap or viewCube, so the rest of the feature graph is now enforced as
  an error.

Verified the rule is live: injecting a legend<->metricsBar cycle errors,
and the clean tree reports 0 errors. Follow-up: break the cluster cycles
via dependency inversion and drop the exemption.

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

* refactor(visualization): move idToBuilding into features/codeMap

idToBuilding maps node ids to CodeMapBuilding objects (a codeMap rendering
concept) and was a shared service in services/ that imported codeMap while
codeMap imported it back — a services->feature layering smell and one of the
grandfathered codeMap cycles.

- Move services/idToBuilding/idToBuilding.service.ts to
  features/codeMap/idToBuilding.service.ts and point its CodeMapBuilding
  import at the internal ./rendering/codeMapBuilding (no longer via facade).
- Repoint codeMap's internal consumers (arrow, mouseEvent, threeScene) to the
  intra-feature path; expose IdToBuildingService via the codeMap facade for
  the two external consumers (sidebarExplorer, nodeContextMenu).

The codeMap<->idToBuilding dependency is now intra-feature (covered by the
app-wide no-circular warn) instead of a cross-feature edge. Behavior
unchanged. tsc, full Jest suite (378 suites), and dep-cruiser (0 errors) pass.

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

* refactor(visualization): move 3DExports into features/3dPrint

The 3D-print export engine (3DPreview mesh models, geometry/color strategies,
generateXML, serialize3mf) lived in services/ but is consumed only by
features/3dPrint's export dialog. Move the whole services/3DExports tree to
features/3dPrint/3DExports so the print engine lives with its feature.

- It is largely self-contained; the only outward relative imports are
  codeCharta.model and codeMap's facade (indicesPerNode/CodeMapMesh) — bumped
  by one level for the deeper location. 3dPrint now depends one-way on codeMap
  via the facade (no new cycle).
- Repoint the export3DMapDialog consumer to the intra-feature path and fix the
  serialize3mf spec's __dirname fixture path (resources/minimalScene.json).

Behavior unchanged. tsc, full Jest suite (378 suites), dep-cruiser (0 errors)
and `ng build` (exit 0) pass.

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

* refactor(visualization): extract file loaders' shared helpers/constants to low layers

Structural prep (Tidy First) before turning the loaders into a feature:

- Extract CC_FILE_EXTENSION (".cc.json") from a static on LoadFileService into
  model/files/files.ts, so util/fileNameHelper and util/fileDownloader stop
  importing the service for a constant (removes a util->service inversion).
- Extract the MetricQueryParemter enum out of updateQueryParameters.effect into
  a sibling metricQueryParameter.ts; loadInitialFile.service imported the enum
  from the effect while the effect injects the service — extracting it breaks
  that cycle.
- Move the pure, ngrx-free helpers fileParser, fileRoot, and urlExtractor (+spec)
  from services/ into util/, where their util/state/feature consumers can use
  them without a layering inversion.

No behavior change. tsc, full Jest suite (378 suites), and dep-cruiser
(0 errors) pass.

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

* refactor(visualization): turn the file loaders into the features/loadFile slice

Move loadFile.service and loadInitialFile.service out of services/ into
features/loadFile (the services/ folder is now gone), behind a facade.

- Full ngrx stores refactor: only stores/ touch @ngrx/store. LoadFileStore
  wraps loadFile's few selects/dispatches; LoadInitialFileStore holds the
  ~53 dispatches that hydrate the whole store from a saved file/URL/IndexedDB
  (the apply*/map* settings logic moved in verbatim). The services keep the
  orchestration (flow, error dialogs, URL reading via UrlExtractor) and call
  store methods; loadInitialFile reads URL params and passes the values to
  LoadInitialFileStore.setMetricsFromUrlValues/setRenderState.
- facade.ts exposes LoadFileService, LoadInitialFileService, sampleFile1/2.
  Repoint navBar, globalSettings, codeCharta.component and the
  updateQueryParameters effect to the facade.

Behavior unchanged. tsc, full Jest suite (378 suites), dep-cruiser (0 errors)
and `ng build` (exit 0) pass.

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

* style(visualization): resolve SonarCloud findings from the feature migration

Address the 32 new code smells flagged on the PR:

- S3863 (x27): merge the duplicate same-module imports left by repointing
  several symbols to one feature facade (Biome's organizeImports is off, so it
  didn't merge them). 11 files across codeMap/viewCube/loadFile facade users.
- S7735 (x3): invert the negated `if (!(key in saved)) ... else` conditions in
  LoadInitialFileStore.apply{File,Dynamic,App}Settings (branch swap, equivalent).
- S2933 (x2): mark ThreeSceneService#subscription and #threeSceneStore readonly.

No behavior change. tsc, full Jest suite (378 suites), and dep-cruiser
(0 errors) pass.

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

* refactor(visualization): relocate feature-private utils into their slices

Move utils that are used exclusively by a single feature out of util/ and
co-locate them next to their consumer, following the feature-slice
architecture (features have no util/ folder):

- selectTopNByValue -> features/codeMap/
- formatCompactNumber -> features/sidebarExplorer/
- clipboardWriter -> features/viewCubeToolbox/services/
- loadFilesValidationToErrorDialog -> features/loadFile/services/
- uploadFiles/{createCCFileInput,readFiles} -> features/navBar/services/
- pipes/removeExtension.pipe -> features/navBar/

Also relocate the misplaced loadFilesValidationToErrorDialog.spec.ts (it
sat under util/uploadFiles/) next to its source, and fix the clipboardWriter
spec filename casing. Shared utils (algorithm/, codeMapHelper, color/,
blacklist/, ...) remain in util/.

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

---------

Co-authored-by: Christian Hühn <christian.huehn@maibornwolff.de>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
C
Christian Hühn committed
066912e95112e6f850726fc339c2122ad1a3c72a
Parent: f95015d
Committed by GitHub <noreply@github.com> on 6/25/2026, 6:15:34 AM