Repository navigation
Vue frontend duplication campaign — 9,666 dup lines across 274 component pairs (CSS/HTML/TS) #9859
Description
Summary
The Vue frontend is the single largest source of code duplication in the repo: 9,666 duplicated lines across 274 component pairs (jscpd -k 70 -l 8 --skip-comments), broken down as:
| Kind | Dup lines | Risk to dedup |
|---|---|---|
<style scoped> CSS |
7,649 | High — cascade-order sensitive, no visual-regression net |
<template> HTML |
1,335 | Medium — markup change, migrate onto existing BaseModal.vue |
<script> / .ts logic |
682 | Low — verifiable by type-check, but small & scattered |
Discovered during a duplication-elimination sweep on Dev_new_gui. Deliberately deferred from that sweep because the changes cannot be verified visual-identical in a headless environment, and the sweep's hard constraint was behavior/visual preservation.
Why deferred (not done in the dedup sweep)
- No safety net. None of the worst offenders (
AddSourceModal,ShareSourceModal,CodeReviewDashboard,PerformanceAnalysisDashboard,CategoryEditModal,DocumentExportModal,MemoryOrphanManager,SessionOrphanManager, …) have unit tests; there is no Storybook visual-regression harness wired for them. - CSS cascade-order risk. No clone pair has a wholesale-identical
<style>block — every pair is a partial overlap (e.g.AddSourceModal/ShareSourceModalshare 127/149 style lines). Splitting common-from-unique CSS into a shared sheet can change equal-specificity rule order → silent visual drift. - Hard requirement was "visual output identical." That cannot be evidenced without rendering, which isn't possible in the sweep's headless context.
The infrastructure to migrate ONTO already exists
src/components/ui/BaseModal.vue(+BaseModal.stories.ts)src/components/base/— BaseButton, BaseCard, BasePanel, BaseTable, BaseBadge, BaseInputsrc/design-system/tokens.ts
So this is a migration + visual-QA campaign, not greenfield.
Recommended approach (safe, incremental)
- Stand up visual-regression first (Storybook stories for the offender components + a snapshot/Chromatic-style diff, or Playwright screenshot tests). No CSS extraction lands without it.
- Extract shared scoped CSS via
<style scoped src="@/design-system/styles/*.css">— Vue applies each component's own scope id to the external sheet, so the result is byte-for-byte equivalent to inlining. Only extract rule-blocks whose selectors are disjoint from each component's remaining rules (disjoint selectors ⇒ order-independent ⇒ provably identical). - Migrate modal chrome onto
BaseModal.vuefor the HTML clones (CategoryEditModal↔DocumentExportModal260L,BulkEditModal↔AdvancedStepConfirmationModal234L), slotting the unique form bodies. - Verify each step against the visual-regression baseline; one PR per component-family (analytics dashboards, knowledge modals, plugin modals, orphan managers).
Top clusters (by duplicated lines)
396L analytics/CodebaseDependenciesPanel.vue <-> analytics/panels/CodebaseChartsSection.vue (css)
321L analytics/CodeReviewDashboard.vue <-> analytics/PerformanceAnalysisDashboard.vue (css)
293L analytics/panels/CodebaseApiEndpointsPanel.vue <-> analytics/panels/CodebaseCrossLanguagePanel.vue (css)
260L knowledge/modals/CategoryEditModal.vue <-> knowledge/modals/DocumentExportModal.vue (html)
245L analytics/AddSourceModal.vue <-> analytics/ShareSourceModal.vue (css)
239L views/MarketplaceView.vue <-> views/PluginsView.vue (css)
234L knowledge/modals/BulkEditModal.vue <-> terminal/AdvancedStepConfirmationModal.vue (html)
182L chat/ChatMessages.vue <-> chat/MessageItem.vue (css)
174L knowledge/MemoryOrphanManager.vue <-> knowledge/SessionOrphanManager.vue (css)
162L analytics/CodeReviewDashboard.vue <-> analytics/PrecommitHookDashboard.vue (css)
The <script>/.ts pairs (≤50L each: FeatureFlagsApiClient↔VisionMultimodalApiClient, llc board views, file-browser views) were also left alone — extracting a shared base for those would couple unrelated domains for marginal gain (duplication is cheaper than a wrong abstraction).
Child issues (decomposed 2026-06-17 — native sub-issues)
Wave 1 — story-covered, verifiable via existing visual-regression harness
- dedup(#9859 Family C): AddSourceModal ↔ ShareSourceModal — 245L shared scoped CSS #10300 — Family C: AddSourceModal ↔ ShareSourceModal (245L css)
- dedup(#9859 Family A): analytics dashboards CSS — CodeReviewDashboard ↔ Performance/Precommit (483L) #10301 — Family A: analytics dashboards CSS (483L)
- dedup(#9859 Family E): MemoryOrphanManager ↔ SessionOrphanManager — 174L shared scoped CSS #10302 — Family E: Memory ↔ Session OrphanManager (174L css)
Wave 2 — add Storybook stories + baselines first
- dedup(#9859 Family B): analytics panels CSS — Codebase{Dependencies,Charts,ApiEndpoints,CrossLanguage} (689L) #10304 — Family B: analytics panels CSS (689L)
- dedup(#9859 Family F): MarketplaceView ↔ PluginsView — 239L shared scoped CSS #10305 — Family F: Marketplace ↔ Plugins views (239L)
- dedup(#9859 Family G): ChatMessages ↔ MessageItem — 182L shared scoped CSS #10306 — Family G: ChatMessages ↔ MessageItem — DONE via @reference (PR dedup(#9859 Family G): extract shared chat CSS via @reference (#10306, retry) #10366)
Wave 3 — HTML → BaseModal migration
- [~] dedup(#9859 Family D): migrate knowledge/terminal modal HTML onto BaseModal.vue (494L) #10307 — Family D: DEFERRED — modals already use BaseModal; residual dup is form-body markup (wrong-abstraction risk, needs visual gate + design judgment)
Each = one PR gated on the visual-regression screenshot suite (zero diff). <script>/.ts clone pairs excluded (wrong-abstraction).
Activity
refactor(dedup): eliminate code duplication via behavior-preserving e…
mrveiss commentedon Jun 18, 2026
Decomposition — task tree (2026-06-17)
Per the umbrella mandate (decompose before code) and this issue's own "one PR per component-family, visual-regression first" guidance. Grouped the 274 pairs / 9,666 lines into families, ordered by verifiability: pairs whose components already have Storybook stories are covered by the existing visual-regression harness (
tests/visual/storybook-stories.spec.tsauto-discovers every story), so their dedup is provable; pairs without stories need stories+baselines first.Harness status
tests/visual/__screenshots__/{chromium-light,chromium-dark}).Wave 1 — story-covered CSS pairs (verifiable now, disjoint-selector extraction)
Wave 2 — need Storybook stories + baselines first, then CSS extraction
Wave 3 — HTML clones → migrate onto
BaseModal.vueNOT doing (per issue):
<script>/.tspairs — extracting a shared base couples unrelated domains for marginal gain (duplication < wrong abstraction).Per-PR protocol (each family = 1 PR)
@/design-system/styles/<family>.css, included via<style scoped src="…">(Vue re-applies each component's scope id ⇒ byte-identical output; disjoint ⇒ order-independent ⇒ provably identical).vue-tsc) + the family's unit tests.Execution gate: these touch production UI and the issue's hard constraint is visual-identical output, which cannot be proven headlessly without the screenshot suite. Recommend confirming the approach before Wave 1 starts landing.
mrveiss commentedon Jun 18, 2026
Child issues filed (per-family campaign tasks)
Each lands as one PR gated on the visual-regression screenshot suite (zero diff). The
<script>/.tsclone pairs are intentionally excluded (wrong-abstraction risk). #9859 stays open as the tracking sub-epic until all 7 land.dedup(#9859 Family C): extract shared scoped CSS from source modals (#…
dedup(#9859 Family C): extract shared scoped CSS from source modals (#…
dedup(#9859 Family E): extract shared scoped CSS from orphan managers (…
dedup(#9859 Family A): extract shared scoped CSS from analytics dashb…
dedup(#9859 Family F): extract shared scoped CSS from marketplace/plu…
dedup(#9859 Family B): extract shared scoped CSS from analytics panels (
dedup(#9859 Family G): extract shared scoped CSS from chat components…
mrveiss commentedon Jun 20, 2026
Campaign complete — closing
Decomposed into 7 per-family child issues; 6 deduped (~1,531 duplicated lines removed across 12 components), 1 deliberately not-extracted.
8ad5f7bcf(−174)53d968ea1(−169)c029c06cf(−401)64bb3cdd0(−151)ff98bb183(−535)38c7aa287(−101, via@reference)Method: extract only byte-identical, no-intra-file-dup scoped-CSS rule blocks (⇒ disjoint ⇒ order-independent ⇒ provably identical) into shared sheets via
<style scoped src>; verify the effective rule-set is unchanged per component. Plain CSS proven by rule-set equivalence; Tailwind@applyblocks (chat) handled via@referencewith the build as the gate (build-error-only failure mode ⇒ green build = identical output).#10307 not done — by design: the 4 modals already use
BaseModal(chrome dedup already in place); the residual jscpd-detected duplication is form-body markup, whose extraction would couple unrelated modals — a wrong abstraction (the campaign's own principle: duplication is cheaper than the wrong abstraction). The<script>/.tsclone pairs were excluded for the same reason.The safely-removable Vue duplication is eliminated. Closing as complete.