Skip to content

Vue frontend duplication campaign — 9,666 dup lines across 274 component pairs (CSS/HTML/TS) #9859

Closed
7 of 7 issues completed

Description

@mrveiss

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/ShareSourceModal share 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, BaseInput
  • src/design-system/tokens.ts

So this is a migration + visual-QA campaign, not greenfield.

Recommended approach (safe, incremental)

  1. 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.
  2. 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).
  3. Migrate modal chrome onto BaseModal.vue for the HTML clones (CategoryEditModal↔DocumentExportModal 260L, BulkEditModal↔AdvancedStepConfirmationModal 234L), slotting the unique form bodies.
  4. 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

Wave 2 — add Storybook stories + baselines first

Wave 3 — HTML → BaseModal migration

Each = one PR gated on the visual-regression screenshot suite (zero diff). <script>/.ts clone pairs excluded (wrong-abstraction).

Activity

  1. mrveiss commented on Jun 18, 2026

    @mrveiss
    OwnerAuthor

    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.ts auto-discovers every story), so their dedup is provable; pairs without stories need stories+baselines first.

    Harness status

    • ✅ Visual-regression harness EXISTS (Playwright + Storybook, baselines in tests/visual/__screenshots__/{chromium-light,chromium-dark}).
    • Story coverage of offenders: have stories → AddSourceModal, ShareSourceModal, CodeReviewDashboard, PerformanceAnalysisDashboard, MemoryOrphanManager, SessionOrphanManager. No story → CategoryEditModal, DocumentExportModal, BulkEditModal, MarketplaceView, PluginsView.

    Wave 1 — story-covered CSS pairs (verifiable now, disjoint-selector extraction)

    • Family C — source modals: AddSourceModal ↔ ShareSourceModal (245L css)
    • Family A — analytics dashboards: CodeReviewDashboard ↔ PerformanceAnalysisDashboard (321L) + ↔ PrecommitHookDashboard (162L)
    • Family E — orphan managers: MemoryOrphanManager ↔ SessionOrphanManager (174L css)

    Wave 2 — need Storybook stories + baselines first, then CSS extraction

    • Family B — analytics panels: CodebaseDependenciesPanel ↔ CodebaseChartsSection (396L) + CodebaseApiEndpointsPanel ↔ CodebaseCrossLanguagePanel (293L) — add stories first
    • Family F — marketplace/plugins views: MarketplaceView ↔ PluginsView (239L) — add stories first
    • Family G — chat: ChatMessages ↔ MessageItem (182L) — add stories first

    Wave 3 — HTML clones → migrate onto BaseModal.vue

    • Family D — knowledge/terminal modals: CategoryEditModal ↔ DocumentExportModal (260L html), BulkEditModal ↔ AdvancedStepConfirmationModal (234L html) — slot unique form bodies into BaseModal; add stories to cover.

    NOT doing (per issue): <script>/.ts pairs — extracting a shared base couples unrelated domains for marginal gain (duplication < wrong abstraction).

    Per-PR protocol (each family = 1 PR)

    1. Extract ONLY byte-identical rule-blocks whose selectors are disjoint from each component's remaining rules → shared @/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).
    2. Run the visual-regression suite; zero screenshot diff is the merge gate. No CSS lands without it.
    3. Type-check (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.

  2. mrveiss commented on Jun 18, 2026

    @mrveiss
    OwnerAuthor

    Child issues filed (per-family campaign tasks)

    Wave 1 — story-covered, verifiable now (disjoint-identical CSS extraction)
    - [ ] #10300  Family C — AddSourceModal ↔ ShareSourceModal (245L css)
    - [ ] #10301  Family A — analytics dashboards CSS (483L)
    - [ ] #10302  Family E — Memory ↔ Session OrphanManager (174L css)
    
    Wave 2 — add Storybook stories + baselines first, then CSS extraction
    - [ ] #10304  Family B — analytics panels CSS (689L)
    - [ ] #10305  Family F — Marketplace ↔ Plugins views (239L)
    - [ ] #10306  Family G — ChatMessages ↔ MessageItem (182L)
    
    Wave 3 — HTML clones → migrate onto BaseModal.vue
    - [ ] #10307  Family D — knowledge/terminal modals (494L)
    

    Each lands as one PR gated on the visual-regression screenshot suite (zero diff). The <script>/.ts clone pairs are intentionally excluded (wrong-abstraction risk). #9859 stays open as the tracking sub-epic until all 7 land.

  3. added 7 commits that reference this issue on Jun 18, 2026
  4. mrveiss commented on Jun 20, 2026

    @mrveiss
    OwnerAuthor

    Campaign complete — closing

    Decomposed into 7 per-family child issues; 6 deduped (~1,531 duplicated lines removed across 12 components), 1 deliberately not-extracted.

    Child Family Result
    #10300 source modals merged 8ad5f7bcf (−174)
    #10302 orphan managers merged 53d968ea1 (−169)
    #10301 analytics dashboards (3-way) merged c029c06cf (−401)
    #10305 marketplace/plugins views merged 64bb3cdd0 (−151)
    #10304 analytics panels (2 pairs) merged ff98bb183 (−535)
    #10306 chat components merged 38c7aa287 (−101, via @reference)
    #10307 knowledge/terminal modals not extracted (decision)

    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 @apply blocks (chat) handled via @reference with 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>/.ts clone pairs were excluded for the same reason.

    The safely-removable Vue duplication is eliminated. Closing as complete.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions