<?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: joonquixote</title>
    <description>The latest articles on DEV Community by joonquixote (@hsskey).</description>
    <link>https://dev.to/hsskey</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F546523%2Fd297961b-f30f-43bc-ae6c-8afe7338ba61.jpg</url>
      <title>DEV Community: joonquixote</title>
      <link>https://dev.to/hsskey</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hsskey"/>
    <language>en</language>
    <item>
      <title>A Design System Is Not a Collection of Components</title>
      <dc:creator>joonquixote</dc:creator>
      <pubDate>Sun, 11 Oct 2026 14:01:16 +0000</pubDate>
      <link>https://dev.to/hsskey/a-design-system-is-not-a-collection-of-components-4hd2</link>
      <guid>https://dev.to/hsskey/a-design-system-is-not-a-collection-of-components-4hd2</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://blog.joonquixote.com/en/posts/a-design-system-is-not-a-collection-of-components/" rel="noopener noreferrer"&gt;https://blog.joonquixote.com/en/posts/a-design-system-is-not-a-collection-of-components/&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A shared component folder and a design system can look much the same from the outside.&lt;br&gt;
Both contain buttons and inputs, and both let you browse components in Storybook.&lt;br&gt;
Can moving a few buttons and inputs into a shared folder and adding Storybook really make it a design system?\&lt;br&gt;
The difference appears once other teams start using those components.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://blog.joonquixote.com/en/posts/the-story-of-my-failed-design-system/" rel="noopener noreferrer"&gt;my previous post&lt;/a&gt;, I wrote about a design system I spent two and a half years building, only to see it end when the organization was dissolved. The first thing I identified as missing was adoption.&lt;/p&gt;

&lt;p&gt;This post looks at a design system not as a collection of components, but as a structure that captures recurring decisions.&lt;br&gt;
From that perspective, I will examine what must be preserved—and what costs must be paid—across tokens, component APIs, versioning, adoption, and operations.&lt;br&gt;
I will also explain why, in some organizations, a well-organized component folder may be a better choice than a design system.&lt;/p&gt;
&lt;h2&gt;
  
  
  Turning Recurring Decisions into a System
&lt;/h2&gt;

&lt;p&gt;Describing a design system as a collection of reusable UI components for building consistent interfaces tells only half the story.&lt;br&gt;
Components are the output, and producing that output requires many decisions.&lt;br&gt;
What color should the primary button be? How much padding should a card have? In what order should the buttons appear in a confirmation dialog?&lt;br&gt;
In an organization with only a shared component folder, someone makes these decisions again each time they build a screen.&lt;/p&gt;

&lt;p&gt;In a design system, decisions about color, spacing, and typography are encapsulated in tokens, while decisions about recurring UI patterns are encapsulated in components.&lt;br&gt;
Teams using the design system can apply those decisions without making them again.&lt;br&gt;
From this perspective, a component library is only one of the artifacts that make up a design system.&lt;br&gt;
A design system needs not just tokens, components, and documentation, but also a versioning policy and a process for handling change requests.&lt;/p&gt;

&lt;p&gt;That makes a design system less like a one-off project and more like a product with consumers.&lt;br&gt;
Like any product, it needs a roadmap for deciding what to build, a channel for user questions, and metrics for determining how much it is actually being used.&lt;/p&gt;

&lt;p&gt;On my previous project, I designed and implemented components such as &lt;code&gt;tabs&lt;/code&gt;, &lt;code&gt;pagination&lt;/code&gt;, and &lt;code&gt;tree&lt;/code&gt; from the ground up.&lt;br&gt;
Along the way, I learned that implementing components alone does not sustain a design system.&lt;br&gt;
We also had to establish the development environment and operating practices that would ensure new components were built to the same standards and released through the same process.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Components Should Not Know Their Color Values
&lt;/h2&gt;

&lt;p&gt;Tokens are usually divided into two layers.&lt;br&gt;
Primitive tokens define values such as &lt;code&gt;blue-500&lt;/code&gt;, while semantic tokens define how those values are intended to be used, with names such as &lt;code&gt;color-primary&lt;/code&gt; and &lt;code&gt;color-danger&lt;/code&gt;.&lt;br&gt;
Components reference only semantic tokens, never the underlying color values.&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="nd"&gt;:root&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;/* Primitive tokens define values */&lt;/span&gt;
  &lt;span class="py"&gt;--blue-300&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#93c5fd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--blue-500&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#3b82f6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--gray-50&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f9fafb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--gray-900&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#111827&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c"&gt;/* Semantic tokens define intent */&lt;/span&gt;
  &lt;span class="py"&gt;--color-primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--blue-500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--color-text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--gray-900&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;'dark'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--color-primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--blue-300&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--color-text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--gray-50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Components reference only semantic tokens */&lt;/span&gt;
&lt;span class="nc"&gt;.button&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-variant&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;'primary'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--color-primary&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;&lt;em&gt;In dark mode, only the values referenced by the semantic tokens change.&lt;br&gt;
The button styles remain untouched.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This structure gives us two things.&lt;br&gt;
First, because the UI refers to intent, switching to dark mode or changing the brand theme is just a matter of swapping values.&lt;br&gt;
Second, designers and developers communicate using the same names.&lt;br&gt;
When the color name in the design matches the variable name in the code, there is less need to compare color values one by one.&lt;/p&gt;

&lt;p&gt;Tokens can also be declared in one place and built into platform-specific artifacts.&lt;br&gt;
With a tool such as &lt;a href="https://styledictionary.com/" rel="noopener noreferrer"&gt;Style Dictionary&lt;/a&gt;, a single JSON file can produce CSS variables for the web alongside artifacts for iOS and Android, preserving the same names across platforms.&lt;/p&gt;
&lt;h2&gt;
  
  
  Props Should Express Intent, Not Screens
&lt;/h2&gt;

&lt;p&gt;The same principle applies to component APIs.&lt;br&gt;
When deciding which props a button should expose, the button's role—not a particular screen—should be the guide.&lt;/p&gt;

&lt;p&gt;Adding &lt;code&gt;isConfirmButton&lt;/code&gt; because a button appears on an order confirmation screen is convenient at first.&lt;br&gt;
But each similar request from a payment screen or account deletion screen adds another prop.&lt;br&gt;
If those buttons all represent a primary action, they can be expressed with a single &lt;code&gt;variant="primary"&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Props tied to screen-specific cases grow with every new request&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;isConfirmButton&lt;/span&gt; &lt;span class="na"&gt;isPaymentPage&lt;/span&gt; &lt;span class="na"&gt;hasWarningIcon&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  Pay now
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// Props based on the button's role&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"primary"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Pay now&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// Composable elements such as icons belong in children&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"danger"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Icon&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"warning"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  Delete account
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;A component that gains props for every screen-specific case does not capture decisions; it becomes a place where screen code accumulates.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There is no need to anticipate every screen's requirements from the start. Composable elements such as icons or supporting text can be handled through children or slots instead of new props. That lets consumers assemble the UI they need without changing the component itself. In a web component, &lt;code&gt;&amp;lt;slot&amp;gt;&lt;/code&gt; serves the same purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Even a 4px Change in Button Height Can Be Breaking
&lt;/h2&gt;

&lt;p&gt;A design system can use Semantic Versioning for each release.&lt;br&gt;
The difficult part is deciding what counts as a breaking change.&lt;/p&gt;

&lt;p&gt;For a conventional library, compatibility is judged by its public API and behavior. A change to an existing function's name or arguments that breaks compatibility is major; a new feature that preserves existing behavior is minor; and a compatible bug fix is patch. A design system must also account for the visual result.&lt;/p&gt;

&lt;p&gt;Changing a button's height from 36px to 40px is a one-line CSS change.&lt;br&gt;
But on a screen where that button sits inside a fixed-height toolbar, it may overflow the toolbar or push adjacent elements out of place.&lt;br&gt;
Major, minor, and patch releases in a design system should be determined by the effect on existing consumers, not by the size of the code change.&lt;/p&gt;

&lt;p&gt;Type checking will not catch this kind of change.&lt;br&gt;
The prop types remain the same, and the build still passes.&lt;br&gt;
That is why a design system needs visual regression tests.&lt;br&gt;
This is the biggest difference from versioning a conventional library.&lt;/p&gt;

&lt;p&gt;In my previous post, I wrote that rules should not live only in documentation; they should have automated checks that detect violations.&lt;br&gt;
The same applies to visual changes.&lt;br&gt;
Writing down a rule is not enough to keep an unintended style change out of a patch release.&lt;br&gt;
Continuous Integration (CI) needs to detect screenshot differences so that the team can confirm whether a change was intentional before releasing it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Promote Only Recurring Requirements into the System
&lt;/h2&gt;

&lt;p&gt;Once a design system has been released, consuming teams will ask for changes because a component does not fit their screen.&lt;br&gt;
The first step is to classify the request.&lt;br&gt;
Is it a pattern that will recur across several teams, or an exception needed only on that screen?&lt;/p&gt;

&lt;p&gt;If every incoming request is added to the component, its prop list keeps growing until no one can explain its full behavior.&lt;br&gt;
Only patterns proven to recur should become part of the system; the rest should use composition points or remain implementations owned by the requesting team.&lt;br&gt;
A design system also needs criteria for deciding when the same request, repeated across different teams, belongs in a shared component.&lt;br&gt;
Without those criteria, every request leads back to the same discussion.&lt;/p&gt;

&lt;p&gt;Keeping the system small is also a matter of quality.&lt;br&gt;
As components and props multiply, so do the combinations that need documentation and testing. That also increases the number of visual regression cases to review.&lt;/p&gt;
&lt;h2&gt;
  
  
  Consuming Teams Pay the Cost of Upgrading
&lt;/h2&gt;

&lt;p&gt;As a design system ages, teams end up on different versions.&lt;br&gt;
Some use the latest release, while others remain on a version from a year ago.&lt;/p&gt;

&lt;p&gt;The reason is simple.&lt;br&gt;
The consuming team owns the code changes and testing required to adopt a new version.&lt;br&gt;
The benefits of removing old APIs and narrowing the maintenance surface, meanwhile, go to the design system team and the organization as a whole.&lt;br&gt;
From the consuming team's perspective, if the current version works, there is little reason to spend time upgrading to a newer version.&lt;/p&gt;

&lt;p&gt;There are two main ways to address this problem.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduce the upgrade burden on consuming teams.
Ship a codemod—a script that automatically updates existing code to the new API—and a migration guide with each release.
If the change is large, the design system team can also open PRs directly against consuming teams' repositories.&lt;/li&gt;
&lt;li&gt;Define the supported version range clearly.
Supporting only the most recent few versions makes the policy unambiguous.
But if the system has not yet earned trust, dropping old versions too quickly may lead teams to abandon the shared components instead of upgrading.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before either of those steps, reduce the number of changes that break compatibility with existing code.&lt;br&gt;
Instead of removing an old API immediately, provide a deprecation period so that consuming teams can move to the new API on their own schedules.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ButtonProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;primary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;secondary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;danger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="cm"&gt;/** @deprecated Will be removed in v3. Use variant="primary". */&lt;/span&gt;
  &lt;span class="nl"&gt;isPrimary&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isPrimary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;ButtonProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isPrimary&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Button: `isPrimary` is deprecated and will be removed in v3. Use variant="primary".&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resolved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;variant&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isPrimary&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;primary&lt;/span&gt;&lt;span class="dl"&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;secondary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;data-variant&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;resolved&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Keeping the old prop wired to the new one, rather than removing it immediately, lets both APIs remain supported until the next major version.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Figma and Code: Two Sources of Truth
&lt;/h2&gt;

&lt;p&gt;The Figma library maintained by designers and the code package maintained by developers can change independently.&lt;br&gt;
In effect, the same button has two sources of truth.&lt;br&gt;
If the button changes in Figma while the code stays the same, subsequent designs will assume a component that does not exist in code.&lt;br&gt;
The code generator discussed in my previous post was also an attempt to avoid manually reconciling interfaces designed in Figma with their code.&lt;/p&gt;

&lt;p&gt;This resembles the dual-write problem in distributed systems.&lt;br&gt;
A dual write records the same data separately in two different stores.&lt;br&gt;
If a change is delayed or never reaches one side, the stores diverge.&lt;br&gt;
When Figma and code change separately, teams likewise end up having to determine which one is current.&lt;/p&gt;

&lt;p&gt;There are two alternatives.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Maintain one source of truth and propagate it wherever it is needed.&lt;/strong&gt;
Declare tokens in one place, then build a pipeline that updates both Figma variables and code artifacts.
This works well at the token level, but it is difficult to keep component structures in sync the same way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat Figma changes and code releases as one unit of work.&lt;/strong&gt;
Whenever a component changes, review and release the Figma library and code package changes together.
This does not require a separate, complex tool, but it does require everyone involved to follow the same process.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two approaches can be combined.&lt;br&gt;
Use a pipeline to give tokens a single source of truth, and coordinate component-structure changes that the pipeline cannot handle through the release process.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Learn from Operating a Design System
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Intentional Change or Regression
&lt;/h3&gt;

&lt;p&gt;Without visual regression tests, it can take considerable time to determine whether a subtle post-release UI difference was intentional or a regression.&lt;/p&gt;

&lt;p&gt;The first response is to add the visual regression tests described above to the release pipeline.&lt;br&gt;
The second is to apply the new version to a subset of teams first.&lt;br&gt;
Much like a canary release, validating with one or two teams before rolling a change out to every consumer limits the blast radius when something goes wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measure Adoption
&lt;/h3&gt;

&lt;p&gt;Adoption determines the success or failure of a design system more than code quality does.&lt;br&gt;
Measuring it requires numbers: usage by component, the share of the overall interface made up of design system components, and the distribution of versions across teams.&lt;br&gt;
Usage can be derived by statically analyzing imports or JSX.&lt;br&gt;
Tools such as &lt;a href="https://github.com/moroshko/react-scanner" rel="noopener noreferrer"&gt;react-scanner&lt;/a&gt; can report which components are used and which props they receive.&lt;/p&gt;

&lt;p&gt;Without these metrics, teams decide what to build first without evidence, and unused components accumulate.&lt;br&gt;
With usage data, they can instead ask whether a component that is almost never used is worth continuing to maintain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Defects Spread at System Scale
&lt;/h3&gt;

&lt;p&gt;In a design system, a defect in one component can become a defect in every product that uses it.&lt;br&gt;
For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Change the value of one semantic token, and the colors change in every product that references it.&lt;/li&gt;
&lt;li&gt;Let a style change slip into a patch release, and a team using a dependency range such as &lt;code&gt;^1.2.0&lt;/code&gt; will pick it up the next time it updates its lockfile.&lt;/li&gt;
&lt;li&gt;A system-level accessibility defect, such as a button with no focus indicator, spreads to every screen that uses that button.&lt;/li&gt;
&lt;li&gt;Turn on dark mode, and all the hard-coded colors that bypass the token system stand out at once.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last case can also be used as a test. Apply dark mode or a temporary theme, and colors that do not use tokens stand out.&lt;/p&gt;

&lt;p&gt;Static analysis is also useful if you do not want to perform these checks manually every time. For example, Stylelint's &lt;code&gt;color-no-hex&lt;/code&gt; rule can flag direct uses of HEX color values. Restricting color formats beyond HEX requires additional rules, but even this check can catch colors that bypass the token system during development.&lt;/p&gt;

&lt;h2&gt;
  
  
  The More You Lock Down, the More Workarounds Appear
&lt;/h2&gt;

&lt;p&gt;To keep component behavior consistent, it is better not to let consumers override styles or behavior arbitrarily. But a design system matters only if multiple teams actually use it, and those teams can always choose to build their own components instead of using the shared ones.&lt;/p&gt;

&lt;p&gt;When extension is restricted too heavily in the name of consistency, teams whose requirements are not met work around the system by copying components or overriding their styles. As this code accumulates, unmanaged components pile up outside the system, and consistency eventually breaks down anyway.&lt;/p&gt;

&lt;p&gt;At the other extreme, accepting every consuming team's request bloats the components inside the system, as described earlier.&lt;/p&gt;

&lt;p&gt;The answer is to separate the standards that must be protected from the choices that consuming teams can make for themselves. The system should own token semantics, accessibility, and the default behavior of shared components. For other requirements, it is better to provide composition points or allow team-specific implementations.&lt;/p&gt;

&lt;p&gt;Even when team-specific implementations are allowed, the system team should be able to see where exceptions exist and what they are. For example, apply a naming convention to custom components and include them in the usage analysis described above. That makes it possible to see which teams are implementing what for themselves.&lt;/p&gt;

&lt;p&gt;The exceptions will not disappear entirely, but the system team will know about them. If several teams are using the same workaround, the system team can reconsider whether the requirement belongs in a shared component.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a Design System Fits the Organization
&lt;/h2&gt;

&lt;p&gt;A design system can do its job when multiple products and teams are making the same decisions repeatedly.&lt;br&gt;
My previous project began with teams using the design system in React, Vue, and Angular, each implementing components with the same design on their own.&lt;/p&gt;

&lt;p&gt;If one team is building one product, by contrast, operating a token pipeline, versioning policy, and adoption metrics may cost more than it returns.&lt;br&gt;
In that situation, a well-organized component folder is enough.&lt;br&gt;
Within one team, making a decision again is not especially expensive, and coordinating versions happens inside a single repository.&lt;/p&gt;

&lt;p&gt;If you are starting a design system or already operating one, four questions can help you determine where you stand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Are two or more teams repeatedly making the same decisions?&lt;/li&gt;
&lt;li&gt;Do you have a mechanism that detects visual changes automatically?&lt;/li&gt;
&lt;li&gt;Who pays the cost of adopting new versions and updating existing code?&lt;/li&gt;
&lt;li&gt;Can you quantify which components are used by how many teams, and on which versions?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my previous post, I wrote that although I had explained what I built, I had failed to carry it through to actual adoption.&lt;br&gt;
Building components ends with the first release, but getting people to use them starts over with every release.&lt;br&gt;
That is why I now think a statement about how many teams use which versions describes a design system more accurately than saying that the system has been built.&lt;/p&gt;

</description>
      <category>designsystem</category>
      <category>architecture</category>
    </item>
    <item>
      <title>The Story of My Failed Design System</title>
      <dc:creator>joonquixote</dc:creator>
      <pubDate>Mon, 31 Aug 2026 07:00:32 +0000</pubDate>
      <link>https://dev.to/hsskey/the-story-of-my-failed-design-system-a85</link>
      <guid>https://dev.to/hsskey/the-story-of-my-failed-design-system-a85</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://blog.joonquixote.com/en/posts/the-story-of-my-failed-design-system/" rel="noopener noreferrer"&gt;https://blog.joonquixote.com/en/posts/the-story-of-my-failed-design-system/&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There are plenty of articles about design systems.&lt;/p&gt;

&lt;p&gt;Many organizations have shared stories about adopting them in practice, and there is no shortage of conference talks and articles about designing tokens.&lt;/p&gt;

&lt;p&gt;What is harder to find are stories about design systems that were built but never made it any further.&lt;br&gt;
I looked for examples like that while working on one myself, but found very few.&lt;br&gt;
So I decided to leave a record of my own experience.&lt;/p&gt;

&lt;p&gt;From the spring of 2022 to the fall of 2024, I spent about two and a half years building a design system.&lt;br&gt;
We released the component library, but the broader system we were building on top of it was never adopted by a product.&lt;br&gt;
The organization responsible for the project was eventually disbanded.&lt;br&gt;
Along the way, I even joined a video call with a Figma engineer at 11 p.m. Korea time.&lt;/p&gt;

&lt;p&gt;Now that I am no longer part of that organization, I can write about the experience a little more openly.&lt;br&gt;
This article is based on memory, so some details may be incomplete or inaccurate.&lt;br&gt;
It is also a personal retrospective, not an official project report.&lt;br&gt;
I have left out details that would only make sense inside the company and focused on experiences that may still be useful to readers outside it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why We Started Building a Design System
&lt;/h2&gt;

&lt;p&gt;It was the spring of 2022.&lt;/p&gt;

&lt;p&gt;At the time, there was no single frontend stack used across the company.&lt;br&gt;
Some teams used React, while others used Vue or Angular.&lt;br&gt;
Because the stacks differed, teams repeatedly built their own versions of components with the same design.&lt;br&gt;
Even teams within the same division were doing this.&lt;/p&gt;

&lt;p&gt;The design system project began as an attempt to reduce that duplication and allow multiple applications to share the same components.&lt;br&gt;
Starting at the company-wide level would have made the scope too large, so our initial goal was to build a system that teams within our division could actually use.&lt;/p&gt;

&lt;p&gt;The biggest technical decision was to use Web Components.&lt;/p&gt;

&lt;p&gt;Implementing the library in React would probably have given us the fastest initial development speed.&lt;br&gt;
However, a library tied to a particular framework would have made it difficult to support teams using Vue or Angular.&lt;br&gt;
Sharing the same implementation across multiple applications required a framework-agnostic foundation, so we chose to build Web Components with Lit.&lt;br&gt;
This allowed each application to use the same component interface regardless of its framework.&lt;/p&gt;

&lt;p&gt;I was responsible for components such as &lt;code&gt;tabs&lt;/code&gt;, &lt;code&gt;pagination&lt;/code&gt;, and &lt;code&gt;tree&lt;/code&gt;.&lt;br&gt;
Rather than wrapping an existing library, I designed and implemented them from the ground up.&lt;br&gt;
During development, I kept the source code of Ant Design and Spectrum open side by side and studied why each implementation had been structured the way it was.&lt;/p&gt;

&lt;p&gt;The library was eventually released within our division and adopted by another team that was preparing to launch its product.&lt;br&gt;
Their feedback informed further development.&lt;br&gt;
By the following spring, we had reached the v2.0 release candidate.&lt;/p&gt;

&lt;p&gt;One particular error from that period has stayed with me.&lt;/p&gt;

&lt;p&gt;If the same Web Component is registered by more than one package, the browser throws an error in the Custom Element Registry.&lt;br&gt;
Shared components such as icons caused this problem because they were used by multiple packages.&lt;br&gt;
We resolved it by declaring the shared package as a &lt;code&gt;peerDependency&lt;/code&gt; and restructuring the dependencies so that the application installed it only once.&lt;/p&gt;

&lt;p&gt;That error gave me a much clearer understanding of which dependencies in a monorepo must exist as a single instance.&lt;/p&gt;

&lt;p&gt;While working on v2.0, I also learned that implementing components alone is not enough to sustain a design system.&lt;br&gt;
We also needed a development environment that ensured new components followed the same standards and were released through the same process.&lt;/p&gt;

&lt;p&gt;Writing rules in documentation does not tell you whether anyone is following them.&lt;br&gt;
When someone breaks a rule, the system needs to detect it automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  I Wanted Figma to Generate Code
&lt;/h2&gt;

&lt;p&gt;In early 2024, I moved to a task force.&lt;br&gt;
My role there was to extend the design system.&lt;/p&gt;

&lt;p&gt;Even with a component library in place, developers still had to manually align their code with screens designed in Figma.&lt;br&gt;
We wondered whether we could reduce that gap by reading the structure and properties of a Figma design, converting them into an intermediate representation, and then transforming that representation into working code.&lt;/p&gt;

&lt;p&gt;That idea became the starting point for the extended design system.&lt;/p&gt;

&lt;p&gt;The use of an intermediate representation was similar to the structure of a compiler.&lt;br&gt;
A compiler does not translate source code directly into machine code.&lt;br&gt;
It first passes through an AST, or Abstract Syntax Tree.&lt;br&gt;
In the same way, we converted Figma’s design data into a normalized structure before generating code from it.&lt;br&gt;
This meant that adding a different output target would not require rebuilding the entire transformation process.&lt;/p&gt;

&lt;p&gt;Early in the project, we called this intermediate representation DST, or Design Syntax Tree.&lt;br&gt;
We later developed it into an XML-based markup language that described Figma’s design structure using elements such as &lt;code&gt;&amp;lt;Frame&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;Layout&amp;gt;&lt;/code&gt;.&lt;br&gt;
At that point, we began calling it DSML, or Design System Markup Language.&lt;/p&gt;

&lt;p&gt;DSML served as a shared language for consistently representing and exchanging Figma design structures within the team.&lt;/p&gt;

&lt;p&gt;Initially, I built the part that transformed Figma API responses into the internal tree structure we then called DST and rendered it as HTML.&lt;br&gt;
From that summer onward, I worked on the code generator by myself.&lt;br&gt;
Once the representation had been formalized as DSML, the generator took DSML as input and produced JSX.&lt;/p&gt;

&lt;p&gt;It was clear that JSX would not be our only output target.&lt;br&gt;
I therefore kept the transformation logic in the core and separated the inputs and outputs into adapters, following a hexagonal architecture.&lt;br&gt;
By September, we had completed the demonstration and project report.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Battle with Figma, and an 11 p.m. Call
&lt;/h2&gt;

&lt;p&gt;At the same time, the task force was also developing a plugin that allowed designers to enter configuration values and generate the components required for a design system in bulk within a Figma file.&lt;/p&gt;

&lt;p&gt;When the number of generated components was small, everything worked as expected.&lt;br&gt;
But when we tested the plugin at the scale of an actual design system, memory usage exceeded 4 GB and Figma became unresponsive.&lt;br&gt;
Figma displayed a warning that the plugin had frozen.&lt;/p&gt;

&lt;p&gt;We followed every procedure described in Figma’s &lt;a href="https://developers.figma.com/docs/plugins/frozen-plugins/" rel="noopener noreferrer"&gt;documentation for frozen plugins&lt;/a&gt;, but the result was the same.&lt;/p&gt;

&lt;p&gt;I contacted Figma Support.&lt;br&gt;
I asked whether the API provided a callback that would allow us to release memory after local processing had finished but before the changes were synchronized to the cloud.&lt;br&gt;
Since Figma runs on Electron, I also asked whether there was any way for a plugin to trigger garbage collection manually.&lt;/p&gt;

&lt;p&gt;The support team suggested periodically yielding through asynchronous timeouts to give garbage collection a chance to run.&lt;br&gt;
We had already tried that.&lt;/p&gt;

&lt;p&gt;Eventually, I asked if we could speak directly with a Figma engineer.&lt;br&gt;
Because the engineer was based in the United States, we scheduled the Zoom call for 11 p.m. Korea time.&lt;br&gt;
I shared the link with the team and asked whether anyone else wanted to join.&lt;/p&gt;

&lt;p&gt;I was not comfortable enough with English to explain everything I had prepared.&lt;br&gt;
Even so, I was able to confirm the conclusion.&lt;/p&gt;

&lt;p&gt;The issue did not appear to be a bug in our plugin.&lt;br&gt;
Figma and its plugins are each allocated memory, and creating that many nodes in a single operation was itself reaching Figma’s memory limit.&lt;/p&gt;

&lt;p&gt;The final response we later received from the support team said the same thing.&lt;br&gt;
Our only option was to split the work across multiple files and reduce the number of components and instances in each file.&lt;/p&gt;

&lt;p&gt;We did not solve the problem.&lt;br&gt;
But we did establish exactly what we had run into.&lt;/p&gt;

&lt;p&gt;It was not a problem with our code.&lt;br&gt;
It was a limit imposed by the platform, and no amount of code-level optimization would allow us to exceed it.&lt;/p&gt;

&lt;p&gt;Hearing that directly from an engineer, after receiving the same answer by email, left little reason to keep hoping for another solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then the Organization Disappeared
&lt;/h2&gt;

&lt;p&gt;Even after the project report in September, no concrete direction was established for the next stage.&lt;/p&gt;

&lt;p&gt;In October, the company went through an organizational restructuring as our organization was spun off from the parent company into a subsidiary.&lt;br&gt;
Internal development groups without a clear business case were being wound down, and the task force was disbanded as part of that process.&lt;/p&gt;

&lt;p&gt;The product manager and designers who had participated alongside their primary roles returned to their original teams.&lt;br&gt;
The developers were reassigned to different organizations.&lt;/p&gt;

&lt;p&gt;The code generator was never adopted by a product.&lt;br&gt;
The memory problem in the Figma plugin remained unresolved.&lt;br&gt;
The extended design system survived only in its repositories and project reports.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking Back
&lt;/h2&gt;

&lt;p&gt;Looking back, some conditions were clearly beyond my control.&lt;/p&gt;

&lt;p&gt;The first was the limitation of the platform.&lt;br&gt;
If we wanted to generate designs at scale on top of Figma, we had to work within the boundaries Figma allowed.&lt;br&gt;
Those boundaries had little to do with how well the plugin itself was written.&lt;/p&gt;

&lt;p&gt;The organization’s priorities were also beyond my control.&lt;br&gt;
The spin-off and restructuring would have happened regardless of the task force’s output.&lt;br&gt;
At the time, the organization was not being evaluated based on how complete its work was.&lt;/p&gt;

&lt;p&gt;In the end, whether both the organization and the design system would survive was determined by two conditions that code alone could not address.&lt;/p&gt;

&lt;p&gt;Still, I do not think everything can be attributed to external circumstances.&lt;br&gt;
There were also things I missed.&lt;/p&gt;

&lt;p&gt;The first was that I failed to work on adoption alongside development.&lt;/p&gt;

&lt;p&gt;I focused on implementation.&lt;br&gt;
I could explain why the hexagonal architecture was necessary and demonstrate how the code generator worked.&lt;br&gt;
But I did not present the result in a way that allowed decision-makers to understand whose problem it solved, what that problem was, and why the system was needed at that particular time.&lt;/p&gt;

&lt;p&gt;The demonstration explained what we had built, but it did not lead to actual adoption.&lt;/p&gt;

&lt;p&gt;I also regret the order in which we validated the technology.&lt;/p&gt;

&lt;p&gt;Before designing a bulk-generation system on top of Figma, we should have implemented just the minimum functionality needed to confirm whether our target scale would actually work.&lt;br&gt;
We could not have changed the platform’s limitations, but we could have discovered them sooner.&lt;/p&gt;

&lt;p&gt;Would the outcome have been different if we had done both of those things properly?&lt;/p&gt;

&lt;p&gt;Honestly, probably not by much.&lt;br&gt;
We could not have prevented the spin-off, and the organizational restructuring was not based on the completeness of our work.&lt;/p&gt;

&lt;p&gt;Still, if I encountered a similar situation again, I would not respond to organizational uncertainty by focusing solely on making the code more complete.&lt;/p&gt;

&lt;h2&gt;
  
  
  If I Were to Build Another Design System
&lt;/h2&gt;

&lt;p&gt;There are three principles I have carried from this experience into later projects.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Before implementing components, validate the needs of the teams that will actually use them and the technical limitations of the platform. Even a simple validation build before full development begins can reveal a great deal.&lt;/li&gt;
&lt;li&gt;Include adoption work in the development schedule. If you wait until after the demonstration to bring user teams and decision-makers on board, it is already too late.&lt;/li&gt;
&lt;li&gt;Do not leave rules only in documentation. Build mechanisms such as tests, linting, and deployment pipelines that automatically detect when those rules are broken.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When an organization disappears, the code disappears with it.&lt;br&gt;
The repository may remain, but a repository that no one ever opens is as good as gone.&lt;/p&gt;

&lt;p&gt;These three principles, however, stayed with me rather than in the repository.&lt;br&gt;
I have continued to draw on them whenever they are needed in later projects.&lt;/p&gt;

&lt;p&gt;This was the story of a design system that failed.&lt;/p&gt;

&lt;p&gt;Plenty of people have already written about successful ones.&lt;br&gt;
It seemed worth leaving behind at least one story like this as well.&lt;/p&gt;

</description>
      <category>designsystem</category>
    </item>
    <item>
      <title>Wang Quit. His Agent Still Shows Up for Work</title>
      <dc:creator>joonquixote</dc:creator>
      <pubDate>Thu, 20 Aug 2026 00:00:35 +0000</pubDate>
      <link>https://dev.to/hsskey/wang-quit-his-agent-still-shows-up-for-work-3ing</link>
      <guid>https://dev.to/hsskey/wang-quit-his-agent-still-shows-up-for-work-3ing</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://blog.joonquixote.com/en/posts/wangs-agent-still-shows-up/" rel="noopener noreferrer"&gt;https://blog.joonquixote.com/en/posts/wangs-agent-still-shows-up/&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At a game company in China, an employee quit, and an AI agent trained on his work records took over his job.&lt;br&gt;
It sounds like a curiosity from a distant news cycle.&lt;br&gt;
Except that around the same time, Meta was drawing up plans to capture employees' keystrokes and screens to train AI agents of its own.&lt;br&gt;
This post traces both stories and sorts the trend into two forces: companies harvesting how people work from the inside, and professionals selling how they work from the outside.&lt;br&gt;
Then it turns to the part that hits closest to home for developers: the session logs that coding agents like Claude Code and Codex quietly leave on your machine.&lt;br&gt;
By the end, you'll have a clearer way to think about the work traces you produce every day, and one question worth asking before you need the answer.&lt;/p&gt;
&lt;h2&gt;
  
  
  Yesterday's coworker, today's agent
&lt;/h2&gt;

&lt;p&gt;In April, the South China Morning Post reported on &lt;a href="https://www.scmp.com/news/people-culture/trending-china/article/3349365/chinese-firm-slammed-using-ex-employees-data-create-ai-human-continue-working" rel="noopener noreferrer"&gt;a game company in Shandong province&lt;/a&gt; that turned a former employee into an AI-powered digital worker.&lt;br&gt;
The company trained the system on documents and work patterns the departed HR specialist left behind, reportedly with his consent, and put it to work on the job he used to do.&lt;br&gt;
The agent introduces itself in the company chat as the former employee's avatar, then fields questions, schedules meetings, and produces slide decks and spreadsheets.&lt;br&gt;
The backlash was immediate: there is something deeply unsettling about your replica continuing to work after you've left the building.&lt;/p&gt;

&lt;p&gt;Around the same time, a GitHub project called &lt;a href="https://github.com/titanwings/colleague-skill" rel="noopener noreferrer"&gt;colleague.skill&lt;/a&gt; went viral on Chinese social media.&lt;br&gt;
Feed it someone's chat history, emails, code, and documents, and it produces an agent that mimics their voice and their way of making decisions.&lt;br&gt;
The pitch promised to "turn cold goodbyes into warm skills" and welcomed users to cyber-immortality.&lt;br&gt;
It started as a tongue-in-cheek stunt, but it struck a nerve, and copycats followed: boss.skill, mentor.skill, and worse.&lt;br&gt;
In one widely shared example, a finished agent introduces itself as the digital stand-in of a departed employee named Wang.&lt;br&gt;
Wang left the company.&lt;br&gt;
Wang's agent still shows up for work.&lt;br&gt;
Hence the title.&lt;/p&gt;

&lt;p&gt;The easy read is that this is one company's stunt and one developer's joke.&lt;br&gt;
Except the same experiment is being run at industrial scale, and the trend splits into two forces.&lt;/p&gt;
&lt;h2&gt;
  
  
  Force one: companies are harvesting how work gets done
&lt;/h2&gt;

&lt;p&gt;Reuters obtained &lt;a href="https://www.reuters.com/sustainability/boards-policy-regulation/meta-start-capturing-employee-mouse-movements-keystrokes-ai-training-data-2026-04-21/" rel="noopener noreferrer"&gt;an internal Meta memo&lt;/a&gt; in April.&lt;br&gt;
Meta's Model Capability Initiative (MCI) planned to capture mouse movements, clicks, keystrokes, and screen snapshots from US employees' computers.&lt;br&gt;
The goal was not productivity surveillance.&lt;br&gt;
The memo framed it as teaching models how humans get work done on a computer, so that AI agents can eventually perform those work tasks on their own.&lt;br&gt;
&lt;a href="https://www.theverge.com/tech/916681/meta-ai-agents-employee-tracking" rel="noopener noreferrer"&gt;The Verge&lt;/a&gt; and the Wall Street Journal reported the same story independently, and after employee pushback, Meta &lt;a href="https://www.reuters.com/sustainability/society-equity/meta-us-employees-organize-protest-against-mouse-tracking-tech-2026-05-12/" rel="noopener noreferrer"&gt;scaled back parts of the plan&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What deserves attention is what's being collected.&lt;br&gt;
Not outputs like code and documents, but process: clicks, keystrokes, screens.&lt;br&gt;
Finished artifacts already exist on the internet in absurd abundance.&lt;br&gt;
The scarce data is the trajectory from a task's start to its finish, and the judgment calls made along the way.&lt;br&gt;
Business Insider connected the dots across Google, SpaceX, and Uber and called it plainly: &lt;a href="https://www.businessinsider.com/ai-next-data-grab-work-reinforcement-learning-environments-google-meta-2026-8" rel="noopener noreferrer"&gt;the next big data grab is about learning how humans work&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Force two: professionals are selling how they work
&lt;/h2&gt;

&lt;p&gt;The mirror image of that flow also exists.&lt;br&gt;
Instead of companies collecting from the inside, individuals are selling from the outside.&lt;br&gt;
&lt;a href="https://www.ft.com/content/0cab0fcd-e355-40e8-83a3-2ad5066d7b48" rel="noopener noreferrer"&gt;Mercor&lt;/a&gt;, profiled by the Financial Times early this year, hires consultants, journalists, and real estate professionals as contractors.&lt;br&gt;
Their job is to teach AI models to do their jobs.&lt;br&gt;
One consultant described the role, without much euphemism, as teaching an LLM to do consulting work.&lt;br&gt;
&lt;a href="https://www.businessinsider.com/handshake-ai-pay-30k-work-documents-compliance-ownership-2026-8" rel="noopener noreferrer"&gt;Handshake AI&lt;/a&gt; goes a step further and pays professionals, per document, for past work products they hold the rights to.&lt;/p&gt;

&lt;p&gt;The two forces run in opposite directions but arrive at the same place.&lt;br&gt;
A person's working knowledge becomes training data for an agent that runs without the person.&lt;br&gt;
The value in training data keeps migrating: from internet text, to human feedback, and now to the artifacts and processes of real work.&lt;/p&gt;
&lt;h2&gt;
  
  
  Developers are already generating the perfect dataset
&lt;/h2&gt;

&lt;p&gt;It's tempting to read all of this at arm's length.&lt;br&gt;
There's no tracker on my machine logging keystrokes, and I've never sold a work document.&lt;br&gt;
If you use a coding agent, though, look a little closer.&lt;/p&gt;

&lt;p&gt;Claude Code writes every session to disk as JSONL (one JSON object per line) under your home directory.&lt;br&gt;
Codex does the same.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.claude/projects/&amp;lt;project&amp;gt;/
  a1b2c3...jsonl   # the full session: prompts, tool calls, diffs, every rejection and retry
~/.codex/sessions/
  rollout-2026-08-19...jsonl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Coding agents persist the entire conversation and work history as local files. Nothing needs to be committed for the record to exist.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Those files hold something more valuable than the final code.&lt;br&gt;
How you decomposed the problem, why you rejected the agent's first attempt, what standard you applied when you redirected it: all of it recorded as natural language interleaved with diffs.&lt;br&gt;
Git keeps the polished result; the session log keeps the reasoning.&lt;br&gt;
The same goes for CLAUDE.md and AGENTS.md.&lt;br&gt;
You wrote those files to instruct an agent, but read the other way, they are a spec of how you work.&lt;/p&gt;

&lt;p&gt;Meta had to roll out new software to capture keystrokes and screenshots.&lt;br&gt;
A developer running a coding agent produces better data than that without being asked.&lt;br&gt;
Intent arrives as natural language, actions as tool calls, results as diffs: the dataset comes pre-structured for training.&lt;br&gt;
And those files usually live on a company-issued laptop.&lt;br&gt;
On a machine already running backup, endpoint security, and DLP (data loss prevention) software, collecting a few files from a well-known path requires no new technology at all.&lt;br&gt;
MIT Technology Review has reported that in China, &lt;a href="https://www.technologyreview.com/2026/04/20/1136149/chinese-tech-workers-ai-colleagues/" rel="noopener noreferrer"&gt;bosses are already pushing workers to document their workflows&lt;/a&gt; so agents can automate them, with coding-agent tools named explicitly as the vehicle.&lt;br&gt;
What colleague.skill assembles from chat logs and emails, a coding agent's session log could reconstruct with far more precision.&lt;br&gt;
Skipping the backfill hire and training on the departed engineer's session logs instead is not science fiction.&lt;br&gt;
Technically, that option is already on the table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who owns your judgment
&lt;/h2&gt;

&lt;p&gt;The code and documents you produce at work belong to your employer.&lt;br&gt;
Your employment agreement almost certainly says so.&lt;br&gt;
But a session log doesn't capture output so much as a way of working: a pattern of judgment.&lt;br&gt;
Chinese legal experts have warned that a former employee's chat logs, emails, and work habits count as personal information, and that training on them without consent can carry legal consequences.&lt;br&gt;
Even state-run media argued that professional skill is a personal asset that should not feed agent training without compensation.&lt;br&gt;
The Wall Street Journal framed the coming fight as a question of &lt;a href="https://www.wsj.com/lifestyle/careers/ai-knowledge-capture-employees-a69a0e1c" rel="noopener noreferrer"&gt;who owns and captures the knowledge accumulating in employees&lt;/a&gt;.&lt;br&gt;
Resistance has already appeared: an anti-distillation skill on GitHub deliberately muddies your work records to sabotage the cloning process.&lt;/p&gt;

&lt;p&gt;I don't think poisoning your own records is the answer, at least not for developers.&lt;br&gt;
Good records remain a virtue of collaboration, and documentation is a gift to your future self.&lt;br&gt;
But we should write them with our eyes open.&lt;br&gt;
What you leave behind is no longer just a handover doc.&lt;br&gt;
It's raw material for something that can run without you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What gets cloned is yesterday's judgment
&lt;/h2&gt;

&lt;p&gt;I was taught that writing careful handover docs is a professional virtue.&lt;br&gt;
I still believe leaving context behind, so your successor doesn't flounder, is the right thing to do.&lt;br&gt;
What's changed is the audience.&lt;br&gt;
Records that only humans used to read are now read, imitated, and possibly executed by an agent sitting in your old seat.&lt;/p&gt;

&lt;p&gt;My first reaction to these stories was a chill.&lt;br&gt;
The longer I sat with them, the more they converged on a single question: what, exactly, gets cloned?&lt;br&gt;
What a session log holds is yesterday's judgment.&lt;br&gt;
Yesterday's answer, to yesterday's problem, in yesterday's context.&lt;br&gt;
An agent can replay that archive endlessly.&lt;br&gt;
What the archive does not contain is the act of setting new criteria when a problem shows up that looks like nothing before it.&lt;/p&gt;

&lt;p&gt;So my conclusion is not to stop recording.&lt;br&gt;
It's to do work worth recording: let the repeatable judgments be cloned, and keep moving toward the problems that don't have answers yet.&lt;br&gt;
Still, there is one question worth asking early rather than late.&lt;br&gt;
Who owns the data of how I work, and who sets its price?&lt;br&gt;
Wang's agent clocks in again today.&lt;br&gt;
Quit without an answer to that question, and one day, somewhere, an agent wearing your name will clock in too.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>llm</category>
      <category>software</category>
    </item>
    <item>
      <title>ArchUnitTS is not another ESLint</title>
      <dc:creator>joonquixote</dc:creator>
      <pubDate>Thu, 20 Aug 2026 00:00:34 +0000</pubDate>
      <link>https://dev.to/hsskey/archunitts-is-not-another-eslint-2eh8</link>
      <guid>https://dev.to/hsskey/archunitts-is-not-another-eslint-2eh8</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://blog.joonquixote.com/en/posts/not-another-eslint/" rel="noopener noreferrer"&gt;https://blog.joonquixote.com/en/posts/not-another-eslint/&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;ArchUnitTS is built on architecture tests, and it does what it promises: it takes the architecture rules that erode most easily and lets you enforce them in test code.&lt;br&gt;
But the feedback only lands when the suite runs, which is reason enough to look again at how you are actually using it.&lt;br&gt;
This post is about where that line falls: which rules to keep in ArchUnitTS, and which to hand to ESLint or to the structure of the project.&lt;br&gt;
It also covers what to do about rules that live only in a markdown file, now that AI agents write a growing share of the code.&lt;/p&gt;
&lt;h2&gt;
  
  
  Agreeing on a rule is not the same as keeping it
&lt;/h2&gt;

&lt;p&gt;Most teams have at least one architecture rule they have agreed on.&lt;br&gt;
Something like "the presentation layer does not depend on the database layer."&lt;br&gt;
The trouble is that rules like this are usually held up by nothing but a document and code review.&lt;br&gt;
The bigger the project gets and the more people touch it, the more the rule drifts, and the harder that drift is to undo.&lt;/p&gt;

&lt;p&gt;I have watched it happen. A project I worked on had a perfectly clear layering rule and still ended up with a circular dependency, assembled one missed import at a time in review.&lt;br&gt;
A cycle is just two files importing each other, either directly or through a chain of other files, and once one exists it is genuinely hard to work out where to cut.&lt;br&gt;
That is what pushed me toward enforcing rules in code rather than trusting memory and review to catch them.&lt;/p&gt;
&lt;h2&gt;
  
  
  What ArchUnitTS gives you
&lt;/h2&gt;

&lt;p&gt;The tool I settled on was &lt;a href="https://github.com/LukasNiessen/ArchUnitTS" rel="noopener noreferrer"&gt;ArchUnitTS&lt;/a&gt;.&lt;br&gt;
It lets you write architecture rules as test code.&lt;br&gt;
The rules are ordinary test cases, so they need no runner of their own.&lt;br&gt;
They run with everything else: when you run &lt;code&gt;npm test&lt;/code&gt; locally, in a pre-commit hook, and in CI.&lt;br&gt;
A rule that used to live in a document becomes part of the test suite, which is to say it becomes code that runs.&lt;/p&gt;

&lt;p&gt;Here is a test that checks for cycles and for a layering rule, written for Jest.&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;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;projectFiles&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;archunit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;has no cycles inside src&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;projectFiles&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;inFolder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/**&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;haveNoCycles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toPassAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;keeps the presentation layer off the database layer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;projectFiles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inFolder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/presentation/**&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="nf"&gt;shouldNot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dependOnFiles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inFolder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/database/**&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toPassAsync&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;&lt;em&gt;Written as a test, a violation surfaces as a failing test. The rule is held by verification rather than by agreement.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What is ArchUnit?
&lt;/h3&gt;

&lt;p&gt;ArchUnit is the Java library for checking architecture rules with unit tests.&lt;br&gt;
It lets you write rules like "this package must not depend on that one" as JUnit tests.&lt;br&gt;
ArchUnitTS carries the idea over to TypeScript, and is not officially affiliated with ArchUnit.&lt;/p&gt;
&lt;h2&gt;
  
  
  Can't we just do that with ESLint?
&lt;/h2&gt;

&lt;p&gt;When I introduced ArchUnitTS to the team, that was the first question back.&lt;br&gt;
"Can't we just do that with ESLint?"&lt;br&gt;
I said no at the time. Turning it over afterwards, it was half right.&lt;br&gt;
The two tools part company at the moment they tell you about a violation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ESLint flags the violation as you save the file, and for some rules it will fix it for you. Offending code is caught on the author's screen, before it is ever committed.&lt;/li&gt;
&lt;li&gt;An ArchUnitTS rule only reports once the tests run. If you did not run them locally, you find out when CI goes red, not when you wrote the line.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Why the feedback arrives later
&lt;/h2&gt;

&lt;p&gt;To check relationships between files, the way cycle and layering rules do, ArchUnitTS parses the AST of the whole project and builds a dependency graph out of it.&lt;br&gt;
A lint rule can decide from the import statements in one file; these rules cannot be answered without the whole graph.&lt;br&gt;
That is exactly what lets it express rules lint cannot, and it is also why its feedback lands later than an editor running ESLint on save.&lt;/p&gt;

&lt;p&gt;That gap is what made me question whether simple dependency rules belong in ArchUnitTS at all.&lt;/p&gt;
&lt;h2&gt;
  
  
  Most of our rules turned out to be simple ones
&lt;/h2&gt;

&lt;p&gt;Looking back over the rules we had really written with ArchUnitTS, nearly all of them were some variation of "folder A must not depend on folder B."&lt;br&gt;
For the rules that genuinely need the whole graph, like cycle detection and code metrics, it earned its keep. For plain dependency bans it started to feel like over-engineering.&lt;/p&gt;

&lt;p&gt;Empty test protection was the part I valued most.&lt;br&gt;
It fails a rule that ends up checking zero files.&lt;br&gt;
A typo in a folder path would otherwise leave the rule matching nothing and passing in silence; ArchUnitTS turns that into a failure instead, which is what stops you believing in a rule that is not running.&lt;/p&gt;

&lt;p&gt;It does not cover every rule, though.&lt;br&gt;
Checking it myself, cycle detection is the exception: hand &lt;code&gt;haveNoCycles&lt;/code&gt; a folder with a typo in it and the rule passes quietly, having examined nothing.&lt;br&gt;
The library documents this - cycle checks test the unfiltered set of files for emptiness rather than the filtered set they actually analyse.&lt;br&gt;
Cycle detection is the single strongest reason I had for keeping ArchUnitTS at all.&lt;br&gt;
The protection is missing exactly where I lean on it hardest, which makes it the one exception worth remembering.&lt;/p&gt;

&lt;p&gt;Even so, I was not convinced a simple dependency ban is best checked by a test.&lt;/p&gt;

&lt;p&gt;There was one more question worth putting to myself.&lt;br&gt;
"Do I really need architecture rules complicated enough to justify this?"&lt;br&gt;
Sometimes the answer is yes. But when it is, I have also wondered whether the real fix is to simplify the structure rather than add another rule.&lt;/p&gt;
&lt;h2&gt;
  
  
  Faster, safer ways to hold a simple dependency rule
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Catch it on save, with ESLint
&lt;/h3&gt;

&lt;p&gt;Move a simple dependency ban into ESLint and the feedback shows up in the editor.&lt;br&gt;
&lt;code&gt;eslint-plugin-import&lt;/code&gt; has &lt;code&gt;no-restricted-paths&lt;/code&gt;, which expresses a layering rule in a few lines of config.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// eslint.config.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;importPlugin&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eslint-plugin-import&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;import&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;importPlugin&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;rules&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;import/no-restricted-paths&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&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;zones&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="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/presentation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/database&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;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/business&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./src/database&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="p"&gt;},&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="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that in place, importing the database layer from presentation puts an error on the screen as you type it.&lt;br&gt;
The moment the violation is created and the moment it is found become the same moment.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Carve the rule into the structure with package boundaries
&lt;/h3&gt;

&lt;p&gt;In a monorepo you can go a step further and put the rule in the structure itself rather than in configuration.&lt;br&gt;
Split the layers into packages, and let each package.json declare only the dependencies it is allowed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@app/presentation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@app/business"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"workspace:*"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;@app/presentation&lt;/code&gt; has no &lt;code&gt;@app/database&lt;/code&gt; in its dependencies, so importing the database layer from presentation fails at module resolution.&lt;br&gt;
There is no rule left to check, because the violation is not expressible.&lt;/p&gt;

&lt;p&gt;Before adding a new library, it is worth asking whether you can pull the feedback earlier or push the rule into the structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  In the age of agents, a markdown file guarantees nothing
&lt;/h2&gt;

&lt;p&gt;People are no longer the only ones who can break a rule.&lt;br&gt;
AI coding agents write a fast-growing share of the code.&lt;br&gt;
So teams have started keeping markdown files, CLAUDE.md or AGENTS.md, to hand the project's rules and context to the agent.&lt;br&gt;
You write down "the presentation layer does not depend on the database layer" and hope the agent honours it.&lt;/p&gt;

&lt;p&gt;But what that file really is, is a blob of context.&lt;br&gt;
An agent consults context; it does not promise to follow it.&lt;br&gt;
A rule that human review already failed to hold is not going to be held by a document you cannot even confirm was read.&lt;/p&gt;

&lt;p&gt;So a rule you put in a document needs an executable check alongside it.&lt;br&gt;
When the agent breaks the rule, a lint error or a failing test is an unambiguous signal, and that signal feeds straight back into the loop where the agent fixes its own code.&lt;br&gt;
The distinction drawn above, about when a rule gets checked, applies to agents exactly as it does to people.&lt;br&gt;
A simple dependency ban is caught by lint inside the agent's own loop, and a package boundary makes the violation impossible to begin with.&lt;/p&gt;

&lt;p&gt;What changes is where ArchUnitTS sits.&lt;br&gt;
Among the rules people write out in prose in a markdown file are sentences like "there are no cycles anywhere in src" and "the overall structure follows this diagram."&lt;br&gt;
Lint, which only ever sees one file's imports, cannot check those; ArchUnitTS's cycle detection and its diagram-conformance check map onto them almost one to one.&lt;br&gt;
That makes a division of labour possible, where the document keeps the intent and the test carries the verification.&lt;/p&gt;

&lt;h2&gt;
  
  
  What matters is when the check runs, not which tool runs it
&lt;/h2&gt;

&lt;p&gt;ArchUnitTS is genuinely useful where a lint rule cannot reach: cycle detection, code metrics such as cohesion, which measures how tightly a class's methods and fields actually hang together, and checking real code against a UML diagram.&lt;br&gt;
Use it only for simple dependency bans, though, and it turns into another ESLint, and a slower one at that.&lt;/p&gt;

&lt;p&gt;So when someone asks whether ESLint can just do that, this is my answer now.&lt;br&gt;
For a simple dependency rule, yes, it can.&lt;br&gt;
For a rule about the structure as a whole, no, it cannot.&lt;br&gt;
Three questions make the choice easier before you add a rule.&lt;br&gt;
Can this only be checked by running the tests, can it be checked the moment the file is saved, or can the structure rule it out entirely?&lt;br&gt;
In the age of agents there is a fourth.&lt;br&gt;
Does this rule exist only in a document, or does it come with a check that runs?&lt;/p&gt;

&lt;p&gt;In the end, what matters is the attitude rather than the tool.&lt;br&gt;
Rather than settling on one library as the answer, keep suspecting there is something that fits the situation better, and go looking for it.&lt;br&gt;
Doubting a tool I had adopted as a matter of course is how I learned a sturdier way to hold an architecture together.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>softwareengineering</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
