You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2026/08/28 12:20:35 UTC

(skywalking-horizon-ui) branch main updated: refactor(ui): move Modal and MonacoDiff into components/primitives (#143)

This is an automated email from the ASF dual-hosted git repository.

wu-sheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-horizon-ui.git


The following commit(s) were added to refs/heads/main by this push:
     new 9c6690c6 refactor(ui): move Modal and MonacoDiff into components/primitives (#143)
9c6690c6 is described below

commit 9c6690c6b2befcfcf9c114e20ef5b1adcdedf3fb
Author: 吴晟 Wu Sheng <wu...@foxmail.com>
AuthorDate: Fri Aug 28 20:20:29 2026 +0800

    refactor(ui): move Modal and MonacoDiff into components/primitives (#143)
    
    Both are generic — a dialog shell and a Monaco diff viewer — and neither
    contains a single reference to operate, cluster, inspect, dsl or live-debug.
    They lived under `features/operate/_shared/` because that is where they were
    first written, and fifteen files came to import them from there: `shell/`,
    `render/widgets/`, `layer/`, and three features besides operate. A shell-level
    prompt reaching into a feature folder for a dialog is the shape that says the
    address is wrong rather than the rule.
    
    Moving them takes the cross-feature imports the layering rule forbids from
    fourteen down to one — the admin editor for the 3D map reading that feature's
    own config composable, which is a page editing the thing it is the editor for.
    
    No behaviour change: the two files are unmodified apart from a relative
    `monaco/setup` import that the new depth broke, now on the `@/` alias like
    everything around it.
---
 apps/ui/CLAUDE.md                                                     | 4 +++-
 .../src/{features/operate/_shared => components/primitives}/Modal.vue | 0
 .../operate/_shared => components/primitives}/MonacoDiff.vue          | 2 +-
 apps/ui/src/features/admin/_shared/TemplateDiffModal.vue              | 4 ++--
 apps/ui/src/features/admin/infra-3d/Infra3dAdminView.vue              | 4 ++--
 apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue   | 4 ++--
 apps/ui/src/features/admin/layer-templates/PageEntityFilter.vue       | 2 +-
 .../features/admin/overview-templates/NewOverviewDashboardModal.vue   | 2 +-
 .../src/features/admin/overview-templates/OverviewTemplatesAdmin.vue  | 4 ++--
 apps/ui/src/features/admin/translations/TranslationsView.vue          | 4 ++--
 apps/ui/src/features/events/EventsPopout.vue                          | 2 +-
 apps/ui/src/features/operate/_shared/DestructiveConfirm.vue           | 2 +-
 apps/ui/src/features/operate/alerting-rules/AlertingRulesView.vue     | 2 +-
 apps/ui/src/features/operate/dsl/DslEditorView.vue                    | 2 +-
 apps/ui/src/features/operate/live-debug/LalCellPopout.vue             | 4 ++--
 apps/ui/src/layer/browser-errors/SourceMapManager.vue                 | 2 +-
 apps/ui/src/render/widgets/BrowserErrorPopout.vue                     | 4 ++--
 apps/ui/src/render/widgets/LogDetailPopout.vue                        | 4 ++--
 apps/ui/src/shell/TemplateConflictPrompt.vue                          | 2 +-
 19 files changed, 28 insertions(+), 26 deletions(-)

diff --git a/apps/ui/CLAUDE.md b/apps/ui/CLAUDE.md
index fca0faca..455f7962 100644
--- a/apps/ui/CLAUDE.md
+++ b/apps/ui/CLAUDE.md
@@ -85,6 +85,8 @@ Each control owns its own reset and its own indicator, and resolves as its query
 
 `api/` is the only path to HTTP. `shell/` knows about layers and routes, never about a feature's data. `controls/` owns the time range and the refresh ticker; pages subscribe and never own them. `state/` is global Pinia that survives navigation. `features/<feature>/` and `layer/<tab>/` are self-contained — views, composables and components together. `render/` is template-driven and generic. `components/{primitives,charts,icons}/` is feature-AGNOSTIC: the moment a shared component needs f [...]
 
-**Features do not import from each other**, and the exceptions today say what the rule is for rather than weakening it. `Modal` and `MonacoDiff` sit under `features/operate/_shared/` and are borrowed by admin and events — they are primitives in everything but their address, and moving them is a tidy-up nobody has done. The admin editor for the 3D map reads that feature's own config composable, which is a page editing the thing it is the editor for. Neither is one feature reaching into an [...]
+**Features do not import from each other.** The one exception today is the admin editor for the 3D map, which reads that feature's own config composable — a page editing the thing it is the editor for, not one feature reaching into another's *data*, which is what the rule exists to stop.
+
+A component that several features borrow is a primitive whichever folder it happens to sit in, and belongs in `components/primitives/`. `Modal` and `MonacoDiff` lived under `features/operate/_shared/` for a while because that is where they were first written; fifteen files across `shell/`, `render/`, `layer/` and three features imported them from there, which is the shape that tells you the address was wrong rather than the rule.
 
 **A chart component owns its ECharts instance; a VIEW never creates one.** Most go through the shared wrappers in `components/charts/`; a couple of chart types specific to one feature have their own component beside that feature, which is the same rule applied to a component that knows a feature's shape. What is banned is a view calling `echarts.init` inline, because then nothing owns disposal or theming. D3 lifecycles belong to a composable that tears down on unmount.
diff --git a/apps/ui/src/features/operate/_shared/Modal.vue b/apps/ui/src/components/primitives/Modal.vue
similarity index 100%
rename from apps/ui/src/features/operate/_shared/Modal.vue
rename to apps/ui/src/components/primitives/Modal.vue
diff --git a/apps/ui/src/features/operate/_shared/MonacoDiff.vue b/apps/ui/src/components/primitives/MonacoDiff.vue
similarity index 97%
rename from apps/ui/src/features/operate/_shared/MonacoDiff.vue
rename to apps/ui/src/components/primitives/MonacoDiff.vue
index 67f09930..a02e1994 100644
--- a/apps/ui/src/features/operate/_shared/MonacoDiff.vue
+++ b/apps/ui/src/components/primitives/MonacoDiff.vue
@@ -17,7 +17,7 @@
 <script setup lang="ts">
 import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
 import * as monaco from 'monaco-editor';
-import { setupMonaco, RR_THEME_NAME } from '../../../monaco/setup.js';
+import { setupMonaco, RR_THEME_NAME } from '@/monaco/setup';
 
 const props = withDefaults(
   defineProps<{
diff --git a/apps/ui/src/features/admin/_shared/TemplateDiffModal.vue b/apps/ui/src/features/admin/_shared/TemplateDiffModal.vue
index b5a7decf..b6972bea 100644
--- a/apps/ui/src/features/admin/_shared/TemplateDiffModal.vue
+++ b/apps/ui/src/features/admin/_shared/TemplateDiffModal.vue
@@ -35,8 +35,8 @@ import { computed, onMounted, ref, watch } from 'vue';
 import { useI18n } from 'vue-i18n';
 import { bff } from '@/api/client';
 import type { TemplateSyncRow } from '@/api/scopes/template-sync';
-import Modal from '@/features/operate/_shared/Modal.vue';
-import MonacoDiff from '@/features/operate/_shared/MonacoDiff.vue';
+import Modal from '@/components/primitives/Modal.vue';
+import MonacoDiff from '@/components/primitives/MonacoDiff.vue';
 import Btn from '@/components/primitives/Btn.vue';
 
 const { t } = useI18n({ useScope: 'global' });
diff --git a/apps/ui/src/features/admin/infra-3d/Infra3dAdminView.vue b/apps/ui/src/features/admin/infra-3d/Infra3dAdminView.vue
index 2ef4f56a..0987e220 100644
--- a/apps/ui/src/features/admin/infra-3d/Infra3dAdminView.vue
+++ b/apps/ui/src/features/admin/infra-3d/Infra3dAdminView.vue
@@ -57,8 +57,8 @@ import { useInfra3dLevels, type ResolvedLevel } from './useInfra3dLevels';
 import SyncStatusBanner from '@/features/admin/_shared/SyncStatusBanner.vue';
 import TemplateStatusBadge from '@/features/admin/_shared/TemplateStatusBadge.vue';
 import Infra3dLayerRow, { type LayerRow } from './Infra3dLayerRow.vue';
-import Modal from '@/features/operate/_shared/Modal.vue';
-import MonacoDiff from '@/features/operate/_shared/MonacoDiff.vue';
+import Modal from '@/components/primitives/Modal.vue';
+import MonacoDiff from '@/components/primitives/MonacoDiff.vue';
 
 // We hydrate the Layers section from the catalog union'd with config
 // keys, so an OAP layer the admin hasn't classified yet shows up here
diff --git a/apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue b/apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue
index 731d017a..b6cd2b84 100644
--- a/apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue
+++ b/apps/ui/src/features/admin/layer-templates/LayerDashboardsAdmin.vue
@@ -35,8 +35,8 @@ import type { AdminLayerTemplate } from '@/api/client';
 import type { DashboardWidget, InstanceAttributePredicate } from '@skywalking-horizon-ui/api-client';
 
 import SyncStatusBanner from '@/features/admin/_shared/SyncStatusBanner.vue';
-import Modal from '@/features/operate/_shared/Modal.vue';
-import MonacoDiff from '@/features/operate/_shared/MonacoDiff.vue';
+import Modal from '@/components/primitives/Modal.vue';
+import MonacoDiff from '@/components/primitives/MonacoDiff.vue';
 import DependencyConfigSection from './DependencyConfigSection.vue';
 import ProcessConfigEditor from './ProcessConfigEditor.vue';
 import TopologyConfigEditor from './TopologyConfigEditor.vue';
diff --git a/apps/ui/src/features/admin/layer-templates/PageEntityFilter.vue b/apps/ui/src/features/admin/layer-templates/PageEntityFilter.vue
index cfc4afca..5a2cfe84 100644
--- a/apps/ui/src/features/admin/layer-templates/PageEntityFilter.vue
+++ b/apps/ui/src/features/admin/layer-templates/PageEntityFilter.vue
@@ -36,7 +36,7 @@ import { useLayerInstances } from '@/layer/useLayerInstances';
 import { useLayerEndpoints } from '@/layer/useLayerEndpoints';
 import { instancePageMatcher } from '@/render/layer-dashboard/instancePageFilter';
 import TypeaheadSelect from '@/components/primitives/TypeaheadSelect.vue';
-import Modal from '@/features/operate/_shared/Modal.vue';
+import Modal from '@/components/primitives/Modal.vue';
 
 const { t } = useI18n({ useScope: 'global' });
 
diff --git a/apps/ui/src/features/admin/overview-templates/NewOverviewDashboardModal.vue b/apps/ui/src/features/admin/overview-templates/NewOverviewDashboardModal.vue
index 7116cda4..408edbeb 100644
--- a/apps/ui/src/features/admin/overview-templates/NewOverviewDashboardModal.vue
+++ b/apps/ui/src/features/admin/overview-templates/NewOverviewDashboardModal.vue
@@ -24,7 +24,7 @@
 <script setup lang="ts">
 import { ref, watch } from 'vue';
 import { useI18n } from 'vue-i18n';
-import Modal from '@/features/operate/_shared/Modal.vue';
+import Modal from '@/components/primitives/Modal.vue';
 import { vAutosize } from '@/utils/autosize';
 
 const { t } = useI18n({ useScope: 'global' });
diff --git a/apps/ui/src/features/admin/overview-templates/OverviewTemplatesAdmin.vue b/apps/ui/src/features/admin/overview-templates/OverviewTemplatesAdmin.vue
index 64c5436e..ef0039d9 100644
--- a/apps/ui/src/features/admin/overview-templates/OverviewTemplatesAdmin.vue
+++ b/apps/ui/src/features/admin/overview-templates/OverviewTemplatesAdmin.vue
@@ -54,8 +54,8 @@ import { refreshConfigBundle } from '@/controls/configBundle';
 import { stableStringify } from '@/utils/stableJson';
 import TemplatePicker, { type TemplatePickerEntry } from '@/features/admin/_shared/TemplatePicker.vue';
 import { useTemplateSync } from '@/features/admin/_shared/useTemplateSync';
-import Modal from '@/features/operate/_shared/Modal.vue';
-import MonacoDiff from '@/features/operate/_shared/MonacoDiff.vue';
+import Modal from '@/components/primitives/Modal.vue';
+import MonacoDiff from '@/components/primitives/MonacoDiff.vue';
 import OverviewEditCanvas from './OverviewEditCanvas.vue';
 import OverviewWidgetDrawer from './OverviewWidgetDrawer.vue';
 import NewOverviewDashboardModal from './NewOverviewDashboardModal.vue';
diff --git a/apps/ui/src/features/admin/translations/TranslationsView.vue b/apps/ui/src/features/admin/translations/TranslationsView.vue
index 99b22e9a..fc0f19c7 100644
--- a/apps/ui/src/features/admin/translations/TranslationsView.vue
+++ b/apps/ui/src/features/admin/translations/TranslationsView.vue
@@ -44,8 +44,8 @@ import LayerDashboardCanvas from '@/features/admin/_shared/LayerDashboardCanvas.
 import OverviewDashboardCanvas from '@/features/admin/_shared/OverviewDashboardCanvas.vue';
 import TemplatePicker from '@/features/admin/_shared/TemplatePicker.vue';
 import TypeaheadSelect from '@/components/primitives/TypeaheadSelect.vue';
-import Modal from '@/features/operate/_shared/Modal.vue';
-import MonacoDiff from '@/features/operate/_shared/MonacoDiff.vue';
+import Modal from '@/components/primitives/Modal.vue';
+import MonacoDiff from '@/components/primitives/MonacoDiff.vue';
 import TranslationFieldPanel from './TranslationFieldPanel.vue';
 import { useTranslationPicker } from './useTranslationPicker';
 import { useTranslationDraft, type EffectiveSource } from './useTranslationDraft';
diff --git a/apps/ui/src/features/events/EventsPopout.vue b/apps/ui/src/features/events/EventsPopout.vue
index bf068688..bcee3d0f 100644
--- a/apps/ui/src/features/events/EventsPopout.vue
+++ b/apps/ui/src/features/events/EventsPopout.vue
@@ -24,7 +24,7 @@
 <script setup lang="ts">
 import { computed, ref, watch } from 'vue';
 import { useI18n } from 'vue-i18n';
-import Modal from '@/features/operate/_shared/Modal.vue';
+import Modal from '@/components/primitives/Modal.vue';
 import EventsGantt from './EventsGantt.vue';
 import EventsDetailPanel from './EventsDetailPanel.vue';
 import { useEventsWindow, PRESETS, MAX_CUSTOM_MS } from './useEventsWindow';
diff --git a/apps/ui/src/features/operate/_shared/DestructiveConfirm.vue b/apps/ui/src/features/operate/_shared/DestructiveConfirm.vue
index ffa846a2..33e445d6 100644
--- a/apps/ui/src/features/operate/_shared/DestructiveConfirm.vue
+++ b/apps/ui/src/features/operate/_shared/DestructiveConfirm.vue
@@ -17,7 +17,7 @@
 <script setup lang="ts">
 import { computed, ref, watch } from 'vue';
 import { useI18n } from 'vue-i18n';
-import Modal from './Modal.vue';
+import Modal from '@/components/primitives/Modal.vue';
 import Btn from '@/components/primitives/Btn.vue';
 
 const { t } = useI18n();
diff --git a/apps/ui/src/features/operate/alerting-rules/AlertingRulesView.vue b/apps/ui/src/features/operate/alerting-rules/AlertingRulesView.vue
index 9be6f9b7..17bd128b 100644
--- a/apps/ui/src/features/operate/alerting-rules/AlertingRulesView.vue
+++ b/apps/ui/src/features/operate/alerting-rules/AlertingRulesView.vue
@@ -46,7 +46,7 @@ import {
   type AlarmRunningContext,
   type AlarmMqeSnapshotSeries,
 } from '@/api/client';
-import Modal from '@/features/operate/_shared/Modal.vue';
+import Modal from '@/components/primitives/Modal.vue';
 import Sparkline from '@/components/charts/Sparkline.vue';
 import { useOapInfo } from '@/shell/useOapInfo';
 
diff --git a/apps/ui/src/features/operate/dsl/DslEditorView.vue b/apps/ui/src/features/operate/dsl/DslEditorView.vue
index 543ef3ff..44931a8f 100644
--- a/apps/ui/src/features/operate/dsl/DslEditorView.vue
+++ b/apps/ui/src/features/operate/dsl/DslEditorView.vue
@@ -30,7 +30,7 @@ import { sha256Hex } from '@/features/operate/dsl/contentHash';
 import Btn from '@/components/primitives/Btn.vue';
 import Pill from '@/components/primitives/Pill.vue';
 import MonacoYaml from '@/features/operate/_shared/MonacoYaml.vue';
-import MonacoDiff from '@/features/operate/_shared/MonacoDiff.vue';
+import MonacoDiff from '@/components/primitives/MonacoDiff.vue';
 import DestructiveConfirm from '@/features/operate/_shared/DestructiveConfirm.vue';
 import ApplyProgress from '@/features/operate/dsl/ApplyProgress.vue';
 import AdminFeatureWarning from '@/shell/AdminFeatureWarning.vue';
diff --git a/apps/ui/src/features/operate/live-debug/LalCellPopout.vue b/apps/ui/src/features/operate/live-debug/LalCellPopout.vue
index cfc092de..caa26656 100644
--- a/apps/ui/src/features/operate/live-debug/LalCellPopout.vue
+++ b/apps/ui/src/features/operate/live-debug/LalCellPopout.vue
@@ -27,9 +27,9 @@
  */
 import { computed, ref, watch } from 'vue';
 import { useI18n } from 'vue-i18n';
-import Modal from '@/features/operate/_shared/Modal.vue';
+import Modal from '@/components/primitives/Modal.vue';
 import MonacoView from '@/features/operate/_shared/MonacoView.vue';
-import MonacoDiff from '@/features/operate/_shared/MonacoDiff.vue';
+import MonacoDiff from '@/components/primitives/MonacoDiff.vue';
 import FloatingPanel from '@/components/primitives/FloatingPanel.vue';
 import Icon from '@/components/icons/Icon.vue';
 
diff --git a/apps/ui/src/layer/browser-errors/SourceMapManager.vue b/apps/ui/src/layer/browser-errors/SourceMapManager.vue
index 75ba8552..12bbf6af 100644
--- a/apps/ui/src/layer/browser-errors/SourceMapManager.vue
+++ b/apps/ui/src/layer/browser-errors/SourceMapManager.vue
@@ -26,7 +26,7 @@ import { useI18n } from 'vue-i18n';
 import type { SourceMapDescriptor, SourceMapUsage } from '@/api/client';
 import Btn from '@/components/primitives/Btn.vue';
 import Icon from '@/components/icons/Icon.vue';
-import Modal from '@/features/operate/_shared/Modal.vue';
+import Modal from '@/components/primitives/Modal.vue';
 import { useAuthStore } from '@/state/auth';
 
 const { t } = useI18n({ useScope: 'global' });
diff --git a/apps/ui/src/render/widgets/BrowserErrorPopout.vue b/apps/ui/src/render/widgets/BrowserErrorPopout.vue
index 261d4067..193165a0 100644
--- a/apps/ui/src/render/widgets/BrowserErrorPopout.vue
+++ b/apps/ui/src/render/widgets/BrowserErrorPopout.vue
@@ -22,7 +22,7 @@
   the per-layer Browser Logs tab; the host owns the maps list (so it can also
   render the SourceMapManager), this popout owns the per-row resolve call.
 
-  Built on `_shared/Modal.vue` (backdrop + Escape + close).
+  Built on `components/primitives/Modal.vue` (backdrop + Escape + close).
 
   Props:
     row     — the BrowserErrorRow to show; `null` keeps the popout closed.
@@ -36,7 +36,7 @@ import { computed, ref, watch } from 'vue';
 import { useI18n } from 'vue-i18n';
 import type { BrowserErrorRow, ResolveResponse, SourceMapDescriptor } from '@/api/client';
 import { bffClient, describeApiError } from '@/api/client';
-import Modal from '@/features/operate/_shared/Modal.vue';
+import Modal from '@/components/primitives/Modal.vue';
 
 const { t } = useI18n();
 
diff --git a/apps/ui/src/render/widgets/LogDetailPopout.vue b/apps/ui/src/render/widgets/LogDetailPopout.vue
index 1f9ccbd7..1efe9a32 100644
--- a/apps/ui/src/render/widgets/LogDetailPopout.vue
+++ b/apps/ui/src/render/widgets/LogDetailPopout.vue
@@ -21,7 +21,7 @@
   tags table, the service / instance / endpoint / time meta strip, a
   copy button, and the trace-id link.
 
-  Built on `_shared/Modal.vue` (backdrop + Escape + close). Used by BOTH
+  Built on `components/primitives/Modal.vue` (backdrop + Escape + close). Used by BOTH
   the per-layer Logs tab and the cross-layer Log inspect view. The host
   owns the trace jump — the popout just emits `jump-trace`.
 
@@ -35,7 +35,7 @@
 import { computed, onBeforeUnmount, ref, watch } from 'vue';
 import { useI18n } from 'vue-i18n';
 import type { LogRow } from '@/api/client';
-import Modal from '@/features/operate/_shared/Modal.vue';
+import Modal from '@/components/primitives/Modal.vue';
 
 const { t } = useI18n();
 
diff --git a/apps/ui/src/shell/TemplateConflictPrompt.vue b/apps/ui/src/shell/TemplateConflictPrompt.vue
index ff11cb93..1bf76931 100644
--- a/apps/ui/src/shell/TemplateConflictPrompt.vue
+++ b/apps/ui/src/shell/TemplateConflictPrompt.vue
@@ -27,7 +27,7 @@
 import { computed, ref } from 'vue';
 import { useI18n } from 'vue-i18n';
 import { useRoute, useRouter } from 'vue-router';
-import Modal from '@/features/operate/_shared/Modal.vue';
+import Modal from '@/components/primitives/Modal.vue';
 import { useLayers } from '@/shell/useLayers';
 import { useConfigBundle } from '@/controls/configBundle';
 import { useLocalTemplateEdits } from '@/controls/localTemplateEdits';