Back to writing

Writing / AI Engineering

Can copy-paste beat code generation?

Copying vetted source worked best overall. It still failed where components were connected, and every dashboard broke on mobile.

DateAug 13, 2026
Read time26 min read
TagsAI Agents · Evaluation · Frontend Engineering
Can copy-paste beat code generation?

Imagine asking an AI coding agent to build an admin screen. The screen needs familiar parts: buttons, form fields, menus, tables and dialogs. Good versions of those parts already exist in component libraries.

The practical question is simple: should the agent write those parts again, or copy the tested source and spend its effort on the product-specific work?

Executive summary

Copying vetted source worked best overall

Arm C built successfully in every matched run, wrote the least custom application code and had the fewest accessibility findings.

Generating the interface directly came second

Arm A also built reliably and often looked more polished, but it repeated more control logic and missed more interaction edge cases.

Reading the documentation and rewriting the components worked worst

Arm B failed to build in five of 16 runs and cost more than either alternative.

Copying was not a complete solution

Correct components still lost behaviour when the application connected them incorrectly, and every dashboard broke at a phone-sized viewport.

These findings come from 88 runs across eight interface tasks, followed by a manual production pass over 48 matched high-reasoning outputs. The experiment used one inexpensive model, so the ranking is evidence from this study rather than a universal law.

Three ways to build the same interface

A component is a reusable interface part, such as a button or dialog. An API is its rulebook: the names, options and behaviours that other code must use correctly.

The experiment compared three ways an agent could work:

Arm A — generate everything

The model writes the interface directly, mostly with ordinary HTML elements and styling.

Arm B — retrieve, then generate

The model reads the real component documentation, but rewrites the component source itself.

Arm C — copy, then compose

A tool copies the vetted component files exactly; the model writes only the application code that connects them.

I use materialisation to mean that last copying step: the component becomes a real project file without passing through model output. I use primitive for a foundational component such as a button, input, dialog or menu.

Figure 1

Three source paths create two useful comparisons

The model, task prompt and component-free starting template stayed the same.

Arm AGenerate everything

Model weights → all interface source

Arm BRetrieve, then generate

Registry API → model recreates every line

Arm CCopy, then compose

Registry source → CLI copies components; model writes the glue

A → BWhat retrieval changes
B → CWhat copying the source changes
A to B isolates the effect of retrieving the real API. B to C isolates materialisation: whether the model recreates the component source or a CLI copies it intact.

The first version of this study compared only arms A and C. That made the result look like a tie: arm A had no external component rulebook to break, while arm C received the correct files intact. Neither approach asked the model to recreate a known component API.

Arm B supplied the missing test. It gave the model the correct documentation and then asked it to rewrite the component. Five of 16 projects failed to build. The earlier study had not disproved the predicted failure; it had omitted the condition that could produce it.

The two useful comparisons are therefore different. A versus B asks whether reading the correct API helps. B versus C asks whether copying the implementation is safer than recreating it. Only the second comparison isolates the benefit of materialisation.

Every run used DeepSeek-V4-Flash, the same task, and the same empty starting project. Both registry arms could search and read the component documentation. Only arm C could install the source directly.

A successful build is not the same as a working product

The compiler is the software that translates and checks source code before it ships. It catches broken imports and invalid types, but it cannot tell whether a menu ignores Escape or whether keyboard focus disappears after a dialog closes.

The experiment therefore checked each result in four ways:

Build check

Can the project compile and ship?

Browser test

Do its menus, forms and dialogs behave correctly when someone uses them?

Accessibility scan

Does an automated check find serious barriers for disabled users?

Composition audit

Did the application connect reusable components in the way needed to preserve their built-in behaviour?

That last boundary is the composition seam: the point where a correct reusable component meets product-specific application code. A dialog can contain the right focus behaviour and still lose it if the application fails to identify which button opened the dialog.

Evidence table

What each scoring layer can see

A green result in one layer does not imply a shippable or usable product.

What each scoring layer can see. A green result in one layer does not imply a shippable or usable product.
LayerRuns flaggedCatchesCannot see
Compiler + production build5Broken imports, invented properties and wrong API shapesAnything that compiles
Browser behaviour7Dismissal, focus and state across routesType errors when the development server still opens
Accessibility scan9Serious and critical rule violationsBroken behaviour outside the rules
Composition audit10Useful primitive behaviour discarded by the connecting codeInterfaces without a Radix-backed primitive

The disagreements between these layers matter. One arm B command-palette run failed the compiler with two errors and still passed all five browser checks. The development server could display it, but a production build could not ship it. A browser-only score would have called that run perfect.

The scorers were tested before the experiment. A hand-built reference had to pass cleanly, while a deliberately broken version had to fail only the checks tied to its injected defects. On the 15-check dashboard, the reference scored 15 of 15 and the negative control scored 12 of 15, missing exactly the three intended checks.

Missing evidence was also kept separate from a clean result. If an accessibility bundle was absent, the run reported “not run”, not zero violations. One of the 88 runs was not runtime-scored, so runtime conclusions use the 87 runs with browser evidence.

Where the failures appeared

Most runs worked. The failures were concentrated enough to show in one ledger rather than being scattered across everything the model wrote.

Figure 2

The complete defect ledger

88 runs across 8 tasks. Counts are defects or failed checks, not affected runs.

TaskArm A · GenerateArm B · RetrieveArm C · Materialise
Login formNo defect foundNot runNo defect found
Users table1 contrast · 4 behaviour2 build · 1 contrast2 contrast
Kanban boardNo defect foundNot runNo defect found
Command paletteNo defect found2 build · 1 behaviour · 1 seamNo defect found
Variant matrixNo defect foundNot runNo defect found
Form errorsNo defect foundNo defect foundNo defect found
Compound state11 contrast1 build · 6 contrast · 4 seam4 seam
Dashboard1 behaviourNo defect found1 behaviour · 1 seam
The predicted build failures appear only in arm B. Contrast problems cluster in two tasks. The composition seam is most visible in the compound-state task.

Five runs failed to build, all in arm B. Seven runs failed at least one automated browser behaviour. Nine runs had serious or critical accessibility findings; every one was a colour-contrast issue. The composition audit found ten cases where application code discarded behaviour that a reusable component already provided.

Arm A’s browser failures were mainly the users-table menu at lower reasoning effort, plus one dashboard focus failure. Arm C had one dashboard focus failure. Arm B added one command-palette focus failure even when its project could otherwise run.

Rewriting known components broke five builds

The original prediction was specific: a model that rewrites an existing library component will sometimes invent a reference or use the library incorrectly. Arm B created exactly that condition, and five of its 16 projects failed to build.

Evidence table

Every build failure in the programme

All five came from arm B: the path that knew the real API and tried to reproduce its source.

Every build failure in the programme. All five came from arm B: the path that knew the real API and tried to reproduce its source.
RunCompiler codesWhat went wrong
c1_command_palette · seed s2TS2769Passed an onClick property to a component that does not accept it.
c1_command_palette · seed s3TS2604 / TS2786Used Slot as a component when the real component is Slot.Root.
c4_compound_state · seed s4TS2604 / TS2786Repeated the same Slot namespace mistake on another task.
t3_users_table · seed s1TS2307 ×2Imported two module paths that do not exist.
t3_users_table · seed s4TS2307 ×2Imported two different module paths that do not exist; this previously unscored run was recovered and rebuilt during the manual audit.

Arm A avoided these particular mistakes by using ordinary interface elements instead of the library. Arm C avoided them by copying the real component files. Only arm B knew what the component should be and still tried to recreate it from model output.

The fifth failure was not a new run. The original snapshot had no build result for one users-table output. Recovering its final source from the trace and rebuilding it revealed two library import paths that did not exist, the same failure that had already broken another arm B table. The earlier count of four therefore understated the production result by one.

The automated error label also missed two of the five failures. It counted references to things that did not exist, but not incorrect uses of names that did exist. The compiler codes and exact causes remain in the evidence table above.

Arm B could also drift from the documented component without breaking the build. On the task combining tabs, a table and a dialog, three of four arm B runs either omitted or misused a documented button behaviour.

The correction to the earlier article is direct: “zero hallucination errors across 70 runs” did not show that the mechanism was absent. It showed that the experiment had no condition in which it could appear.

Reading the documentation was not enough

Knowing the correct API was not enough. On every task completed by all three arms, arm B cost roughly twice as much as arm A, around three times as much as arm C, wrote the most source and was the only arm that failed to build.

Figure 3

Cost and model-authored code

Mean per completed high-effort run on the five tasks tested in all three arms.

Cost per run · USD

Users table

A$0.0054
B$0.0107
C$0.0039

Command palette

A$0.0040
B$0.0089
C$0.0036

Form errors

A$0.0045
B$0.0088
C$0.0043

Compound state

A$0.0034
B$0.0116
C$0.0033

Dashboard

A$0.0123
B$0.0234
C$0.0129

Lines written by the model

Users table

A376
B673
C255

Command palette

A244
B371
C114

Form errors

A292
B491
C261

Compound state

A408
B836
C259

Dashboard

A1117
B1769
C887
Arm B is the most expensive path and writes the most source on every shared task. Arm C returns spend to roughly arm A’s level while keeping the component source out of model output.

Moving from B to C reversed both effects. Spend returned to roughly arm A’s level and the build failures disappeared. The useful step was not giving the model more information. It was removing the need to rewrite source that already existed.

The task combining tabs, a table and a dialog makes the difference clear. Arm B wrote 836 lines. Arm C wrote 258 lines and copied the existing components. Reading the API made the model do more work; copying removed that unnecessary work from model output.

This does not mean tools or retrieval are intrinsically expensive. It means this particular workflow asked the model to study an API and then rebuild an implementation that already existed. The extra turns and source generation bought no measured improvement.

Copying reduced AI-written code, not repository size

“Copy-paste writes less code” is only true if code means lines emitted by the model.

Arm C wrote fewer application lines, but its copied component files still became part of the shipped project. When those files are counted, arm C carried between 1.46 and 2.26 times arm A’s total source on the five tasks shared by all three arms.

Figure 4

Lines of code that actually ship

Solid segments were written by the model. Hatched segments were copied verbatim from the registry.

Users table

A376
B673
C736

Command palette

A244
B371
C357

Form errors

A292
B491
C494

Compound state

A408
B836
C922

Dashboard

A1117
B1769
C1841
Arm C authors the fewest lines and ships the most. Arm A looks lean because it usually has no reusable component layer at all.

On the dashboard, arm A wrote six buttons directly inside the page. Arm C copied one reusable Button component containing its visual variants, keyboard-focus styling, disabled states and error states. Arm A did not create a smaller version of that component; it created no reusable button layer at all.

Arm B shows what that layer costs when a model writes it. Its totals landed close to arm C: 836 versus 922 lines on compound state, and 673 versus 736 on the users table. The layer is similar in size whoever produces it. The important difference is that five of 16 arm B runs did not compile.

The source shape changed too. Arm A usually concentrated work in one file and repeated raw elements and styling strings. Arms B and C had a component layer and far less repetition.

Evidence table

Shape of the model-authored source

All completed runs. The A-to-B/C comparison is confounded by arm A’s instruction to build the interface directly.

Shape of the model-authored source. All completed runs. The A-to-B/C comparison is confounded by arm A’s instruction to build the interface directly.
ArmRunsFiles / runRuns with components folderRaw buttons + inputsRepeated class strings / 100 lines
Arm A · Generate361.45 of 364.81.63
Arm B · Retrieve166.516 of 161.80.18
Arm C · Materialise361.21 of 360.10.12

This comparison has an important caveat. Arm A was told to “build the UI directly”, while the other arms were told where component files belong. The prompt itself nudged arm A towards page-specific code, so the difference reflects both architecture and instruction wording.

The B-to-C contrast survives this problem. Both arms were told to create the same layer and differed only in who wrote it. Their structural shape was similar; their build correctness was not.

Copied components bring behaviour with them

Copied source brought two useful things that the original hypothesis barely mentioned: colour choices and behaviour that the prompt never had to request.

The automated accessibility result was narrower than accessibility as a whole. Every serious or critical finding concerned colour contrast. The scan found no missing label or other structural barrier.

Evidence table

Serious and critical accessibility findings

High-effort runs. Every finding was colour contrast; the scan found no structural accessibility failure.

Serious and critical accessibility findings. High-effort runs. Every finding was colour contrast; the scan found no structural accessibility failure.
TaskArm AArm BArm C
Compound state1160
Users table112
All other tasks000
Total1272

The total favours arm C, but one task explains most of the difference. The tabs-and-dialog task produced 11 contrast findings in arm A, six in arm B and none in arm C. On the users table, arm C had two findings while both other arms had one. Copying did not eliminate contrast problems; it carried a better colour palette on one influential task.

The behaviour result is sharper. The users-table prompt never asked for its row menu to close on Escape. Arm A had to remember to add that behaviour. Arm C inherited it from the copied dropdown component.

Figure 5

Escape-to-close when the prompt never asked for it

Users-table task, split by reasoning effort. The test opens a row menu, presses Escape and checks that it closes.

No extra reasoning

Arm A
0 of 3 pass
Arm C
3 of 3 pass

High reasoning effort

Arm A
3 of 4 pass
Arm C
4 of 4 pass
Arm A’s result depends on how much reasoning it is allowed. Arm C inherits the behaviour from the copied primitive at both effort levels.

Without extra reasoning, arm A failed all three runs while arm C passed all three. At high reasoning effort, arm A passed three of four and arm C passed all four. Arm A’s correctness depended on its reasoning budget. Arm C’s did not; the behaviour arrived in the file.

This is the clearest benefit of copying in the experiment. Mature source can carry behaviour that nobody remembered to request.

Correct components can still be connected incorrectly

Copied behaviour only helps when the application connects it correctly.

The copied dialog knows how to return keyboard focus to the button that opened it. But the application still has to identify that button as the opener. When it does not, the behaviour remains in the component file but never reaches the user.

The composition audit looked for that broken connection wherever the same dialog library appeared, whether arm C had copied it or arm B had recreated it. Looking only at copied files would have missed four of the ten findings.

Evidence table

Orphaned triggers in Radix-backed interfaces

46 of 88 runs were eligible for this audit.

Orphaned triggers in Radix-backed interfaces. 46 of 88 runs were eligible for this audit.
TaskEligible runsFlaggedWhere the finding appeared
Compound state884 arm B + 4 arm C · every eligible run
Dashboard21Arm C; arm B connected the trigger
Command palette71Arm B; one arm C run repaired focus manually
Other five tasks290No finding
Total4610 · 21.7%Arm A had no eligible Radix-backed interface

Ten of 46 eligible runs failed to connect the opening button correctly, or 21.7%. Eight came from the task combining tabs, a table and a dialog: every arm B and arm C run. The defect was not peculiar to copying. It appeared whenever the interface made the dialog awkward to connect.

In that task, the dialog lived in one part of the page while its opening button lived inside a tab. Connecting them properly required rearranging the page structure. Every eligible run took the easier shortcut and controlled the dialog separately.

I tested this explanation with one controlled change. I connected arm C’s existing dashboard button through the dialog’s intended opening mechanism and changed nothing else. The browser score moved from 14 of 15 to 15 of 15, and focus returned to the button. The same structural repair fixed the tabs-and-dialog task.

The compiler, production build and accessibility scan could not see this defect. The application built, opened without an error and reported no serious accessibility violation. More documentation was not an obvious fix either: the model had read the relevant dialog guidance and used the equivalent menu connection correctly elsewhere.

No component library can supply all of the product-specific code that connects its parts.

What the three approaches looked like

The compound-state task is where the argument becomes visible. It combines tabs, a filterable table and a dialog that writes into the list behind it. All three arms passed the five specified browser checks. All three lost focus to the document after the dialog closed: arm A because it never implemented restoration, arms B and C because they had the underlying behaviour and routed around it.

Arm A compound-state interface, generated directly with native elements and local styling.
Arm A compound-state interface, generated directly with native elements and local styling.
Arm B compound-state interface, built from components the model recreated after reading the API.
Arm B compound-state interface, built from components the model recreated after reading the API.
Arm C compound-state interface, composed from copied registry components.
Arm C compound-state interface, composed from copied registry components.

The three results converge visually despite taking different source paths. Arm A uses a native selector and more local presentational decisions. Arms B and C share the structure of a component system.

Arm A create-task dialog. It opens and closes, but does not restore focus.
Arm A create-task dialog. It opens and closes, but does not restore focus.
Arm B create-task dialog. Its recreated component can return focus, but the application bypasses that behaviour.
Arm B create-task dialog. Its recreated component can return focus, but the application bypasses that behaviour.
Arm C create-task dialog. The copied component can return focus, but the application does not connect its opening button.
Arm C create-task dialog. The copied component can return focus, but the application does not connect its opening button.

The original arm A and arm C compound-state workspaces had been pruned. These captures come from workspaces rebuilt from durable experiment artefacts: generated files replayed from traces and copied files restored from the pinned registry snapshot. They are faithful reconstructions, not byte-identical originals.

At application scale, the same convergence appears in a four-route operations dashboard. Arm A adds pill badges, chart gridlines and stronger local visual decisions. Arm C inherits structural details such as a close button that the prompt never requested. Arm B builds a similar layer itself at greater cost.

Arm A dashboard overview, with KPI cards, gridlined charts and recent events.
Arm A dashboard overview, with KPI cards, gridlined charts and recent events.
Arm B dashboard overview, using components recreated after API retrieval.
Arm B dashboard overview, using components recreated after API retrieval.
Arm C dashboard overview, using copied components and a visually quieter chart treatment.
Arm C dashboard overview, using copied components and a visually quieter chart treatment.

Copied components supplied structure. The model still made the presentation choices. Neither result was uniformly better, and the experiment did not score visual quality.

Arm A services table with its generated row-action menu open.
Arm A services table with its generated row-action menu open.
Arm B services table with a model-written, library-backed row menu open.
Arm B services table with a model-written, library-backed row menu open.
Arm C services table with its materialised dropdown menu open.
Arm C services table with its materialised dropdown menu open.

All three dashboard menus opened and closed on Escape. The earlier users-table gap did not become a universal law. The model could remember dismissal in a larger task; the copied component carried it without depending on that recall.

Manual browser testing made the ranking clearer

The automated checks answer narrow questions consistently. They do not tell you what it feels like to put every result through a release pass. I therefore reconstructed and browser-tested all 48 matched high-reasoning outputs: 16 per arm across command palettes, forms, users tables, compound workspaces and dashboards. I also checked one representative result from every arm and task at a 390-pixel viewport.

This was follow-up quality assurance, not another experimental arm. It preserved the original tasks and outputs, then tested production builds, full workflows, focus, empty states, persistence and responsive behaviour. The line counts below use clean final source, exclude the unchanged project template and count copied files as shipped code even when the model did not author them.

Evidence table

What the manual production pass changed

Forty-eight reconstructed high-effort outputs: 16 per arm across the five shared tasks, plus one representative 390px check for every arm and task.

What the manual production pass changed. Forty-eight reconstructed high-effort outputs: 16 per arm across the five shared tasks, plus one representative 390px check for every arm and task.
MeasureArm A · generateArm B · retrieveArm C · copy
Production builds16 of 1611 of 1616 of 16
Application / glue lines per run358239237
Primitive lines per run0412 · model-written450 · copied
Total shipped lines per run358652688
Dashboard at 390pxNot usableNot usableNot usable
Practical rankingSecondThirdFirst

The ranking is arm C first, arm A second and arm B third. Arm C built every output, kept application logic separate from reusable components and recorded the lowest accessibility defect count. Arm A also built every output and was often the most visually polished, but its custom controls produced inconsistent edge cases. Arm B hand-wrote an entire reusable layer and failed to build in five runs, including two users tables that could not render because their generated component files imported paths that did not exist.

The individual tasks add useful context:

Command palettes

All 12 opened, kept keyboard focus inside, filtered results and closed with Escape. One arm B palette did not return focus to its opening button.

Forms

All nine showed errors, described them correctly, focused the first invalid field and submitted. This task barely distinguished the approaches.

Users tables

Arms A and C completed sorting, search, paging, menus and deletion. One arm A menu did not close with Escape. Two arm B tables could not render because their generated component files imported paths that did not exist.

Tabbed workspaces

All 12 preserved filters, new tasks, project names and state across tabs. Every dialog still returned focus to the page body instead of its opening button.

The dashboards expose a shared failure that the desktop screenshots hide. All three completed the requested filters, menus, incident flow, routing and settings persistence. At 390 pixels, all three kept a 256-pixel sidebar, squeezed the main content to roughly 119 pixels and forced horizontal scrolling. Forms and command palettes fitted; wide tables were mixed, with arm B and arm C containing them inside scroll regions while one representative arm A table widened the whole page. Copying a component layer improved consistency, but it did not supply an application-level responsive strategy.

The source totals sharpen the wording around code volume. Arm A averaged 358 application lines. Arm C averaged 237 application lines plus 450 copied component lines, or 688 shipped lines in total. Arm A therefore wrote about 121 additional custom application lines per run compared with arm C, but arm C still shipped the larger repository. The defensible claim is that copy-paste reduces model-authored custom code, not total code.

The experiment also exposed measurement problems

The test harness—the software that ran and scored each project—can create misleading results of its own. Six defects had already distorted a reported number before they were found and fixed.

Evidence table

Six defects found in the measuring apparatus

Each had already distorted a reported number before it was corrected.

Six defects found in the measuring apparatus. Each had already distorted a reported number before it was corrected.
DefectWhat it distorted
Reasoning echo counted as re-sent contextMade 23.9% of input tokens look wasteful even though the endpoint resolved that reasoning server-side at no charge.
The token guard counted cheap cache hitsStopped a tool-heavy arm C run at 627,000 tokens after only $0.016 of spend.
Task time-outs were parsed but not usedSilently ignored the longer time budgets declared by two tasks.
A forced rerun appended to an old traceCould restore files from a previous run even when the manifest described only the new one.
Run workspaces were not durableLeft 70 of 88 runs without a complete workspace; generated source was recoverable, copied files had to be rebuilt.
A cheap re-score erased browser resultsRunning the scorer without its runtime flag rebuilt the payload and deleted previously collected browser evidence.

There was also a separate seventh issue in the experimental prompt: arm A’s instruction to build the interface “directly”. It did not corrupt a stored number, but it made the code-shape comparison partly a prompt comparison. I keep it separate from the six apparatus defects because the remedy is a new experimental arm, not a scorer repair.

Two earlier checks failed for a more basic reason: they inspected implementation shape instead of observable behaviour. One demanded a particular input element and marked a correct component as broken. Another read an attribute and marked a menu as working without ever opening it. The final checks drive the interface and require a visible state change.

The accounting also needs context. Dependencies were prewarmed, so installation cost was not measured. High-reasoning runs used three or four seeds per task, and three tasks also received three no-extra-reasoning seeds. Reasoning mode removed temperature control, so variation was observed rather than suppressed; within one arm and task, token use varied by roughly three times.

All figures on this page are derived from the experiment data rather than typed as claims. The local data snapshot records 88 runs, 70 high-reasoning runs, 87 runtime-scored runs and total model spend of $0.3844.

What we can say with confidence

The failure mechanism is real, but narrow. Recreating source against a known external API produced invented imports, an invented property and incorrect API usage in five of 16 arm B runs. Copying removed that failure class because the model no longer rewrote those lines. Arm A avoided the same risk by not using the library at all.

Retrieval alone bought nothing measurable here. It increased spend and model-authored source while producing every build failure in the corpus. The benefit came when the source file arrived intact.

Copy-paste was not the smallest repository. It produced the least model-authored code and shipped the most total code. Those extra copied lines carried reusable states, styling and behaviour that arm A often did not build as a component layer.

The defect that survived was the seam. More than one in five eligible runs discarded behaviour that was already available, and every eligible run failed the composition audit on the task designed to stress that connection. Correct source does not guarantee correct composition.

What this experiment cannot prove

This remains a small experiment: one inexpensive model, eight tasks and three or four seeds in each high-effort cell. Arm B covered five of the eight tasks. The accessibility difference is driven mainly by one task. The scorer did not measure visual design quality, screen-reader experience, maintenance after later edits or dependency-install cost.

The most valuable next test is a much stronger frontier model. It may reproduce the library rules correctly, removing arm B’s build-failure gap. That would narrow the case for copying even further.

The next structural test is a neutral version of arm A, called arm A′: build the interface and extract reusable components where useful. That would show whether arm A’s page-specific style was the model’s preference or a consequence of the prompt.

After that comes a blocks-based version of arm C. Instead of copying isolated buttons, menus and dialogs, it would copy larger interface sections that already connect those parts. The question is whether moving more connecting code into the registry reduces the 21.7% seam rate or merely moves the next seam elsewhere.

So, can copy-paste beat code generation?

Copy-paste can beat code generation, but not because generation is generally unreliable. Arm A completed 36 runs, including 27 at high reasoning effort, without a build failure.

It wins when the alternative is asking a model to recreate a specific external API from memory. It can also carry a palette and behaviours that nobody remembered to request. Those advantages end where the application must connect the pieces.

The honest answer is therefore conditional: copy the parts whose contracts already exist, generate the product-specific work, and test the seam between them as seriously as the component itself.