<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Denis Bratchikov</title>
    <description>The latest articles on DEV Community by Denis Bratchikov (@denis_bratchikov).</description>
    <link>https://dev.to/denis_bratchikov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2898523%2Feb45c2e6-68b3-4b3e-bdbb-13dcb8f39578.jpg</url>
      <title>DEV Community: Denis Bratchikov</title>
      <link>https://dev.to/denis_bratchikov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/denis_bratchikov"/>
    <language>en</language>
    <item>
      <title>Design System: Governance and Adoption</title>
      <dc:creator>Denis Bratchikov</dc:creator>
      <pubDate>Tue, 09 Dec 2025 12:02:38 +0000</pubDate>
      <link>https://dev.to/denis_bratchikov/design-system-governance-and-adoption-30ai</link>
      <guid>https://dev.to/denis_bratchikov/design-system-governance-and-adoption-30ai</guid>
      <description>&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;Building a design system is only half of the work.&lt;br&gt;
Yes, it's challenging to evaluate multiple options, gather feedback from stakeholders, and implement everything in code - but this is still the part most engineers feel comfortable with. We know how to explore technologies, analyze requirements, write specifications, and eventually ship the technical foundation.&lt;/p&gt;

&lt;p&gt;The real challenge, at least for me, was bringing this new solution into the daily life of other developers. Any new system introduces friction: it requires time, attention, and a willingness to go through the learning curve. And when teams are under roadmap pressure, people naturally gravitate toward the approaches they already know - even if those approaches caused the very problems we were trying to solve.&lt;/p&gt;

&lt;p&gt;On top of that, some decisions that seem perfectly reasonable at the beginning may turn out to be inaccurate or incomplete - not only when they meet real-world usage across multiple teams, but also as that real world evolves faster than expected.&lt;/p&gt;

&lt;p&gt;That's what I want to share in this final part of the series: how we handled governance, adoption, and the human side of making a design system truly work.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Why adoption is hard (even when the tech is good)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  2.1. Common challenges familiar to any company
&lt;/h3&gt;

&lt;p&gt;Adoption is rarely a technical problem - it's a human one.&lt;br&gt;
Even when the new system is objectively better, teams face familiar obstacles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;People prefer familiar tools, especially under roadmap pressure.&lt;/li&gt;
&lt;li&gt;New patterns require time, attention, and a mental shift.&lt;/li&gt;
&lt;li&gt;Legacy code feels "&lt;em&gt;safe&lt;/em&gt;", even if it's the cause of recurring issues.&lt;/li&gt;
&lt;li&gt;Every team has slightly different habits, priorities, and constraints.&lt;/li&gt;
&lt;li&gt;Benefits of a design system are long-term, while costs are immediate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words: even good technology doesn't adopt itself.&lt;/p&gt;




&lt;h3&gt;
  
  
  2.2. What was hard in our case
&lt;/h3&gt;

&lt;p&gt;Our situation wasn't radically different from the typical one, but a few challenges stood out.&lt;/p&gt;

&lt;p&gt;First, since our single source of truth was Figma, some of the core complexities came from the design side. Understanding the correct meaning and usage of components - like when to use a &lt;code&gt;Menu&lt;/code&gt;, a &lt;code&gt;Select&lt;/code&gt;, or a &lt;code&gt;Popover&lt;/code&gt; - isn't always obvious if you haven't worked deeply with accessibility-driven patterns. Things get even more complicated when a design &lt;em&gt;blends attributes of multiple components&lt;/em&gt;, such as a popup that allows switching accounts and shows contextual actions at the same time.&lt;/p&gt;

&lt;p&gt;Second, the learning curve was amplified by the number of new technologies introduced at once. The design system was built in parallel with a broader transformation of our frontend architecture: new monorepo structure, the first ui-kit package, migration from &lt;code&gt;styled-components&lt;/code&gt; to &lt;code&gt;PostCSS modules&lt;/code&gt;, introduction of &lt;code&gt;Radix Primitives&lt;/code&gt;, design tokens, component architecture patterns, Storybook documentation, and Chromatic visual tests.&lt;/p&gt;

&lt;p&gt;Each of these changes was reasonable on its own - but together they naturally made adoption more demanding than it would have been with fewer moving parts.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Migration strategy: slow, controlled, predictable
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3.1. Main principles for gradual and consistent migration
&lt;/h3&gt;

&lt;p&gt;The foundation of our migration strategy was simple: move forward without breaking what already works. A full "big bang" rewrite may look tempting from an engineering perspective, but it is rarely justified for the business unless the gains are dramatic - such as a major performance improvement or a critical SEO uplift. Neither was the case for us, so gradual adoption became the most reasonable and sustainable option.&lt;/p&gt;

&lt;p&gt;Our main principles were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;All new features must use the new components&lt;/strong&gt;
This applied both to Figma (as the single source of truth) and to code. New work should build on the new system; legacy components should remain only where they already exist.
This ensures natural growth of the new system without forcing teams into expensive rewrites.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"If you touch it - improve it"&lt;/strong&gt;
This company-wide rule proved extremely useful. Whenever someone modifies or refactors a piece of UI, it's a good opportunity to migrate that part to the new components as well.
Since the PR must already be reviewed and visually tested, merging the migration into the same change often reduces the total cost compared to doing it later as a separate task.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together, these principles created a predictable and manageable adoption flow: slow enough not to disrupt product work, but steady enough to avoid stagnation.&lt;/p&gt;




&lt;h3&gt;
  
  
  3.2. Design review as part of adoption
&lt;/h3&gt;

&lt;p&gt;In most teams, new designs are reviewed by a &lt;strong&gt;Head of Design&lt;/strong&gt; or &lt;strong&gt;CPO&lt;/strong&gt; before they are marked as &lt;em&gt;Ready for Dev&lt;/em&gt;.&lt;br&gt;
However, neither of those roles initially owned the full context of the newly introduced design system - its components, constraints, patterns, or how they translated into code.&lt;/p&gt;

&lt;p&gt;To bridge this gap, we introduced an additional review step for the first few months: &lt;strong&gt;Design System Review&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This review was handled by two people who fully understood the system's architecture and principles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;strong&gt;Design System Lead&lt;/strong&gt; from the design side, and&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;Project Lead&lt;/strong&gt; from engineering.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This additional checkpoint produced several important benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Catching inconsistencies early&lt;/strong&gt;
Issues in component choice, naming, and patterns were flagged before any code was written.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Knowledge sharing&lt;/strong&gt;
Designers learned how to use the new system correctly - which components to pick, how semantic tokens worked, when to use specific patterns, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better predictability&lt;/strong&gt;
Designs handed to developers were more consistent, more realistic, and aligned with the actual capabilities of the system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This short-lived review phase significantly accelerated adoption by ensuring that both design and engineering moved forward with the same mental model.&lt;/p&gt;




&lt;h3&gt;
  
  
  3.3 Developer Experience improvements
&lt;/h3&gt;

&lt;p&gt;People naturally gravitate toward familiar behavior, we introduced several &lt;strong&gt;DX-focused guardrails&lt;/strong&gt; to promote consistent usage of the design system and reduce friction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Custom ESLint rules&lt;/strong&gt;
These rules enforced correct usage of components and tokens (as mentioned in Part II).
They provided immediate feedback in the editor, long before a PR review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Marking all legacy components as deprecated&lt;/strong&gt;
Even components not yet fully reimplemented in the new design system were flagged.
This wasn't meant as pressure — just a clear visual signal that &lt;em&gt;this is the old path&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation (as a last resort)&lt;/strong&gt;
Proper docs were written and maintained, but realistically, documentation works best as a safety net, not as the primary adoption tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation scripts&lt;/strong&gt;
We built tools to export Figma variables into code, generate boilerplate for new components, and streamline contributions to the design system.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A dedicated Slack channel&lt;/strong&gt;
Centralised communication helped teams ask questions, share examples, and stay aligned.
Most importantly, it created a sense of community rather than &lt;em&gt;another rule to follow&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together, these DX additions lowered the cognitive load of using the system and made the correct path the easiest one.&lt;/p&gt;

&lt;p&gt;And most importantly, improving DX ensured that adoption didn't rely on discipline alone - the system itself guided developers toward the right patterns.&lt;/p&gt;




&lt;h3&gt;
  
  
  3.4 AI: migrating in 2025 and beyond
&lt;/h3&gt;

&lt;p&gt;As LLMs have become more accurate and context-aware, a significant part of manual migration work can now be delegated to them.&lt;/p&gt;

&lt;p&gt;To make adoption smoother, we embraced &lt;code&gt;Cursor&lt;/code&gt; and &lt;code&gt;GitHub Copilot&lt;/code&gt; as migration assistants:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automated migration guides&lt;/strong&gt;
With a simple Cursor prompt, we generated structured guides showing how legacy component configurations should be adjusted to match the new API.
These guides could also be reused directly as transformation prompts, e.g.:
"&lt;em&gt;Migrate all legacy &lt;code&gt;Button&lt;/code&gt; components to the new &lt;code&gt;Button&lt;/code&gt; from &lt;code&gt;ui-kit&lt;/code&gt; using &lt;code&gt;docs/migration-guides/Button.mdx&lt;/code&gt;.&lt;/em&gt;"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI rules aligned with the design system&lt;/strong&gt;
Copilot/Cursor suggestions were adapted to prefer design-system components and patterns when generating new code.
This gently nudged developers toward the correct implementation without enforcement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review automation&lt;/strong&gt;
AI-assisted review rules were added to detect code that could be replaced with design-system components and leave appropriate comments.
This offloaded part of the cognitive burden from reviewers and helped teams identify migration opportunities early.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI didn't replace the adoption process - but it became a surprisingly effective accelerator, reducing repetitive work and helping teams move toward the new system with less manual effort.&lt;/p&gt;

&lt;p&gt;This allowed us to shift our focus from mechanical migration tasks to higher-level decisions about the future of the system.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. What worked well
&lt;/h2&gt;

&lt;p&gt;Here's a summary of the practices and approaches that proved especially effective during the development and adoption of our design system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unified and consistent design across new features&lt;/strong&gt;
Once components were standardized, new features automatically looked and behaved consistently across the product.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Higher-quality components with accessibility and keyboard support built in&lt;/strong&gt;
The new components had a clearer API, fewer edge-case bugs, and predictable behavior compared to their legacy equivalents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A solid foundation for future technologies&lt;/strong&gt;
Introducing the first UI package in our monorepo, PostCSS instead of styled-components, Storybook as the central documentation hub, and design tokens created a long-lasting base for all upcoming UI work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Snapshot stories increased trust in UI changes&lt;/strong&gt;
Visual regression testing made developers more confident that refactoring or migration wouldn't break existing UI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DX tooling significantly reduced migration friction&lt;/strong&gt;
Boilerplate generators, token sync scripts, ESLint rules - all of these made the new path easier than the old one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design review improved alignment between design and engineering&lt;/strong&gt;
This step increased designers' awareness of component architecture and helped ensure that the designs handed to engineering were consistent and implementable.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  5. What I would change (and what didn't work)
&lt;/h2&gt;

&lt;p&gt;Looking back almost a year after we started the project, some decisions now seem much clearer - including those I would approach differently today.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1. Starting the project after the engineering team scaled
&lt;/h3&gt;

&lt;p&gt;The design system effort began after a period of rapid growth. This created unnecessary technical debt and onboarding complexity.&lt;br&gt;
In retrospect, investing in foundational quality &lt;em&gt;before&lt;/em&gt; scaling the team would have saved time and reduced friction later.&lt;br&gt;
Newcomers have to learn the system anyway - better to onboard them into a clean, consistent foundation.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2. Underestimating the impact of AI evolution (as of 2024)
&lt;/h3&gt;

&lt;p&gt;We couldn't predict how dramatically AI-assisted development would improve.&lt;br&gt;
Today, I would lean more toward &lt;strong&gt;mainstream, widely adopted technologies&lt;/strong&gt;, simply because AI models are already deeply trained on them.&lt;br&gt;
More mainstream tech =&amp;gt; better suggestions =&amp;gt; smoother onboarding =&amp;gt; fewer custom rules.&lt;/p&gt;

&lt;p&gt;For example, &lt;code&gt;Tailwind&lt;/code&gt; might have been a more AI-friendly choice than vanilla CSS modules with utilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3. Documentation should live in the codebase, not Notion
&lt;/h3&gt;

&lt;p&gt;This lesson became obvious in the age of AI agents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docs stored outside the repo are invisible to IDE assistants.&lt;/li&gt;
&lt;li&gt;To make AI truly helpful, your documentation must be &lt;strong&gt;co-located with code&lt;/strong&gt; - and ideally in a format consumable by Storybook, Docusaurus, or similar tools to publish it easily.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;High-level architectural docs in Notion sound nice, but they become disconnected from where development actually happens.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Lessons learned (my biggest challenge)
&lt;/h2&gt;

&lt;p&gt;When I first imagined this project, I assumed the hardest part would be designing the system and getting leadership approval to invest in it.&lt;/p&gt;

&lt;p&gt;I was only half right.&lt;/p&gt;

&lt;p&gt;The real challenge was convincing people - designers and engineers - to actually use the new design system.&lt;br&gt;
This experience taught me several important lessons:&lt;/p&gt;

&lt;h3&gt;
  
  
  6.1. Know your client (KYC)
&lt;/h3&gt;

&lt;p&gt;My &lt;em&gt;clients&lt;/em&gt; were developers with a full roadmap, tight deadlines, and limited time to learn new systems.&lt;br&gt;
A design system must feel familiar, even if that means compromising on theoretical best practices.&lt;br&gt;
Practicality over purity.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.2. People follow the easiest path
&lt;/h3&gt;

&lt;p&gt;If the new system is not frictionless, adoption slows down dramatically.&lt;br&gt;
Ease of use wins every time - even over code perfection.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.3. Find allies
&lt;/h3&gt;

&lt;p&gt;You need people who care - even a few of them.&lt;br&gt;
Looking back, a great idea would have been to assign each team 1–2 components aligned with their roadmap.&lt;br&gt;
This would distribute ownership, improve knowledge sharing, and turn contributors into advocates.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.4. Celebrate contributions
&lt;/h3&gt;

&lt;p&gt;I once created a simple script to count contributions to the design system (PRs, LOC, etc.) and shared the results quarterly.&lt;br&gt;
Recognising people publicly boosted engagement and encouraged others to join.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Conclusion
&lt;/h2&gt;

&lt;p&gt;A design system is not just a UI library - it's processes, people, habits, and trust.&lt;br&gt;
Technology creates the foundation, but governance and culture create the actual value.&lt;/p&gt;

&lt;p&gt;This project wasn't an easy walk in the park - it was a real organisational and technical challenge.&lt;br&gt;
But it was a successful one: we built a scalable, predictable, trustworthy system that helped us deliver features faster and with more confidence.&lt;/p&gt;




&lt;p&gt;Feel free to &lt;strong&gt;connect with&lt;/strong&gt; me on &lt;a href="https://www.linkedin.com/in/denis-bratchikov/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🚀 &lt;strong&gt;More content coming soon - stay tuned!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>designsystem</category>
      <category>frontend</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Design System: Building the Foundations</title>
      <dc:creator>Denis Bratchikov</dc:creator>
      <pubDate>Tue, 11 Nov 2025 09:00:00 +0000</pubDate>
      <link>https://dev.to/denis_bratchikov/design-system-building-the-foundations-1l75</link>
      <guid>https://dev.to/denis_bratchikov/design-system-building-the-foundations-1l75</guid>
      <description>&lt;p&gt;In the previous article, I talked about how our team moved from chaos to consistency.&lt;/p&gt;

&lt;p&gt;This time, let's go deeper - into the engineering side of our design system: how we chose the right tools, why we made some trade-offs, and how these decisions shaped the foundation for everything that came later.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. High-level architecture overview
&lt;/h2&gt;

&lt;p&gt;Before diving into the details, it's worth mentioning that the actual technology stack or tool isn't what truly matters - it's why you pick one over another.&lt;br&gt;
Tools come and go, but the reasoning behind your choices defines how maintainable and scalable your system will be in the long run.&lt;/p&gt;

&lt;p&gt;Below, I'll walk through each technology we used and the reasoning behind those choices. Sometimes, the logic was the result of long discussions; other times, it was simply obvious (and that's perfectly fine - not every decision has to be over-engineered 😄).&lt;/p&gt;

&lt;p&gt;To give a quick overview, here's our tech-stack snapshot:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Tool / Approach&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Packaging&lt;/td&gt;
&lt;td&gt;Monorepo (Turborepo)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Styles&lt;/td&gt;
&lt;td&gt;PostCSS Modules + Design Tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI Components&lt;/td&gt;
&lt;td&gt;Radix Primitives + Custom components&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documentation&lt;/td&gt;
&lt;td&gt;Storybook&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Testing&lt;/td&gt;
&lt;td&gt;Chromatic visual diffs + Unit tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Developer Experience&lt;/td&gt;
&lt;td&gt;Scaffolding CLI, ESLint rules&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  2. Consistency by Design: Mirroring Figma
&lt;/h2&gt;

&lt;p&gt;We have a strong design team in the company, and Figma is our single place for creating and maintaining visual consistency - from colors and typography to component states and prototypes.&lt;br&gt;
Designers use &lt;strong&gt;variables&lt;/strong&gt; for colors, font styles, spacing, and shared &lt;strong&gt;components&lt;/strong&gt; like &lt;code&gt;Button&lt;/code&gt;, &lt;code&gt;Input&lt;/code&gt;, or &lt;code&gt;Dropdown&lt;/code&gt; to ensure reusability and alignment across all product teams.&lt;/p&gt;

&lt;p&gt;When it comes to implementation, developers receive the final designs for a feature and translate them into code.&lt;br&gt;
That translation, however, can easily become a bottleneck if both sides speak different "&lt;em&gt;languages&lt;/em&gt;".&lt;br&gt;
So before we even started building the design system, we defined one guiding principle:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;No translation layer between Figma and code.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That meant - if a developer sees &lt;code&gt;variant="secondary"&lt;/code&gt; on a &lt;code&gt;Button&lt;/code&gt; in Figma, they should expect the exact same &lt;code&gt;variant="secondary"&lt;/code&gt; prop in the UI kit component.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fssgq5h90hl12efv9t2wk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fssgq5h90hl12efv9t2wk.png" alt="Button example from Figma" width="800" height="179"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;This approach brought two key benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single source of truth - Figma. The design system in code is not a copy of Figma; it's an extension of it. We don't reinvent naming conventions or variants - we mirror them.&lt;/li&gt;
&lt;li&gt;Reduced cognitive overhead. Developers no longer need to mentally translate "what does &lt;em&gt;Primary&lt;/em&gt; / &lt;em&gt;Neutral&lt;/em&gt; / &lt;em&gt;Subtle&lt;/em&gt; mean in code?" Instead, the codebase becomes a 1:1 reflection of the design decisions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course, this approach wasn't free of trade-offs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Designers now need to be careful when renaming or restructuring Figma components, since the code directly relies on those definitions.&lt;/li&gt;
&lt;li&gt;Complex components (like &lt;code&gt;comboboxes&lt;/code&gt; or &lt;code&gt;date pickers&lt;/code&gt;) sometimes don't map cleanly to Figma due to differences in interaction logic or platform constraints.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Still, the result was worth it - this alignment made handoffs almost frictionless, and helped both designers and developers think in terms of &lt;strong&gt;system design&lt;/strong&gt;, not just pixels or props.&lt;/p&gt;


&lt;h2&gt;
  
  
  3. Technology choices &amp;amp; rationale
&lt;/h2&gt;
&lt;h3&gt;
  
  
  3.1. ⚙️ Base Components
&lt;/h3&gt;

&lt;p&gt;This choice was one of the most critical ones - because once we picked the approach, it would define the foundation for years. Changing the architecture later would require enormous effort, so we took this decision seriously.&lt;/p&gt;

&lt;p&gt;For the first iteration, I created an &lt;strong&gt;ADR (Architecture Decision Record)&lt;/strong&gt; describing all possible options, along with their pros and cons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use an existing third-party design system.&lt;/li&gt;
&lt;li&gt;Build a fully custom system from scratch.&lt;/li&gt;
&lt;li&gt;Follow a hybrid approach - adopt a base library and build our own components on top of it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After gathering feedback from fellow developers, we quickly ruled out the first option. Given the nature of our product, we wanted much more control over component behavior and appearance - our designers often push beyond standard UI patterns, and a pre-built system would become a limitation rather than a shortcut.&lt;/p&gt;

&lt;p&gt;That left us with two viable options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;building our own design system from scratch, or&lt;/li&gt;
&lt;li&gt;adopting &lt;a href="https://www.radix-ui.com/primitives" rel="noopener noreferrer"&gt;Radix Primitives&lt;/a&gt;, a well-known component library among our developers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For context: &lt;strong&gt;Radix Primitives&lt;/strong&gt; provides fully unstyled, accessible components that offer full control over visual implementation, while still handling all logic and accessibility behind the scenes.&lt;/p&gt;



&lt;p&gt;To make the comparison more objective, I used a simple &lt;strong&gt;"traffic light"&lt;/strong&gt; approach: I defined several critical criteria - such as &lt;code&gt;time-to-market&lt;/code&gt;, &lt;code&gt;team expertise&lt;/code&gt;, &lt;code&gt;scalability &amp;amp; future-proofing&lt;/code&gt;, &lt;code&gt;maintainability &amp;amp; long-term viability&lt;/code&gt;, etc. - and evaluated both options accordingly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd1h6j3en7lme0ht5onrl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd1h6j3en7lme0ht5onrl.png" alt="Illustration of the traffic light approach" width="800" height="577"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the end, both options proved to be viable - the evaluation showed that neither clearly outperformed the other. However, we decided to go with &lt;strong&gt;Radix Primitives&lt;/strong&gt;, as the &lt;code&gt;time-to-market&lt;/code&gt; (and therefore budget) criteria were the most critical for us as a fast-moving product company. We needed to move fast, ship reliably, and avoid reinventing every accessibility behavior.&lt;/p&gt;

&lt;p&gt;However, we established a few important internal rules to keep our implementation consistent and maintain control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We never expose Radix components directly. All components are wrapped and exported only through our internal &lt;code&gt;ui-kit&lt;/code&gt; package.&lt;/li&gt;
&lt;li&gt;Each component exposes only a &lt;strong&gt;minimal set&lt;/strong&gt; of props. This enforces consistency and prevents uncontrolled API sprawl across teams.&lt;/li&gt;
&lt;li&gt;Custom components follow the same composition pattern as Radix. This keeps the implementation predictable and cohesive throughout the system.&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;
  
  
  3.2. 🧩 CSS Architecture
&lt;/h3&gt;

&lt;p&gt;Before starting the design system, we were using a &lt;strong&gt;CSS-in-JS&lt;/strong&gt; approach. It worked well at the beginning, but by 2024 it had already started showing several limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;styled-components&lt;/code&gt; rely on React Context&lt;/strong&gt;, which makes server-side rendering inefficient - and even incompatible with React Server Components. We wanted to take full advantage of both &lt;strong&gt;Next.js SSR&lt;/strong&gt; and &lt;strong&gt;React Server Components&lt;/strong&gt;, so this became a blocker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime styling overhead&lt;/strong&gt; - no built-in CSS caching or minification, and a visible delay in style application. This could be improved with time investments, but it wasn't worth the complexity at that stage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since we were building the new design system from scratch, it was a great opportunity to &lt;strong&gt;rethink our CSS architecture&lt;/strong&gt; and choose a more scalable, modern, and compatible solution.&lt;/p&gt;



&lt;p&gt;To make the decision structured, we again used the &lt;strong&gt;traffic light approach&lt;/strong&gt;, comparing four main options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/css-modules/css-modules" rel="noopener noreferrer"&gt;CSS Modules&lt;/a&gt;&lt;/strong&gt; - simple, widely supported, with preprocessors like &lt;code&gt;SASS&lt;/code&gt;, &lt;code&gt;PostCSS&lt;/code&gt;, or &lt;code&gt;LESS&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://stylexjs.com/" rel="noopener noreferrer"&gt;StyleX (Meta)&lt;/a&gt;&lt;/strong&gt; - a compile-time CSS-in-JS approach with the benefits of static extraction, but also with migration complexity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind CSS&lt;/a&gt;&lt;/strong&gt; - utility-first, scalable, and mature ecosystem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Our current CSS-in-JS&lt;/strong&gt; - used as a baseline for comparison.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a result, we had two promising candidates: &lt;strong&gt;CSS Modules&lt;/strong&gt; and &lt;strong&gt;Tailwind CSS&lt;/strong&gt;.&lt;br&gt;
&lt;code&gt;StyleX&lt;/code&gt;, while technically appealing, would have added unnecessary infrastructure overhead - especially if we had to support two CSS-in-JS systems simultaneously during migration. It also came with a learning curve, since it isn't a one-to-one replacement for &lt;code&gt;styled-components&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;While &lt;strong&gt;Tailwind&lt;/strong&gt; is scalable, well-known, and future-proof, it came with a significant drawback for our setup. We wanted to keep Figma as the single source of truth and ensure a smooth handoff between design and code. The utility-first nature of Tailwind, however, introduced an additional translation layer: developers had to interpret design specs into utility classes, which added friction and made it harder to maintain a direct connection between the design system in Figma and its implementation in code.&lt;/p&gt;

&lt;p&gt;That was the main reason why we decided to go with &lt;strong&gt;CSS Modules&lt;/strong&gt; - they allowed us to keep &lt;strong&gt;naming conventions and structure aligned with Figma&lt;/strong&gt;, making the implementation process more natural for both designers and developers.&lt;/p&gt;

&lt;p&gt;When selecting a preprocessor, we picked &lt;code&gt;PostCSS&lt;/code&gt;, because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It works &lt;strong&gt;on top of&lt;/strong&gt; &lt;code&gt;.module.css&lt;/code&gt; as a lightweight transpiler - no custom syntax or runtime overhead.&lt;/li&gt;
&lt;li&gt;It's &lt;strong&gt;highly extensible&lt;/strong&gt;, with a huge ecosystem of plugins, yet doesn't introduce unnecessary abstraction.&lt;/li&gt;
&lt;li&gt;It's &lt;strong&gt;fully aligned with the official CSS spec&lt;/strong&gt;, including modern features like CSS Nesting, giving it long-term stability.&lt;/li&gt;
&lt;li&gt;By 2024, &lt;code&gt;SASS&lt;/code&gt; and &lt;code&gt;LESS&lt;/code&gt; were already considered legacy, while &lt;code&gt;PostCSS&lt;/code&gt; continued evolving with modern tooling and browser support.&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;Ultimately, we went with a combination of &lt;strong&gt;CSS Modules + PostCSS&lt;/strong&gt; - a simple, fast, and standards-based approach that gave us flexibility without sacrificing performance or future compatibility.&lt;/p&gt;


&lt;h3&gt;
  
  
  3.3. 🎨 Design Tokens as the Source of Truth
&lt;/h3&gt;

&lt;p&gt;As you may have already noticed, the design system project became a &lt;strong&gt;pivot point&lt;/strong&gt; for us - an opportunity to introduce modern technologies, workflows, and best practices, while gradually updating our existing codebase to align with them.&lt;/p&gt;

&lt;p&gt;Before that (as I mentioned in the first article of the series), we were using &lt;strong&gt;abstract design tokens&lt;/strong&gt; implemented as &lt;strong&gt;CSS variables&lt;/strong&gt; to style UI components.&lt;br&gt;
While this approach served us reasonably well, it revealed clear limitations once we started aiming for a more scalable and maintainable design system.&lt;/p&gt;

&lt;p&gt;First, the same token was often serving multiple purposes.&lt;br&gt;
For example, &lt;code&gt;inkMain&lt;/code&gt; might be used both as a text color in one case and as a background color in another.&lt;br&gt;
This made updates risky: whenever we changed a token's value, we had to search for all its usages and verify that the new color didn't cause &lt;strong&gt;contrast ratio&lt;/strong&gt; or &lt;strong&gt;readability&lt;/strong&gt; issues.&lt;/p&gt;

&lt;p&gt;Second, it was hard to establish a consistent &lt;strong&gt;color scale&lt;/strong&gt; - for example, from light gray to dark gray - because tokens were used inconsistently, and there was no clear hierarchy between base (primitive) and contextual (semantic) values.&lt;/p&gt;



&lt;p&gt;To address this, we introduced a &lt;strong&gt;three-layer token model&lt;/strong&gt;:&lt;br&gt;
&lt;code&gt;Primitive&lt;/code&gt;, &lt;code&gt;Semantic&lt;/code&gt;, and &lt;code&gt;Component-specific&lt;/code&gt; tokens.&lt;br&gt;
This approach didn't require much debate - it was warmly welcomed by both designers and developers, since it created a shared vocabulary and made color updates safer and more predictable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa0vhecwab8h7237wp16d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa0vhecwab8h7237wp16d.png" alt="Primitive =&amp;gt; Semantic =&amp;gt; Component tokens" width="800" height="91"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can find similar models described in &lt;a href="https://help.figma.com/hc/en-us/articles/18490793776023-Update-1-Tokens-variables-and-styles#token-organization" rel="noopener noreferrer"&gt;Figma Learn&lt;/a&gt;, &lt;a href="https://atlassian.design/foundations/tokens/design-tokens" rel="noopener noreferrer"&gt;Atlassian Design Tokens&lt;/a&gt;, or &lt;a href="https://spectrum.adobe.com/page/design-tokens/" rel="noopener noreferrer"&gt;Adobe Spectrum&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Despite the obvious benefits, this structure also introduced a few new challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Designers&lt;/strong&gt; had to learn the new naming conventions and meanings of tokens. For example, both &lt;code&gt;--color-background-panel&lt;/code&gt; and &lt;code&gt;--color-background-interactive&lt;/code&gt; might reference the same primitive token &lt;code&gt;--color-gray-95&lt;/code&gt;, but they serve different purposes: one for static surfaces (panels, containers), and the other for interactive elements (buttons, inputs, etc.).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developers&lt;/strong&gt; had to adapt as well - using &lt;strong&gt;only semantic&lt;/strong&gt; tokens instead of falling back to primitive ones, even when the design itself seemed to suggest otherwise. This required some &lt;strong&gt;muscle memory&lt;/strong&gt; and &lt;strong&gt;discipline&lt;/strong&gt;, but it paid off in long-term maintainability and consistency.&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;
  
  
  3.4. 📘 Storybook + Chromatic
&lt;/h3&gt;

&lt;p&gt;So, the foundation was ready: &lt;strong&gt;Design Tokens&lt;/strong&gt;, &lt;strong&gt;CSS Modules with PostCSS&lt;/strong&gt;, &lt;strong&gt;Radix Primitives&lt;/strong&gt; for base components (and custom UI components where needed), and the existing infrastructure for the Design System package.&lt;br&gt;
The bare minimum was in place - now it was time to make it both &lt;strong&gt;clear&lt;/strong&gt; for its users (developers) and &lt;strong&gt;trustworthy&lt;/strong&gt; in terms of quality and stability.&lt;/p&gt;



&lt;p&gt;We were already using &lt;strong&gt;&lt;a href="https://storybook.js.org/" rel="noopener noreferrer"&gt;Storybook&lt;/a&gt;&lt;/strong&gt; in our main applications to showcase user stories, and it had proven to be a great fit for our workflow.&lt;br&gt;
So there was no debate - we simply adopted it for the design system as well.&lt;/p&gt;

&lt;p&gt;Since the design system can also be valuable for &lt;strong&gt;non-developers&lt;/strong&gt; (e.g., designers or PMs who want to check if a certain pattern or interaction already exists in code), we decided to &lt;strong&gt;publish our Storybook using Chromatic&lt;/strong&gt;.&lt;br&gt;
It made the stories easily accessible to anyone in the company, with versioned previews for every change.&lt;br&gt;
I won't go deep into Storybook configuration or story implementation here - there are already plenty of excellent resources on that.&lt;/p&gt;



&lt;p&gt;Design system components are &lt;strong&gt;visually rich but behaviorally simple&lt;/strong&gt; - they mostly render UI with the correct appearance and delegate event handlers to underlying DOM elements.&lt;br&gt;
Because of that, the reliability of our system depends heavily on &lt;strong&gt;visual accuracy&lt;/strong&gt; rather than complex business logic.&lt;br&gt;
To ensure quality, we adopted a three-layer testing strategy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Static analysis&lt;/strong&gt;, such as type checking and linting (enabled by default).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unit tests&lt;/strong&gt;, covering isolated logic - e.g., disabled states, keyboard interactions, or component hooks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visual regression tests&lt;/strong&gt;, to catch unintended UI changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's skip static and unit tests - those are well-covered by industry practices - and focus on the &lt;strong&gt;visual regression&lt;/strong&gt; part.&lt;/p&gt;

&lt;p&gt;For that, we used &lt;strong&gt;Chromatic's visual snapshot testing&lt;/strong&gt;, integrated directly with our Storybook setup (&lt;a href="https://www.chromatic.com/docs/snapshots/" rel="noopener noreferrer"&gt;docs&lt;/a&gt;).&lt;br&gt;
It's not worth creating visual snapshots for every single story - that would be costly and redundant.&lt;br&gt;
Instead, we focused only on representative visual states for each component.&lt;/p&gt;

&lt;p&gt;To keep this consistent, we disabled automatic snapshots by default and introduced a rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Each component must have a dedicated &lt;code&gt;Snapshot&lt;/code&gt; story that renders all relevant visual variations for regression testing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's also worth mentioning that &lt;strong&gt;Chromatic&lt;/strong&gt; provides other testing capabilities - &lt;a href="https://www.chromatic.com/docs/visual/" rel="noopener noreferrer"&gt;visual&lt;/a&gt;, &lt;a href="https://www.chromatic.com/docs/accessibility/" rel="noopener noreferrer"&gt;accessibility&lt;/a&gt;, and &lt;a href="https://www.chromatic.com/docs/interactions/" rel="noopener noreferrer"&gt;interaction&lt;/a&gt; tests.&lt;br&gt;
However, in our case, those felt like overhead, as the same coverage could be achieved through unit tests - and, importantly, &lt;em&gt;free of charge&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Finally, to ensure no UI change goes unnoticed, we added a Chromatic CI step (&lt;a href="https://www.chromatic.com/docs/ci/" rel="noopener noreferrer"&gt;docs&lt;/a&gt;) into our CI/CD pipeline.&lt;br&gt;
It highlights visual diffs for every pull request and requires a manual review before merging - giving us confidence that no visual regressions slip into production.&lt;/p&gt;


&lt;h3&gt;
  
  
  3.5. 🧠 Developer Experience
&lt;/h3&gt;

&lt;p&gt;Any technological change or new initiative inevitably meets some friction - people need time to learn, adapt, and develop new habits.&lt;br&gt;
To make this process smoother, more reliable, and to improve the overall maintainability of the system, we introduced several developer experience (DX) practices.&lt;/p&gt;



&lt;p&gt;First, we created a simple &lt;strong&gt;scaffolding script&lt;/strong&gt; to generate the boilerplate for new UI components - React file, styles file, tests, stories, and the barrel export.&lt;br&gt;
This ensured a consistent folder structure and prevented developers from accidentally skipping any required files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;Button&lt;/span&gt;
 &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="nt"&gt;Button&lt;/span&gt;&lt;span class="nc"&gt;.tsx&lt;/span&gt;
 &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="nt"&gt;Button&lt;/span&gt;&lt;span class="nc"&gt;.module.css&lt;/span&gt;
 &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="nt"&gt;Button&lt;/span&gt;&lt;span class="nc"&gt;.test.tsx&lt;/span&gt;
 &lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="nt"&gt;Button&lt;/span&gt;&lt;span class="nc"&gt;.stories.tsx&lt;/span&gt;
 &lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="nt"&gt;index&lt;/span&gt;&lt;span class="nc"&gt;.ts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Second, we built a flow to export &lt;strong&gt;design tokens&lt;/strong&gt; from Figma and convert them into CSS variables.&lt;br&gt;
There are plenty of Figma plugins for exporting variables (for example, &lt;a href="https://www.figma.com/community/plugin/1256972111705530093/export-import-variables" rel="noopener noreferrer"&gt;Export/Import Variables&lt;/a&gt;) into a JSON file.&lt;br&gt;
From there, a simple script can generate themed CSS files for both primitive and semantic tokens.&lt;/p&gt;

&lt;p&gt;To boost IDE productivity, we used the &lt;strong&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=phoenisx.cssvar" rel="noopener noreferrer"&gt;CSS Var IntelliSense&lt;/a&gt;&lt;/strong&gt; plugin for VS Code-based IDEs.&lt;br&gt;
It improves autocomplete for CSS variables and allows defining a custom source file, preventing suggestions from unrelated local component tokens.&lt;/p&gt;

&lt;p&gt;However, since Figma variables can be renamed, removed, or overwritten (by accident or intentionally), we wanted to make sure &lt;strong&gt;no invalid tokens could slip into the codebase&lt;/strong&gt;.&lt;br&gt;
To address that, we created a custom &lt;strong&gt;ESLint rule&lt;/strong&gt; that allows only variables either declared locally or listed in the auto-generated source file. This provided a basic but effective safety layer.&lt;/p&gt;

&lt;p&gt;Sometimes you may also need to use a CSS variable inside TypeScript code - for example, setting the &lt;code&gt;gap&lt;/code&gt; property in a &lt;code&gt;Grid&lt;/code&gt; component.&lt;br&gt;
To ensure both type safety and token consistency, we extended the generation script to produce a &lt;code&gt;tokens.ts&lt;/code&gt; file that exports an object of all available variables, e.g.:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;colorText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-text)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;colorBackground&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--color-background)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Developers can then import these tokens directly into components, while the linter and TypeScript will catch any non-existent references during CI.&lt;/p&gt;




&lt;p&gt;Finally, we prepared &lt;strong&gt;thorough documentation&lt;/strong&gt;, explaining how to use the design system, how to contribute, and how to migrate from legacy components to the new ones.&lt;br&gt;
Storybook's &lt;code&gt;.mdx&lt;/code&gt; syntax worked perfectly for that, allowing us to deploy the docs via Chromatic - keeping everything in a &lt;strong&gt;single source of truth&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The migration guide itself was generated using &lt;code&gt;Cursor&lt;/code&gt;, and we plan to use the same AI tool to automate the migration of old components to the new system.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Outcome and lessons learned
&lt;/h2&gt;

&lt;p&gt;These decisions weren't just about tools - they defined how we work. We moved faster, shipped safer, and established a technical culture around clarity and ownership. More importantly, the process helped us align engineering and design thinking - something that shaped how we approach UI development as a team.&lt;/p&gt;

&lt;p&gt;However, the adoption of the design system was not entirely frictionless - it raised many organizational and technical challenges, which I'll dive deeper into in the next article.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Next&lt;/strong&gt;: In Part 3, I'll share how we rolled out the design system across multiple teams - how we handled adoption challenges, established governance, measured success, and made the system a natural part of our daily development workflow.&lt;/p&gt;

</description>
      <category>designsystem</category>
      <category>frontend</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
    <item>
      <title>From Chaos to Clarity: How We Realized We Needed a Design System</title>
      <dc:creator>Denis Bratchikov</dc:creator>
      <pubDate>Wed, 29 Oct 2025 09:00:00 +0000</pubDate>
      <link>https://dev.to/denis_bratchikov/from-chaos-to-clarity-how-we-realized-we-needed-a-design-system-3964</link>
      <guid>https://dev.to/denis_bratchikov/from-chaos-to-clarity-how-we-realized-we-needed-a-design-system-3964</guid>
      <description>&lt;p&gt;Some initiatives are easy to pitch.&lt;br&gt;
Some take a few meetings and diagrams.&lt;br&gt;
And some - even if they're clearly beneficial - feel like dragging a boulder uphill for months.&lt;/p&gt;

&lt;p&gt;For me, that boulder was our design system.&lt;br&gt;
It took almost a year to convince the company that we needed it.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Was Wrong
&lt;/h2&gt;

&lt;p&gt;Before we started the initiative, our frontend lived in a kind of organized chaos.&lt;br&gt;
Here's what it looked like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Design inconsistency&lt;/strong&gt; - Designers re-created components for (almost) every new feature, using loosely defined color tokens. Developers were reusing &lt;em&gt;almighty&lt;/em&gt; components - not as bad as &lt;code&gt;react-select&lt;/code&gt;, but close enough.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lack of reliability&lt;/strong&gt; - Nobody wanted to touch core components. Every small fix risked breaking something elsewhere, and there were barely any tests to catch regressions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No documentation&lt;/strong&gt; - You had to know what components existed or spend time reverse-engineering them from the code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No accessibility&lt;/strong&gt; - Almost no keyboard support, no focus management, no &lt;code&gt;a11y&lt;/code&gt; structure at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a small company focused on delivering features as fast as possible, this was "&lt;em&gt;fine&lt;/em&gt;".&lt;br&gt;
Shipping speed mattered more than consistency or maintainability.&lt;br&gt;
Until it didn't.&lt;/p&gt;




&lt;h2&gt;
  
  
  When Grows Hit
&lt;/h2&gt;

&lt;p&gt;In 2024, our engineering team tripled.&lt;br&gt;
New developers joined critical projects with tight deadlines, and naturally, they had no time to explore the existing UI foundation.&lt;/p&gt;

&lt;p&gt;So they did what anyone under pressure would do - they just built things from scratch to match the designs.&lt;br&gt;
Within a few months, we ended up with dozens of inconsistencies, duplicated code, and bugs that nobody wanted to own.&lt;/p&gt;

&lt;p&gt;The lack of ownership became a real cost - every small feature was getting slower and riskier to release.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Breaking Point: Rebranding
&lt;/h2&gt;

&lt;p&gt;Then came the rebranding project - our "&lt;em&gt;bingo moment&lt;/em&gt;".&lt;br&gt;
We had to update almost &lt;strong&gt;all colors and fonts&lt;/strong&gt; in the product… &lt;em&gt;in two weeks&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;On paper, that sounded simple. In reality, it was a nightmare.&lt;/p&gt;

&lt;p&gt;We did have CSS tokens, but they were too abstract (&lt;code&gt;inkMain&lt;/code&gt;, &lt;code&gt;inkMedium&lt;/code&gt;, etc.). During rebrand, it turned out that the &lt;em&gt;new&lt;/em&gt; &lt;code&gt;inkMain&lt;/code&gt; could mean several different things depending on where it was used - background, text, or hover state, etc.&lt;/p&gt;

&lt;p&gt;This rebrand became a stress test.&lt;br&gt;
We patched components with feature-specific &lt;code&gt;if&lt;/code&gt; statements, applied conditional CSS, and created more exceptions than rules.&lt;br&gt;
CI started producing weird, unpredictable failures that haunted us for months.&lt;/p&gt;

&lt;p&gt;What looked like a "simple UI refresh" turned into &lt;strong&gt;100+ engineering tickets&lt;/strong&gt;, exposing every bit of technical debt we had accumulated.&lt;/p&gt;

&lt;p&gt;We had finally hit the ceiling of our "move fast and patch things" philosophy.&lt;br&gt;
A &lt;strong&gt;design system&lt;/strong&gt; was no longer a nice-to-have; it was the only way to bring order back to the product.&lt;/p&gt;

&lt;p&gt;It was clear that without a unified design language and shared ownership, every future change - even a small one - would cost us exponentially more time and nerves.&lt;/p&gt;

&lt;p&gt;And at that moment, something finally clicked.&lt;br&gt;
One of our senior designers stepped up to become the &lt;strong&gt;design system ambassador&lt;/strong&gt; - someone willing to take responsibility for the design side, ensure that the design team followed consistent Figma components, and work closely with engineering to build the bridge we'd been missing.&lt;/p&gt;

&lt;p&gt;That was the moment when the idea stopped being "my thing" and became &lt;em&gt;our&lt;/em&gt; initiative.&lt;/p&gt;




&lt;h2&gt;
  
  
  Finding the Right Starting Point
&lt;/h2&gt;

&lt;p&gt;Once we finally got the green light, we needed a plan - roadmap, resources, milestones.&lt;/p&gt;

&lt;p&gt;Right after the rebranding, we were scheduled to rebuild two major user experiences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;logged-in dashboard&lt;/strong&gt; and its related pages;&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;core product UI&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was the perfect timing: if we were already rebuilding large parts of the product, we might as well build them &lt;em&gt;properly&lt;/em&gt; this time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Defining The "Foundation"
&lt;/h2&gt;

&lt;p&gt;We started with an &lt;strong&gt;architecture planning session&lt;/strong&gt; to define what "foundation" actually meant for us.&lt;br&gt;
Building a design system isn't a one-week task - so we had to define a minimal, realistic first iteration.&lt;/p&gt;

&lt;p&gt;We used an &lt;strong&gt;impact–effort matrix&lt;/strong&gt; to prioritize components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;most-used and visible components first,&lt;/li&gt;
&lt;li&gt;those blocking new features next,&lt;/li&gt;
&lt;li&gt;and finally, replacements for bug-prone legacy ones (if still have some time).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every new UI task in &lt;em&gt;ClickUp&lt;/em&gt; was linked to a component ticket in the new design-system project. That helped us track dependencies and ensure that the base components were built before any new feature ticket was started.&lt;/p&gt;

&lt;p&gt;Some components were intentionally postponed for the post-release phase, and a few legacy ones we decided not to touch at all until there was a real need to update them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned (So Far)
&lt;/h2&gt;

&lt;p&gt;Looking back, the hardest part wasn’t the tech.&lt;br&gt;
It was the waiting - nearly nine months before we could even start.&lt;/p&gt;

&lt;p&gt;And while we finally made it happen, I still believe we could have saved enormous effort by starting earlier, before the aggressive hiring phase and the architectural sprawl that followed.&lt;/p&gt;

&lt;p&gt;But that's a topic for the final part - where I'll look back at what worked, what didn't, and what I'd absolutely do differently if I were starting again.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Next&lt;/strong&gt;: In Part 2, I'll share how we built the actual system - architecture, tooling, CI/CD, and the technical decisions behind it.&lt;/p&gt;

</description>
      <category>designsystem</category>
      <category>frontend</category>
      <category>architecture</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Monorepo in Practice (part 2): so was it worth it?</title>
      <dc:creator>Denis Bratchikov</dc:creator>
      <pubDate>Tue, 22 Jul 2025 08:45:35 +0000</pubDate>
      <link>https://dev.to/denis_bratchikov/monorepo-in-practice-part-2-so-was-it-worth-it-1a24</link>
      <guid>https://dev.to/denis_bratchikov/monorepo-in-practice-part-2-so-was-it-worth-it-1a24</guid>
      <description>&lt;p&gt;&lt;strong&gt;This post is part 2 of 2. In this one:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where monorepo shines&lt;/li&gt;
&lt;li&gt;Where it sucks&lt;/li&gt;
&lt;li&gt;My honest take: when you should and shouldn't do it&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;br&gt;
&lt;/h2&gt;



&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/denis_bratchikov/monorepo-in-practice-part-1-between-please-dont-and-please-do-3l7f"&gt;part one&lt;/a&gt;, I covered why we decided to move to a monorepo — and what pains we were trying to solve.&lt;/p&gt;

&lt;p&gt;So now, the big question: &lt;strong&gt;was it worth it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Spoiler: mostly yes - but not without surprises.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;br&gt;
&lt;/h2&gt;



&lt;h2&gt;
  
  
  1. What are the benefits?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1.1 Code awareness went through the roof
&lt;/h3&gt;

&lt;p&gt;Now, when I search for a function or a component, chances are it already exists - inside a feature package. I can just move it to a shared package and reuse instead of reimplementing everything from scratch.&lt;/p&gt;

&lt;p&gt;I don't think that kind of visibility happens often in a polyrepo world...&lt;/p&gt;

&lt;p&gt;Also, with a unified structure and shared configs, developers are on the same page. Even if you're changing someone else's code — you know exactly how to test it and where to look.&lt;/p&gt;




&lt;h3&gt;
  
  
  1.2 Refactoring became a walk in the park
&lt;/h3&gt;

&lt;p&gt;Refactoring used to be tedious. So we outsourced it to AI 🤖&lt;/p&gt;

&lt;p&gt;Imagine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You improve a component contract&lt;/li&gt;
&lt;li&gt;You update all its usages&lt;/li&gt;
&lt;li&gt;You double-check every edge case&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Classic headache.&lt;/p&gt;

&lt;p&gt;A while back, we shipped a feature behind a feature flag. It worked great - and when it was time to clean it up, we just asked Cursor:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Hey, remove this flag and delete any leftover code."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It worked &lt;em&gt;brilliantly&lt;/em&gt;.&lt;br&gt;&lt;br&gt;
Mostly because the AI had full visibility into the monorepo and could grep all flag usages, one file at a time. I just prompted, clicked &lt;code&gt;continue&lt;/code&gt; a few times when it hit operation limits - and spent ~20 minutes reviewing.&lt;/p&gt;

&lt;p&gt;That's it.&lt;/p&gt;


&lt;h3&gt;
  
  
  1.3 Developer experience is just better
&lt;/h3&gt;

&lt;p&gt;Day-to-day work became much smoother.&lt;/p&gt;

&lt;p&gt;Want to run just the &lt;code&gt;ui&lt;/code&gt; package?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm dev &lt;span class="nt"&gt;--filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Want to run everything? Go ahead.&lt;/p&gt;

&lt;p&gt;You're no longer connecting the dots across 5 repos to debug a single change. You don't need to play "&lt;em&gt;git archaeologist&lt;/em&gt;" to find where something came from.&lt;/p&gt;

&lt;p&gt;And onboarding?&lt;/p&gt;

&lt;p&gt;New devs can install and start coding in minutes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pnpm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pnpm dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;br&gt;
&lt;/h2&gt;



&lt;h2&gt;
  
  
  2. What are the pain points?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  2.1 CI/CD pipelines
&lt;/h3&gt;

&lt;p&gt;Yeah, that's a real one.&lt;/p&gt;

&lt;p&gt;You go from having a few focused pipelines… to one giant spaghetti-pipeline that tries to handle &lt;em&gt;everything&lt;/em&gt; at once.&lt;/p&gt;

&lt;p&gt;For example, we run Playwright tests for application &lt;code&gt;X&lt;/code&gt; via &lt;a href="https://www.currents.dev/" rel="noopener noreferrer"&gt;currents.dev&lt;/a&gt; - but we absolutely &lt;strong&gt;don't&lt;/strong&gt; want them triggered when someone changes application &lt;code&gt;Y&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Unfortunately, Turborepo doesn't give you that out of the box. Its caching model assumes that you run all affected commands and skip what's not changed - but coordinating that with external CI jobs like Currents is tricky.&lt;/p&gt;

&lt;p&gt;Same goes for other per-app jobs.&lt;/p&gt;

&lt;p&gt;To solve it, we had to build a custom GitHub Action that reads Turbo's output and determines which jobs should run - or be skipped - depending on which apps were actually touched.&lt;/p&gt;

&lt;p&gt;Is it cool? - Yeah.&lt;br&gt;
Is it plug-and-play? - &lt;em&gt;Not even close&lt;/em&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  2.2 Dependency management
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;pnpm&lt;/code&gt; is blazing fast and works great for monorepos - no doubt. But it comes with gotchas.&lt;/p&gt;

&lt;p&gt;Since &lt;code&gt;pnpm&lt;/code&gt; installs each dependency &lt;strong&gt;once&lt;/strong&gt; at the top level and uses symlinks everywhere, &lt;strong&gt;transitive dependencies&lt;/strong&gt; can cause headaches.&lt;/p&gt;

&lt;p&gt;Let’s say:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your app depends on &lt;code&gt;A@2.3.0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;It also depends on &lt;code&gt;B@2.0.0&lt;/code&gt;, and &lt;code&gt;B&lt;/code&gt; depends on &lt;code&gt;A@1.4.0&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;pnpm&lt;/code&gt; will only install &lt;strong&gt;one&lt;/strong&gt; version of &lt;code&gt;A&lt;/code&gt;, and it might pick the wrong one for your use case.&lt;/p&gt;

&lt;p&gt;That can break:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;types&lt;/li&gt;
&lt;li&gt;runtime behavior&lt;/li&gt;
&lt;li&gt;or both&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You'll especially feel the pain when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One app uses a modern stack (e.g. latest React)&lt;/li&gt;
&lt;li&gt;Another app is stuck with legacy versions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...and they both depend on shared packages.&lt;/p&gt;

&lt;p&gt;You either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;start version pinning everything manually, or&lt;/li&gt;
&lt;li&gt;accept the chaos and debug weird mismatches&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So yeah - fast install times come at a price.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;br&gt;
&lt;/h2&gt;



&lt;h2&gt;
  
  
  3. When &lt;code&gt;don't&lt;/code&gt; and when &lt;code&gt;do&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;We actually tried moving our API services into the same monorepo - aiming to have &lt;strong&gt;literally all&lt;/strong&gt; code in one place.&lt;/p&gt;

&lt;p&gt;Sounds neat.&lt;br&gt;
But in practice? A total headache.&lt;/p&gt;

&lt;p&gt;Even though our backend is built with Node + Express (so technically compatible with our frontend stack), the divergence in config, dependencies, CI jobs, and workflows made the whole thing a mess.&lt;/p&gt;

&lt;p&gt;It wasn’t an outright failure - but it forced us to step back and ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do we &lt;em&gt;really&lt;/em&gt; need the API in the same repo?&lt;br&gt;
Or is it enough to keep just the frontend there?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We haven't fully answered that yet. For now, we've paused the migration and kept our backend separate - and honestly, that feels okay. We'll probably revisit it when (or if) the benefits start outweighing the overhead.&lt;/p&gt;




&lt;h3&gt;
  
  
  When I would recommend monorepo:
&lt;/h3&gt;

&lt;p&gt;✅ When your code shares the same stack (e.g., TS + React + tooling)&lt;br&gt;
✅ For medium to large-scale apps (but not mega-enterprise)&lt;br&gt;
✅ When you're trying to kill off submodules and avoid version juggling&lt;/p&gt;




&lt;h3&gt;
  
  
  When I wouldn't:
&lt;/h3&gt;

&lt;p&gt;🚫 When teams work independently on fully untangled apps&lt;br&gt;
🚫 For tiny projects — it's overkill&lt;br&gt;
🚫 At massive scale (think 2k+ devs) - trying to keep &lt;em&gt;everyone&lt;/em&gt; aligned gets absurd fast&lt;/p&gt;




&lt;p&gt;There's no &lt;code&gt;one-size-fits-all&lt;/code&gt;, just like the classic articles said.&lt;br&gt;
But for &lt;strong&gt;our stack, our scale, and our team&lt;/strong&gt; - monorepo turned out to be the right move.&lt;/p&gt;

&lt;p&gt;Will that still be true in five years? No idea.&lt;/p&gt;

&lt;p&gt;But for now, it's a powerful tool - and we're glad we took the leap.&lt;/p&gt;

&lt;p&gt;"(Choo|U)se wisely"!&lt;/p&gt;




&lt;p&gt;Feel free to &lt;strong&gt;connect with&lt;/strong&gt; me on &lt;a href="https://www.linkedin.com/in/denis-bratchikov/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🚀 &lt;strong&gt;More content coming soon - stay tuned!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>monorepo</category>
      <category>frontend</category>
      <category>turborepo</category>
      <category>devops</category>
    </item>
    <item>
      <title>Monorepo in Practice (part 1): Between “Please Don’t” and “Please Do”</title>
      <dc:creator>Denis Bratchikov</dc:creator>
      <pubDate>Tue, 15 Jul 2025 07:53:03 +0000</pubDate>
      <link>https://dev.to/denis_bratchikov/monorepo-in-practice-part-1-between-please-dont-and-please-do-3l7f</link>
      <guid>https://dev.to/denis_bratchikov/monorepo-in-practice-part-1-between-please-dont-and-please-do-3l7f</guid>
      <description>&lt;p&gt;&lt;strong&gt;This post is part 1 of 2. In this one:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why we ditched submodules and went monorepo&lt;/li&gt;
&lt;li&gt;How our structure looks now&lt;/li&gt;
&lt;li&gt;What to consider before migrating (team buy-in, effort, risk)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;br&gt;
&lt;/h2&gt;



&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Not long ago, I stumbled upon two classic articles: &lt;a href="https://medium.com/@mattklein123/monorepos-please-dont-e9a279be011b" rel="noopener noreferrer"&gt;Monorepos: please don’t!&lt;/a&gt; and &lt;a href="https://medium.com/@adamhjk/monorepo-please-do-3657e08a4b70" rel="noopener noreferrer"&gt;Monorepo: please do!&lt;/a&gt;. It's been six years since those posts were written, but the topic is still very much alive.&lt;/p&gt;

&lt;p&gt;That got me thinking: how much has really changed since then? In this post, I’ll share our journey moving to a monorepo for our frontend, what worked, what didn’t, and what surprised us along the way.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;For context:&lt;/strong&gt; Our codebase is &lt;em&gt;~8,700 files&lt;/em&gt;, &lt;em&gt;700k lines of code&lt;/em&gt;, and around &lt;em&gt;1.2GB&lt;/em&gt; (including test screenshots). We’re a team of 25–30 frontend devs working full time on it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;br&gt;
&lt;/h2&gt;



&lt;h2&gt;
  
  
  1. WHY?
&lt;/h2&gt;

&lt;p&gt;Why on earth did we decide to switch to a monorepo?&lt;br&gt;&lt;br&gt;
To answer that, let’s take a look at the setup we had right before the migration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A few shared packages (like &lt;code&gt;ui&lt;/code&gt;, &lt;code&gt;configs&lt;/code&gt;) in separate repos, published to GitHub registry&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app X&lt;/code&gt; — React (CRA 🫠) app, deployed daily on Vercel with Playwright integration tests&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;app Y&lt;/code&gt; — Next.js app (dashboard + landing pages), also deployed daily on Vercel, but uses the main application via GitHub submodules (yep, legacy from when we had only 2 apps)&lt;/li&gt;
&lt;li&gt;Each app had its own GitHub CI pipeline&lt;/li&gt;
&lt;li&gt;The engineering team was growing — we no longer fit in one room, and management started dividing us into smaller teams with distinct business areas... but the code was still shared&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup caused us a lot of problems.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;First — GitHub submodules.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Surprisingly, not many people have worked with them before, so ~70–80% of new hires struggled with them. But that wasn’t even the worst part.&lt;/p&gt;

&lt;p&gt;Imagine you’re mostly working on &lt;code&gt;app X&lt;/code&gt;. You update a component, tweak its behavior and contract, update all its usages, run tests — CI passes — great! You merge it.&lt;br&gt;
All good. Everything works.&lt;br&gt;
Then, someone updates the submodule SHA in &lt;code&gt;app Y&lt;/code&gt; — just one line changed — and boom, CI breaks. Why? Because you forgot to update &lt;code&gt;app Y&lt;/code&gt; when you changed the contract.&lt;/p&gt;

&lt;p&gt;Congrats, your 5-minute fix just turned into a 1-day headache.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Second — delivering fixes to core packages.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Let’s say you fix a bug in the UI components. You’re fast — 15 minutes and it works perfectly in Storybook. But now comes the “real” flow:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;create PR → wait for review → wait for QA → merge and publish the package →&lt;br&gt;
create PR to update &lt;code&gt;app X&lt;/code&gt; → review → QA → merge →&lt;br&gt;
create PR to update &lt;code&gt;app Y&lt;/code&gt; → review → QA → merge →&lt;br&gt;
scratch your eyes out and consider a career in goat herding.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now imagine doing that at scale... or with late feedback.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Third — ownership and code boundaries.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
We were already moving toward smaller, purpose-driven packages with clear ownership. This could work in both mono- and polyrepo setups. We didn’t need true microservices (felt like overengineering), but we &lt;em&gt;did&lt;/em&gt; need structure.&lt;/p&gt;

&lt;p&gt;And on top of the submodule mess, polyrepo gave us even more headaches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every repo needed its own GitHub config: roles, branch rules, required CI jobs...&lt;/li&gt;
&lt;li&gt;Developer experience was rough:
&amp;gt; run package A → symlink it to B → symlink B to C → symlink C to &lt;code&gt;app X&lt;/code&gt; → &lt;em&gt;finally&lt;/em&gt; start debugging&lt;/li&gt;
&lt;li&gt;Knowledge sharing was a joke — unless you proactively hunted through GitHub, you’d have no idea new packages even existed&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Having all this in mind, we decided to migrate to a monorepo (Turborepo) setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;br&gt;
&lt;/h2&gt;



&lt;h2&gt;
  
  
  2. What's the current status?
&lt;/h2&gt;

&lt;p&gt;It’s been almost a year since we migrated to a monorepo. After polishing things up and iterating a bit, we’ve landed on the following setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 Folder structure
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;apps/*&lt;/code&gt; — all applications (both production and dev)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;packages/features/*&lt;/code&gt; — functional, domain-specific feature packages&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;packages/*&lt;/code&gt; — common/shared utilities and modules&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;configs/*&lt;/code&gt; — all configuration code (ESLint, TS configs, custom plugins, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scripts/*&lt;/code&gt; — various dev scripts and automation tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi5kp78rfu33is1qal6n5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi5kp78rfu33is1qal6n5.png" alt="Our Files Structure" width="343" height="833"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2 Tech stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🛠 [Turborepo(&lt;a href="https://turborepo.com/" rel="noopener noreferrer"&gt;https://turborepo.com/&lt;/a&gt;) as the build orchestrator
&lt;/li&gt;
&lt;li&gt;📦 &lt;a href="https://pnpm.io/" rel="noopener noreferrer"&gt;pnpm&lt;/a&gt; as the package manager&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2.3 Compilation strategy
&lt;/h3&gt;

&lt;p&gt;Turborepo supports three main &lt;a href="https://turborepo.com/docs/core-concepts/internal-packages#compilation-strategies" rel="noopener noreferrer"&gt;compilation strategies&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Just-in-Time Packages&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compiled Packages&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publishable Packages&lt;/strong&gt; (not relevant for us — we don't publish internal packages)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We initially went with &lt;strong&gt;compiled packages&lt;/strong&gt; — mostly because it was the easiest to implement given our (legacy) tech stack and tight timeline. It worked, kind of. But now we’re investing time into switching to &lt;strong&gt;just-in-time&lt;/strong&gt; strategy instead. Why?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It requires far less configuration (and less risk of misconfiguring things).&lt;/li&gt;
&lt;li&gt;You don’t need to rebuild packages every time you build the app.&lt;/li&gt;
&lt;li&gt;It enables better code sharing. For instance, we use &lt;code&gt;postcss custom media&lt;/code&gt; defined in the &lt;code&gt;ui&lt;/code&gt; package - but it doesn’t get propagated to other packages because &lt;code&gt;postcss&lt;/code&gt; is stripped out during compilation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So yeah — &lt;em&gt;compiled&lt;/em&gt; got us up and running fast. But &lt;em&gt;just-in-time&lt;/em&gt; is what’s going to scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;br&gt;
&lt;/h2&gt;



&lt;h2&gt;
  
  
  3. Our Migration Story: Time, People, and Pain
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;- "Where’s the money, Devowski?"&lt;br&gt;
- "It's uh... it's in monorepo somewhere, let me take another look."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So - how long does it take to migrate? And what should you think about before even starting?&lt;/p&gt;

&lt;p&gt;Well, first: define your scope - how many apps and packages you’re migrating, your time budget, and what you’re hoping to gain. That will shape everything else.&lt;/p&gt;

&lt;p&gt;In our case, we had a company initiative called &lt;code&gt;Dev Week&lt;/code&gt; — one week where developers aren’t obligated to work on business features and can instead invest in tech debt or internal tools. We figured: perfect timing. Let’s migrate the whole codebase to a monorepo in one shot.&lt;/p&gt;

&lt;p&gt;Here’s how we organized it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A 6-person team&lt;/li&gt;
&lt;li&gt;2 devs focused on the CRA-based app (legacy-heavy)&lt;/li&gt;
&lt;li&gt;2 devs handled the Next.js app (that depends on the CRA one via &lt;em&gt;submodules&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;2 devs worked on infrastructure, migration scripts (for open PRs), and CI pipelines&lt;/li&gt;
&lt;li&gt;We wanted to preserve git history, so we used &lt;a href="https://github.com/newren/git-filter-repo" rel="noopener noreferrer"&gt;&lt;code&gt;git filter-repo&lt;/code&gt;&lt;/a&gt; to merge the old repo histories into their respective folders&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end of the week, we had... a solution that &lt;em&gt;just about worked&lt;/em&gt;.&lt;br&gt;&lt;br&gt;
The CI pipelines were mostly green, preview links were up, and we could ship from the monorepo - so we did.  &lt;/p&gt;

&lt;p&gt;Of course, we hadn’t caught everything - Sentry config, Storybook setup, some edge cases - so we spent the following week fixing and polishing.&lt;/p&gt;




&lt;p&gt;But migrating code is only half the story.&lt;br&gt;&lt;br&gt;
You also need to prepare your &lt;em&gt;team&lt;/em&gt; for that shift.&lt;/p&gt;

&lt;p&gt;From this point on, your devs aren’t in isolated repos anymore — they’re part of a bigger, interconnected system. There's no more "my code" vs "their code" - now it’s "our code I know" and "our code I don’t know yet".&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Moving to monorepo isn’t just a git decision — it’s a team, process, and trust decision.&lt;br&gt;
Make sure you're not only moving code, but moving minds too.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;This is the end of part 1. In Part 2, I’ll cover:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where monorepo shines&lt;/li&gt;
&lt;li&gt;Where it sucks&lt;/li&gt;
&lt;li&gt;My honest take: when you should and shouldn't do it&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;Feel free to &lt;strong&gt;connect with&lt;/strong&gt; me on &lt;a href="https://www.linkedin.com/in/denis-bratchikov/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🚀 &lt;strong&gt;More content coming soon - stay tuned!&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>monorepo</category>
      <category>frontend</category>
      <category>turborepo</category>
      <category>devops</category>
    </item>
    <item>
      <title>AI-Powered Code Refactoring: A Case Study Using Cursor with GPT-4o and Claude 3.7 Sonnet</title>
      <dc:creator>Denis Bratchikov</dc:creator>
      <pubDate>Tue, 04 Mar 2025 09:12:04 +0000</pubDate>
      <link>https://dev.to/denis_bratchikov/ai-powered-code-refactoring-a-case-study-using-cursor-with-gpt-4o-and-claude-37-sonnet-3hh</link>
      <guid>https://dev.to/denis_bratchikov/ai-powered-code-refactoring-a-case-study-using-cursor-with-gpt-4o-and-claude-37-sonnet-3hh</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hey there! Code refactoring is one of those necessary parts of keeping software clean and maintainable. But let’s be honest—it can get pretty tedious, especially when dealing with repetitive tasks across a ton of files. In this case study, I’ll walk you through how I used &lt;strong&gt;Cursor IDE&lt;/strong&gt;, powered by &lt;strong&gt;GPT-4o&lt;/strong&gt; and &lt;strong&gt;Claude 3.7 Sonnet&lt;/strong&gt;, to automate a refactoring task across 64 Playwright test specification files.&lt;/p&gt;

&lt;p&gt;The goal? Remove deprecated arguments from function calls with minimal manual intervention. Let’s dive in!&lt;/p&gt;




&lt;h2&gt;
  
  
  Preconditions
&lt;/h2&gt;

&lt;p&gt;Before we dive into the technical details, it's worth mentioning that Cursor was modified with specific rules from &lt;a href="https://github.com/grapeot/devin.cursorrules" rel="noopener noreferrer"&gt;this github&lt;/a&gt;. These rules helped ensure that the AI would divide tasks into smaller ones and perform one-by-one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem Statement
&lt;/h2&gt;

&lt;p&gt;During a recent redesign, several test helper functions had been temporarily modified to include additional parameters. Specifically, our Playwright test suite contained:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Project initialization functions&lt;/strong&gt; using:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;project_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;IConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;featureFlags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;feature-1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;feature-2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-ui-design&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;= our first target&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;some_other_params&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Snapshot verification&lt;/strong&gt; using:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toHaveSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;optional_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;IParams&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;shouldClip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;= our second target&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;some_other_props&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the redesign, the default behavior of &lt;code&gt;page.toHaveSnapshot()&lt;/code&gt; was restored, meaning &lt;code&gt;shouldClip: true&lt;/code&gt; property was no longer needed. Similarly, &lt;code&gt;new-ui-design: true&lt;/code&gt; was no longer relevant and needed to be removed from all test files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Approach: AI-Assisted Batch Refactoring
&lt;/h2&gt;

&lt;p&gt;To automate the changes, I used Cursor IDE with the Composer (Agent) mode, leveraging both GPT-4o and Claude 3.7 Sonnet for code modifications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initial Prompt (Single-Step Approach)
&lt;/h3&gt;

&lt;p&gt;My first attempt was a single prompt to process all &lt;code&gt;.spec.ts&lt;/code&gt; files at once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;In `application\e2e`, find all `.spec.ts` files and do the following:
1. Remove object with `shouldClip: true`
2. Remove the empty string before the removed object (if any)
3. Remove `'new-ui-design': true,` from the corresponding object
4. If the corresponding object (e.g., `featureFlags`) becomes empty, remove it
5. If the parent object of the corresponding object becomes empty, remove it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While the AI handled a lot of cases correctly, I ran into some hiccups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TSometimes the model &lt;strong&gt;added unintended modifications&lt;/strong&gt;, like changing snapshot names or adding more checks.&lt;/li&gt;
&lt;li&gt;It only partially applied changes across different files (fixing one method but skipping another in the same file).&lt;/li&gt;
&lt;li&gt;The results were not deterministic, varying on different runs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Refining the Approach: Two-Step Refactoring
&lt;/h3&gt;

&lt;p&gt;To make things smoother, I decided to split the refactoring into two separate tasks.&lt;/p&gt;

&lt;p&gt;Prompt 1 (Snapshot Cleanup)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;For each file `application\e2e\**\*.spec.ts`, do the following:
1. Remove object with `shouldClip: true`
2. If the corresponding object becomes empty, remove it
3. Remove the empty string before the removed object (if any)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prompt 2 (Feature Flags Cleanup)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;For each file `application\e2e\**\*.spec.ts`, do the following:
1. Remove `'new-ui-design': true,` from the corresponding object
2. If the corresponding object (e.g., `configureCustomFeatureFlags`) becomes empty, remove it
3. If the parent of the corresponding object becomes empty, remove it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why This Worked Better:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avoided unintended side effects from overlapping edits.&lt;/li&gt;
&lt;li&gt;Ensured consistency across all files.&lt;/li&gt;
&lt;li&gt;Reduced hallucinations, since each task was clearer and more focused.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Code update examples
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Refactoring Example 1, Before:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createProject&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;featureFlags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-ui-design&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-project&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;featureFlags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-ui-design&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;some-feature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;featureFlags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new-ui-design&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;baz&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Refactoring Example 1, After:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createProject&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadProject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-project&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;featureFlags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;some-feature&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;baz&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;ul&gt;
&lt;li&gt;Refactoring Example 2, Before::
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;snapshotParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;shouldClip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toHaveSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;snapshotParams&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toHaveSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-snapshot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;shouldClip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toHaveSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;shouldClip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Refactoring Example 2, After:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;snapshotParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toHaveSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;snapshotParams&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toHaveSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-snapshot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toHaveSnapshot&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Model Comparison: GPT-4o vs. Claude 3.7 Sonnet
&lt;/h2&gt;

&lt;p&gt;When using both &lt;strong&gt;GPT-4o&lt;/strong&gt; and &lt;strong&gt;Claude 3.7 Sonnet&lt;/strong&gt; for this task, I found that there was no significant difference in their performance or accuracy for these specific refactoring tasks. Both models struggled with the one-step approach and were able to process the files, making the changes I needed without introducing major differences in output quality with the two-step approach.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges and AI Limitations
&lt;/h2&gt;

&lt;p&gt;Using AI for this task was pretty great overall, but it wasn’t without its quirks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Task Execution Limit&lt;/strong&gt; - Cursor’s Composer has a default task limit of 25, after which manual confirmation (proceed / continue) is required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Deviations &amp;amp; Over-Eagerness&lt;/strong&gt; - in some runs, the model attempted to generate a Python script to automate the task despite explicit instructions to modify the files directly.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;p&gt;💡 &lt;strong&gt;AI is already a powerful tool&lt;/strong&gt; for large-scale code modifications, allowing for efficient batch refactoring with minimal manual oversight.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;What worked well&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using &lt;strong&gt;Cursor IDE’s Composer mode&lt;/strong&gt; with well-defined prompts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Splitting complex refactoring into smaller, focused tasks.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Leveraging &lt;strong&gt;both GPT-4o and Claude 3.7 Sonnet&lt;/strong&gt; to compare performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;❌ &lt;strong&gt;Challenges&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI still struggles with &lt;strong&gt;perfect consistency across multiple files&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;AI still struggles with &lt;strong&gt;following all instructions&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👨‍💻 &lt;strong&gt;Would I use AI for similar tasks again?&lt;/strong&gt; Absolutely. While AI isn't perfect, it's already a valuable assistant for handling tedious, repetitive refactoring tasks—giving developers more time to focus on higher-level problem-solving.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This was my first adventure with AI-assisted batch refactoring, and I’d love to hear what you think! Have you ever used AI for similar tasks? Let’s chat about it in the comments. 👇&lt;/p&gt;

&lt;p&gt;Feel free to &lt;strong&gt;connect with&lt;/strong&gt; me on &lt;a href="https://www.linkedin.com/in/denis-bratchikov/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🚀 &lt;strong&gt;More content coming soon - stay tuned!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S. This is my first article on development, so I’d really appreciate any feedback or suggestions! Thanks for reading!&lt;/em&gt; 😊&lt;/p&gt;

</description>
      <category>ai</category>
      <category>refactoring</category>
      <category>javascript</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
