chore: make repo fallow-compliant#13284
Conversation
These vi.mock() calls targeted module paths that no longer exist (toastStore, settingStore, firebaseAuthStore, useFirebaseAuth, SearchBox, NodeDragPreview, useRetriggerableAnimation, config/version) after the modules moved or were renamed. Each mock was inert — the system under test imports the real (relocated) module, so the stale-path factory never applied. Removing them is behavior-preserving (all 193 affected tests stay green) and clears fallow's unresolved-import findings. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…selines Configure fallow so `fallow audit` gates only newly introduced dead code, complexity, and duplication while the inherited legacy is captured in per-analysis baselines. Narrow, documented exceptions in .fallowrc.json (each with a why): - entry: CLI/build scripts, Storybook configs, codegen config (roots fallow can't infer) - ignorePatterns: public assets, devtools artifacts, Storybook-only mocks - ignoreDependencies: build-time icon sets, config-loaded lint/style plugins, vite locale aliases, and pnpm-hoisted phantom deps in apps/desktop-ui - ignoreExports: public extension API (litegraph, src/types, scripts/ui), generated OpenAPI types, and dynamically-imported cloud/astro modules - ignoreUnresolvedImports: bundler-resolved static asset + generated sibling Exceptions mirror the existing knip exception ledger where they overlap. Baselines (.fallow-baselines/, relative paths, CI-portable) hold the residual litegraph/store members, intra-lib cycles, duplicate test fixtures, and the marketing file pending page adoption. They are a migration aid — shrink and re-save as the underlying code is cleaned up. Adds `pnpm fallow` and `pnpm fallow:audit` scripts. `fallow audit` passes and `fallow dead-code`/`dupes` are clean against their baselines. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
🎨 Storybook: ✅ Built — View Storybook🎭 Playwright: ✅ 1691 passed, 0 failed · 1 flaky📊 Browser Reports
📦 Bundle Size
⚡ Performance Report
Absolute values
Raw data{
"timestamp": "2026-06-29T18:58:55.633Z",
"gitSha": "8197369ff08d55e88f0f07875441f508155beac7",
"branch": "drjkl/fallow-compliant",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2021.581999999995,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.025999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 342.645,
"heapDeltaBytes": -2278092,
"heapUsedBytes": 56369904,
"domNodes": 18,
"jsHeapTotalBytes": 25952256,
"scriptDurationMs": 13.887,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-idle",
"durationMs": 2040.5809999999747,
"styleRecalcs": 10,
"styleRecalcDurationMs": 8.107000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 343.12199999999996,
"heapDeltaBytes": -2336956,
"heapUsedBytes": 56436196,
"domNodes": 20,
"jsHeapTotalBytes": 26476544,
"scriptDurationMs": 15.130999999999998,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1906.9479999999999,
"styleRecalcs": 76,
"styleRecalcDurationMs": 37.040000000000006,
"layouts": 12,
"layoutDurationMs": 3.4540000000000006,
"taskDurationMs": 813.7869999999999,
"heapDeltaBytes": -6681668,
"heapUsedBytes": 52063724,
"domNodes": 61,
"jsHeapTotalBytes": 26214400,
"scriptDurationMs": 123.179,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1713.0339999999933,
"styleRecalcs": 71,
"styleRecalcDurationMs": 30.712999999999997,
"layouts": 12,
"layoutDurationMs": 3.1690000000000005,
"taskDurationMs": 699.2049999999999,
"heapDeltaBytes": -7029212,
"heapUsedBytes": 51709780,
"domNodes": 53,
"jsHeapTotalBytes": 24903680,
"scriptDurationMs": 115.788,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1711.2720000000081,
"styleRecalcs": 30,
"styleRecalcDurationMs": 14.072000000000001,
"layouts": 6,
"layoutDurationMs": 0.544,
"taskDurationMs": 281.93,
"heapDeltaBytes": 1903752,
"heapUsedBytes": 60621476,
"domNodes": 78,
"jsHeapTotalBytes": 26214400,
"scriptDurationMs": 16.666,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1731.9079999999758,
"styleRecalcs": 31,
"styleRecalcDurationMs": 14.677000000000001,
"layouts": 6,
"layoutDurationMs": 0.5009999999999999,
"taskDurationMs": 287.532,
"heapDeltaBytes": 2646812,
"heapUsedBytes": 63778000,
"domNodes": 76,
"jsHeapTotalBytes": 25690112,
"scriptDurationMs": 17.763000000000005,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 547.4710000000016,
"styleRecalcs": 13,
"styleRecalcDurationMs": 7.9940000000000015,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 328.296,
"heapDeltaBytes": 7767868,
"heapUsedBytes": 66482876,
"domNodes": 22,
"jsHeapTotalBytes": 19660800,
"scriptDurationMs": 55.933,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.663333333333338,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "dom-widget-clipping",
"durationMs": 563.7189999999919,
"styleRecalcs": 12,
"styleRecalcDurationMs": 7.393000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 335.63100000000003,
"heapDeltaBytes": 7067560,
"heapUsedBytes": 65825160,
"domNodes": 20,
"jsHeapTotalBytes": 18612224,
"scriptDurationMs": 60.090999999999994,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-idle",
"durationMs": 2003.232999999966,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.978999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 456.217,
"heapDeltaBytes": 11115556,
"heapUsedBytes": 67977688,
"domNodes": 18,
"jsHeapTotalBytes": 5767168,
"scriptDurationMs": 84.138,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 1992.9139999999848,
"styleRecalcs": 11,
"styleRecalcDurationMs": 9.231,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 473.426,
"heapDeltaBytes": -9652692,
"heapUsedBytes": 61932308,
"domNodes": 22,
"jsHeapTotalBytes": 9842688,
"scriptDurationMs": 83.043,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2114.178000000038,
"styleRecalcs": 70,
"styleRecalcDurationMs": 18.668999999999997,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1117.43,
"heapDeltaBytes": 15462232,
"heapUsedBytes": 88493512,
"domNodes": 18,
"jsHeapTotalBytes": 17620992,
"scriptDurationMs": 482.32199999999995,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-pan",
"durationMs": 2086.911999999984,
"styleRecalcs": 69,
"styleRecalcDurationMs": 16.964,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 980.5649999999999,
"heapDeltaBytes": 10644176,
"heapUsedBytes": 83691852,
"domNodes": 16,
"jsHeapTotalBytes": 11067392,
"scriptDurationMs": 370.699,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-zoom",
"durationMs": 3088.216999999986,
"styleRecalcs": 65,
"styleRecalcDurationMs": 18.072999999999997,
"layouts": 60,
"layoutDurationMs": 7.061000000000001,
"taskDurationMs": 1226.7869999999998,
"heapDeltaBytes": 14911688,
"heapUsedBytes": 77306696,
"domNodes": 12,
"jsHeapTotalBytes": 7602176,
"scriptDurationMs": 471.679,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-zoom",
"durationMs": 3105.2709999999593,
"styleRecalcs": 66,
"styleRecalcDurationMs": 19.326,
"layouts": 60,
"layoutDurationMs": 7.186000000000001,
"taskDurationMs": 1230.162,
"heapDeltaBytes": 14283392,
"heapUsedBytes": 69422236,
"domNodes": 14,
"jsHeapTotalBytes": 7864320,
"scriptDurationMs": 484.96399999999994,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "minimap-idle",
"durationMs": 2012.233999999978,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.906,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 465.685,
"heapDeltaBytes": -9344996,
"heapUsedBytes": 63881348,
"domNodes": 18,
"jsHeapTotalBytes": 7745536,
"scriptDurationMs": 83.748,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2014.1459999999825,
"styleRecalcs": 10,
"styleRecalcDurationMs": 8.541,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 470.266,
"heapDeltaBytes": -9341588,
"heapUsedBytes": 64209512,
"domNodes": 20,
"jsHeapTotalBytes": 8269824,
"scriptDurationMs": 87.35100000000001,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 512.5000000000455,
"styleRecalcs": 46,
"styleRecalcDurationMs": 10.229999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 344.422,
"heapDeltaBytes": 7439388,
"heapUsedBytes": 66277500,
"domNodes": 18,
"jsHeapTotalBytes": 18087936,
"scriptDurationMs": 119.175,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666682,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 546.6250000000059,
"styleRecalcs": 48,
"styleRecalcDurationMs": 11.918000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 343.04200000000003,
"heapDeltaBytes": 10220796,
"heapUsedBytes": 61774700,
"domNodes": 22,
"jsHeapTotalBytes": 15466496,
"scriptDurationMs": 116.446,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "subgraph-idle",
"durationMs": 2008.722000000006,
"styleRecalcs": 11,
"styleRecalcDurationMs": 8.375,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 341.061,
"heapDeltaBytes": -2245628,
"heapUsedBytes": 56603900,
"domNodes": 22,
"jsHeapTotalBytes": 25952256,
"scriptDurationMs": 12.639999999999999,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-idle",
"durationMs": 2011.8969999999763,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.661,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 371.829,
"heapDeltaBytes": -8864184,
"heapUsedBytes": 45110324,
"domNodes": -320,
"jsHeapTotalBytes": 15368192,
"scriptDurationMs": 13.288000000000002,
"eventListeners": -199,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1674.2189999999937,
"styleRecalcs": 75,
"styleRecalcDurationMs": 33.812999999999995,
"layouts": 16,
"layoutDurationMs": 3.9230000000000005,
"taskDurationMs": 621.659,
"heapDeltaBytes": -10510580,
"heapUsedBytes": 48297296,
"domNodes": 61,
"jsHeapTotalBytes": 25952256,
"scriptDurationMs": 85.821,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1674.837000000025,
"styleRecalcs": 75,
"styleRecalcDurationMs": 30.881,
"layouts": 16,
"layoutDurationMs": 3.5780000000000003,
"taskDurationMs": 611.915,
"heapDeltaBytes": -10493404,
"heapUsedBytes": 48352804,
"domNodes": 61,
"jsHeapTotalBytes": 27000832,
"scriptDurationMs": 82.34,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-transition-enter",
"durationMs": 932.298000000003,
"styleRecalcs": 17,
"styleRecalcDurationMs": 27.029000000000003,
"layouts": 4,
"layoutDurationMs": 13.847999999999999,
"taskDurationMs": 708.3,
"heapDeltaBytes": 4502060,
"heapUsedBytes": 80894660,
"domNodes": 13833,
"jsHeapTotalBytes": 17825792,
"scriptDurationMs": 25.782,
"eventListeners": 2531,
"totalBlockingTimeMs": 163,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "viewport-pan-sweep",
"durationMs": 8104.649999999992,
"styleRecalcs": 250,
"styleRecalcDurationMs": 52.232,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3385.528,
"heapDeltaBytes": -4406308,
"heapUsedBytes": 67185668,
"domNodes": 20,
"jsHeapTotalBytes": 16310272,
"scriptDurationMs": 1173.926,
"eventListeners": 20,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8090.724000000023,
"styleRecalcs": 250,
"styleRecalcDurationMs": 52.009,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3475.872,
"heapDeltaBytes": -3666648,
"heapUsedBytes": 67978080,
"domNodes": 22,
"jsHeapTotalBytes": 17096704,
"scriptDurationMs": 1185.5439999999999,
"eventListeners": 20,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 11477.888000000006,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 11456.916,
"heapDeltaBytes": -13656376,
"heapUsedBytes": 172258348,
"domNodes": -3302,
"jsHeapTotalBytes": 22253568,
"scriptDurationMs": 478.69100000000003,
"eventListeners": -16376,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.220000000000073,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 11291.317999999934,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 11274.232999999998,
"heapDeltaBytes": -25577324,
"heapUsedBytes": 171933056,
"domNodes": -3306,
"jsHeapTotalBytes": 22253568,
"scriptDurationMs": 488.64399999999995,
"eventListeners": -16378,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.219999999999953,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-pan",
"durationMs": 14112.89099999999,
"styleRecalcs": 65,
"styleRecalcDurationMs": 15.799000000000007,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 14091.937999999998,
"heapDeltaBytes": -24475492,
"heapUsedBytes": 168564560,
"domNodes": -3302,
"jsHeapTotalBytes": 19894272,
"scriptDurationMs": 760.224,
"eventListeners": -16372,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.223333333333358,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "vue-large-graph-pan",
"durationMs": 13979.328000000009,
"styleRecalcs": 65,
"styleRecalcDurationMs": 15.647999999999996,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 13955.687000000002,
"heapDeltaBytes": -17351464,
"heapUsedBytes": 175840572,
"domNodes": -3302,
"jsHeapTotalBytes": 16924672,
"scriptDurationMs": 775.2900000000001,
"eventListeners": -16369,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.219999999999953,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "workflow-execution",
"durationMs": 446.15900000002284,
"styleRecalcs": 17,
"styleRecalcDurationMs": 22.25,
"layouts": 4,
"layoutDurationMs": 1.1389999999999998,
"taskDurationMs": 107.93700000000001,
"heapDeltaBytes": 5370172,
"heapUsedBytes": 65265844,
"domNodes": 168,
"jsHeapTotalBytes": 3145728,
"scriptDurationMs": 15.508,
"eventListeners": 69,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666682,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "workflow-execution",
"durationMs": 450.54399999992256,
"styleRecalcs": 17,
"styleRecalcDurationMs": 18.936999999999998,
"layouts": 4,
"layoutDurationMs": 0.9869999999999998,
"taskDurationMs": 98.121,
"heapDeltaBytes": 5146160,
"heapUsedBytes": 60936220,
"domNodes": 157,
"jsHeapTotalBytes": 262144,
"scriptDurationMs": 14.427999999999997,
"eventListeners": 71,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66666666666665,
"p95FrameDurationMs": 16.800000000000182
}
]
} |
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## drjkl/cleanup-duplicates #13284 +/- ##
=========================================================
Coverage 78.23% 78.23%
=========================================================
Files 1632 1632
Lines 114034 114034
Branches 38343 38343
=========================================================
Hits 89211 89211
Misses 23918 23918
Partials 905 905
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
Move .fallowrc.json to .fallowrc.jsonc (fallow reads JSONC, keeping the per-entry rationale comments) and document the adoption in ADR 0011. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
a8c94f3 to
836e156
Compare
Makes the repo fallow-compliant:
fallow auditgates only newly-introduced dead code, complexity, and duplication, while inherited legacy is captured in per-analysis baselines. Addspnpm fallow/pnpm fallow:audit.Fixed in code
Removed 11 stale
vi.mock()blocks across 10 test files. Each targeted a module path that no longer exists after the module moved/renamed (toastStore,settingStore,firebaseAuthStore,useFirebaseAuth,SearchBox.vue,NodeDragPreview.vue,useRetriggerableAnimation,config/version). The mocks were inert — each SUT imports the relocated module, so the stale-path factory never applied. Behavior-preserving (193 affected tests stay green); clears fallow's unresolved-import findings.Exceptions (
.fallowrc.json, documented inline)entry: CLI/build scripts, Storybook configs, codegen config — roots fallow can't infer.ignorePatterns:public/**, devtools artifacts, Storybook-only mocks.ignoreDependencies: build-time icon sets, config-loaded lint/style plugins, vite locale aliases, and pnpm-hoisted phantom deps inapps/desktop-ui.ignoreExports: public extension API (litegraph,src/types,scripts/ui), generated OpenAPI types, dynamically-imported cloud/astro modules.ignoreUnresolvedImports: bundler-resolved static asset + generated sibling.Exceptions mirror the existing knip exception ledger where they overlap.
Baselines
.fallow/baselines/{dead-code,health,dupes}.json(relative paths, CI-portable; the local.fallow/cache stays gitignored) hold the residual litegraph/store members, intra-lib cycles, duplicate test fixtures, and the marketing file pending page adoption. A migration aid — shrink and re-save as the code is cleaned up. No complexity threshold was lowered and no rule disabled; per fallow's guidance, the authoritative health gate isfallow audit --gate new-only, not the advisoryfallow health.Result
Follow-up (out of scope): declare the 9 root-hoisted npm packages in
apps/desktop-ui/package.json, then drop theirignoreDependenciesexceptions.Stacked PR (top of series). Base:
drjkl/cleanup-duplicates.